Общая иерархия интерфейса
У меня есть какой-то общий интерфейс, связанный один с другим.
public interface IA
{
int val { get; set; }
}
public interface IB<T> where T:IA
{
T a_val { get; set; }
}
public interface IC<T> where T : IB<IA>
{
T b_val { get; set; }
}
public class a:IA
{
public int val { get; set; }
}
public class b:IB<a>
{
public a a_val { get; set; }
}
public class c:IC<b>
{
public b b_val { get; set; }
}
Для последнего класса c у меня есть ошибка:
Тип "b" нельзя использовать в качестве параметра типа "T" в универсальном типе или методе "IC". Не существует неявного преобразования ссылок из 'b' в 'IB'.
Как я могу правильно использовать универсальные интерфейсы в этом случае?
2 ответа
T
в IC<T>
должен быть IB<IA>
, Вы дали ему IB<A>
, У вас нет гарантии, что IB<A>
может быть использован в качестве IB<IA>
просто так A
инвентарь IA
,
Подумайте об этом так: если IB<T>
означает "я могу есть что-нибудь типа T", и IA
означает "Я фрукт", и A
означает "яблоко", то IB<IA>
означает "я могу есть любые фрукты", и IB<A>
означает "я могу съесть любое яблоко". Если какой-то код хочет накормить вас бананами и виноградом, он должен IB<IA>
не IB<A>
,
Давайте предположим IB<A>
может быть преобразован в IB<IA>
и посмотрим что пойдет не так
class AppleEater : IB<Apple>
{
public Apple a_val { get; set; }
}
class Apple : IA
{
public int val { get; set; }
}
class Orange : IA
{
public int val { get; set; }
}
...
IB<Apple> iba = new AppleEater();
IB<IA> ibia = iba; // Suppose this were legal.
ibia.a_val = new Orange(); // ibia.a_val is of type IA and Orange implements IA
И теперь мы просто установили iba.val
, который является свойством типа Apple
на ссылку на объект типа Orange
,
Вот почему преобразование должно быть незаконным.
Итак, как вы можете сделать это законным?
Поскольку код стоит, вы не можете, потому что, как я только что показал, он не безопасен.
Вы можете сделать это законным, отметив T
как out
как это: interface IB<out T>
, Тем не менее, тогда незаконно использовать T
в любом "входном контексте". В частности, вы не можете иметь какое-либо свойство типа T
у этого есть сеттер. Если мы введем это ограничение, то проблема исчезнет, потому что a_val
не может быть установлен на экземпляр Orange
потому что это только для чтения.
Этот вопрос очень часто задают на SO. Ищите вопросы о "ковариации и контравариантности" в C# для множества примеров.
Я не знаю, можно ли это сделать проще (и немного более чисто), но этот код компилируется:
public interface IA
{
int val { get; set; }
}
public interface IB<T> where T : IA
{
T a_val { get; set; }
}
public interface IC<T, U> where T : IB<U> where U : IA
{
T b_val { get; set; }
}
public class a : IA
{
public int val { get; set; }
}
public class b : IB<a>
{
public a a_val { get; set; }
}
public class c : IC<b, a>
{
public b b_val { get; set; }
}
Что еще более важно, это не позволяет вам делать что-то подобное:
public class a1 : IA
{
public int val { get; set; }
}
public class c : IC<b, a1>
{
public b b_val { get; set; }
}
Компилятор выдает следующую ошибку:
Тип "ConsoleApplication2.b" нельзя использовать в качестве параметра типа "T" в универсальном типе или методе "ConsoleApplication2.IC". Не существует неявного преобразования ссылок из "ConsoleApplication2.b" в "ConsoleApplication2.IB".
И это действительно крутая особенность, не так ли?