Non-generic Wrapper instead of Base Class or Interface

A common solution to treating a generic type as non-generic is to implement an interface or make the generic type extend from a non-generic base class. Among other reasons, this allows you to instantiate a collection of generic types. There are varying implementations of this pattern, but they all seem to share this common strategy. In this article I present an alternate approach favoring composition over inheritance which I argue is more appropriate in circumstances where the intent is to break type safety.

To clarify, I will follow Steven Lowe’s argumentation on misuse of inheritanceSemantically, the statement “SomeType<T> is SomeType” is not always true; SomeType<T> is not a proper subtype of SomeType when the base type exposes type-specific bits (as Jon Skeet puts it). In this case the extending type imposes more restrictions than the base type. When there is a need to expose type-specific bits (implying type casts are involved), using inheritance does not follow the Liskov substitution principle (LSP). Regardless, such subtypes seem common. Just consider Microsoft’s List<T> implementing a non-generic IList which to quote Eric Lippert, “is a bit odd, since List for any type other than object does not fulfill the full contract of IList”.

From this it becomes clear there are real-world scenarios where a generic type needs to be accessed in a non-generic way (e.g. during reflection as I argued previously). Type safety is temporarily broken, leaving it is up to the caller to guarantee only the correct types are used. How then to improve on the following common implementation? Note that since the non-generic interface is implemented explicitly, the caller consciously needs to cast to ISomeType, somewhat alleviating the problem of possible misuse.

public interface ISomeType
{
    object Value { get; set; }
}

public class SomeType<T> : ISomeType
{
    public T Value { get; set; }

    // Explicit implementation (cast to ISomeType needed to be used).
    object ISomeType.Value
    {
        get { return this.Value; }
        set { this.Value = (T)value; }
    }
}

We need a non-generic interface but are given a generic interface, so why not apply the adapter pattern?

An adapter helps two incompatible interfaces to work together. […] Interfaces may be incompatible but the inner functionality should suit the need.

Since the class we are trying to create an adapter for is generic, our adapter implementation needs to be generic too. However, we can expose the non-generic interface which will be used by the client. Just like a usual adapter, the non-generic wrapper contains the adaptee and refers all of the IAdaptor calls to it, casting to T where necessary.

public interface IAdaptor
{
    object Value { get; set; }
}

class NonGenericWrapper<T> : IAdaptor
{
    private readonly Adaptee<T> _adaptee;

    public NonGenericWrapper(Adaptee<T> adaptee)
    {
        _adaptee = adaptee;
    }

    public object Value
    {
        get { return _adaptee.Value; }
        set { _adaptee.Value = (T) value; }
    }
}

Furthermore, to facilitate the creation of this non-generic wrapper (and in addition hiding its implementation) the wrapper can optionally be initialized from within the adaptee and added as a member. This approach seems similar to how user interface controls in .NET expose the window handle they operate on as a member variable, allowing for unsafe operations when the control class does not offer the required functionality.

public class Adaptee<T>
{
    public T Value { get; set; }

    public IAdaptor NonGeneric { get; private set; }

    public Adaptee()
    {
        NonGeneric = new NonGenericWrapper<T>(this);
    }
}
Pattern which can be used to expose a non-generic interface from within a generic class.
Pattern which can be used to expose a non-generic interface from within a generic class.

This approach still requires boiler-plate code to be written and maintained (NonGenericWrapper<T>), but no longer breaks the Liskov substitution principle. Ideally creating non-generic wrappers can be automated, of which I created an early prototypical solution before (not functional in all scenarios). Until then, this solution provides a more robust implementation for complex scenarios using generics, like collections of generic types with varying type parameters on which the same operations need to be performed. This might be the topic of a future post.

Author: Steven Jeuris

I have a PhD in Human-Computer Interaction and am currently working both as a software engineer at iMotions and as a postdoc at the Technical University of Denmark (DTU). This blend of research and development is the type of work which motivates and excites me the most. Currently, I am working on a distributed platform which enables researchers to conduct biometric research 'in the wild' (outside of the lab environment). I have almost 10 years of professional software development experience. Prior to academia, I worked for several years as a professional full-stack software developer at a game development company in Belgium: AIM Productions. I liked the work and colleagues at the company too much to give up entirely for further studies, so I decided to combine the two. In 2009 I started studying for my master in Game and Media Technology at the University of Utrecht in the Netherlands, from which I graduated in 2012.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: