C# .Net Covariance - еще раз для старых времен?
Итак, у нас есть это:
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
{ }
}
Я понимаю, почему это не скомпилируется: WidgetProcessor.ProcessWidgets1(new List<Widget>());
В C# правила ковариации мудро говорят, что не должны, или вы можете столкнуться со всевозможными глупостями, как подробно объяснялось в другом месте.
Но ProcessWidgets2: что за...?
Как получается, что это компилируется и запускается: WidgetProcessor.ProcessWidgets2(new List<Widget>());
С нетерпением жду удаления моего невежества, но я не вижу, как ProcessWidgets1 и ProcessWidgets2 (эффективно) отличаются.
2 ответа
ProcessWidgets2<T>
это общий метод. Когда вы звоните с new List<Widget>()
компилятор делает вывод, что тип T
является Widget
, который соответствует ограничениям, и вызывает его, так как List<T>
инвентарь IList<T>
,
Потенциально проще всего смотреть на него так, как будто он разбит на несколько вызовов:
IList<Widget> temp = new List<Widget>();
WidgetProcessor.ProcessWidgets2<Widget>(temp); // Widget is an IWidget, so this matches constraints
Здесь нет различий в игре, так как List<T>
непосредственно реализует IList<T>
и вы вызываете с определенным типом, который выводится компилятором.
В первом примере вы можете выполнить код:
widgets.Add(new SomeOtherWidget());
Если widgets
на самом деле было разрешено быть List<Widget>
тогда вы бы положили SomeOtherWidget
в список Widget
объекты. Это было бы плохо, так как, когда вы достали объект, это может быть не Widget
совсем.
Со вторым примером, widgets.Add(new SomeOtherWidget());
не скомпилируется. SomeOtherWidget
не будет типа T
, Вам будет разрешено только позвонить Add
в списке с объектом типа T
, а не любой старый IWidget
объект, поэтому он может поддерживать безопасность типов.