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.
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.