Search code examples
c#.netcovariancecontravariance

C# .Net Covariance - once more for old times sake?


So we have this:

public interface IWidget
{
    int Id { get; set; }
}

public class Widget : IWidget
{
    public int Id { get; set; }
}

public class WidgetProcessor
{
    public static void ProcessWidgets1(IList<IWidget> widgets)
    { }

    public static void ProcessWidgets2<T>(IList<T> widgets) where T : IWidget
    { }
}

I get why this wouldn't compile: WidgetProcessor.ProcessWidgets1(new List<Widget>()); The C# rules around covariance wisely say it shouldn't, or you could get up to all sorts of naughtiness, as explained at length elsewhere.

But ProcessWidgets2: what the...?
How is it that this does compile and run: WidgetProcessor.ProcessWidgets2(new List<Widget>());

Look forward to having my ignorance removed, but I can't see how ProcessWidgets1 and ProcessWidgets2 are (effectively) any different.


Solution

  • ProcessWidgets2<T> is a generic method. When you call it with new List<Widget>(), the compiler infers that the type T is Widget, which matches the constraints, and calls it, since List<T> implements IList<T>.

    It's potentially easiest to look at it as if it was broken up into multiple calls:

    IList<Widget> temp = new List<Widget>();
    WidgetProcessor.ProcessWidgets2<Widget>(temp); // Widget is an IWidget, so this matches constraints
    

    There is no variance in play here, since List<T> directly implements IList<T>, and you're calling with a specific type that's inferred by the compiler.