Это ошибка ковариации в С# 4?
В следующем фрагменте кода я ожидал, что он сможет неявно отбрасывать от elements
до baseElements
, потому что TBase
неявно конвертируется в IBase
.
public interface IBase { }
public interface IDerived : IBase { }
public class VarianceBug
{
public void Foo<TBase>() where TBase : IBase
{
IEnumerable<TBase> elements = null;
IEnumerable<IDerived> derivedElements = null;
IEnumerable<IBase> baseElements;
// works fine
baseElements = derivedElements;
// error CS0266: Cannot implicitly convert type
// 'System.Collections.Generic.IEnumerable<TBase>' to
// 'System.Collections.Generic.IEnumerable<IBase>'.
// An explicit conversion exists (are you missing a cast?)
baseElements = elements;
}
}
Однако я получаю сообщение об ошибке, указанное в комментарии.
Цитата из спецификации:
Тип T<A1, …, An>
является дисперсионно-конвертируемым для типа T<B1, …, Bn>
, если T
является либо интерфейсом, либо типом делегата, объявленным с параметрами типа варианта T<X1, …, Xn>
, и для каждого параметра типа варианта Xi
один имеет место следующее:
-
Xi
является ковариантным и существует неявное обращение ссылки или идентичности от Ai
до Bi
-
Xi
является контравариантным и существует неявное обращение ссылки или идентичности от Bi
до Ai
-
Xi
является инвариантным и существует тождественное преобразование от Ai
до Bi
Проверка моего кода, похоже, соответствует спецификации:
-
IEnumerable<out T>
- тип интерфейса
-
IEnumerable<out T>
объявляется с параметрами типа варианта
-
T
является ковариантным
-
существует неявное ссылочное преобразование от TBase
до IBase
Итак - это ошибка в компиляторе С# 4?
Ответы
Ответ 1
Отклонение работает только для ссылочных типов (или есть преобразование идентичности). Неизвестно, что TBase
является ссылочным типом, если вы не добавите : class
:
public void Foo<TBase>() where TBase : class, IBase
так как я мог бы написать:
public struct Evil : IBase {}
Ответ 2
Марк прав - я как раз собирался вставить тот же ответ.
См. часто задаваемые вопросы о ковариации и контравариантности:
http://blogs.msdn.com/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx
Из FAQ:
"Разница поддерживается только в том случае, если параметр типа является ссылочным типом.
Разница не поддерживается для типов значений
Следующее не компилируется:
// int is a value type, so the code doesn't compile.
IEnumerable<Object> objects = new List<int>(); // Compiler error here.