OdeToCode IC Logo

A Simple Case For C# Generic Constraints

Tuesday, June 18, 2013

Imagine you need to support multiple projects that retrieve “widgets” of different types from various data sources. You might start with some simple type definitions that build a core API around widgets and widget storage.

public class Widget
{
    public int Key { get; set; }        
}

public interface IWidgetStore
{
    Widget GetWidget(int key);
}

The IWidgetStore interface is needed so that reusable algorithms can work with widgets.

Given these definitions, someone can build a widget specific for their project.

public class NamedWidget  : Widget
{        
    public string Name { get; set; }
}

As well as a concrete class for storing widgets.

public class WidgetStore : IWidgetStore
{
    public Widget GetWidget(int key)
    {          
        return _store.Single(u => u.Key == key);
    }
    
    readonly List<NamedWidget> _store = new List<NamedWidget>();
}

But the friction in development will start because specific projects don’t want to work with Widget, they want to work with NamedWidget where business properties like Name are defined. If every IWidgetStore can only return Widget, every caller has to cast the return value of GetWidget to a type like NamedWidget.

var widget = (NamedWidget)store.GetWidget(id);
var name = widget.Name;

One solution is to use an explicit interface definition to hide the IWidgetStore from application developers.

public class WidgetStore : IWidgetStore
{
    public NamedWidget GetWidget(int key)
    {            
        return _store.Single(u => u.Key == key);
    }

    Widget IWidgetStore.GetWidget(int key)
    {
        return GetWidget(key);
    }

    readonly List<NamedWidget> _store = new List<NamedWidget>();
}

Now the application code can work with NamedWidget and still pass the WidgetStore to other low level algorithms which will access the object through the IWidgetStore interface.

But, if the application is trying to stay flexible or testable it might want to continue working with widget stores through the IWidgetStore interface too, just like the reusable algorithms, and then the code would still need to cast the return value of GetWidget.

Another solution is to use a generic type parameter to parameterize a widget store with the desired type of widget.

public interface IWidgetStore<TWidget>
{
    TWidget GetWidget(int key);
}

Now a concrete widget store can implement the interface and return derived Widget objects.

public class WidgetStore<TWidget> : 
    IWidgetStore<TWidget>
    where TWidget: Widget
{
    public TWidget GetWidget(int key)
    {
        return _store.Single(u => u.Key == key);
    }

    readonly List<TWidget> _store = new List<TWidget>();
}

What makes it all work is the generic constraint where TWidget: Widget. The C# complier assumes a TWidget is of type Object unless told otherwise, so the LINQ query in GetWidget will fail with a compiler error because Object doesn’t have a Key property.

The generic constraint tells the C# compiler that a TWidget has to be a Widget, and since a Widget has to have a Key property the C# compiler is happy to compile the code.