Reputation: 97
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.
Upvotes: 3
Views: 128
Reputation: 203815
With the first example you could execute the code:
widgets.Add(new SomeOtherWidget());
If widgets
were actually allowed to be a List<Widget>
then you would have put a SomeOtherWidget
into a list of Widget
objects. That would be bad, as when you got an object out it might not be a Widget
at all.
With the second example, widgets.Add(new SomeOtherWidget());
won't compile. SomeOtherWidget
won't be of type T
. You will only ever be allowed to call Add
on the list with an object of type T
, rather than any old IWidget
object, so it's able to maintain type safety.
Upvotes: 0
Reputation: 564851
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.
Upvotes: 5