Общая иерархия интерфейса

У меня есть какой-то общий интерфейс, связанный один с другим.

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

И это действительно крутая особенность, не так ли?

Другие вопросы по тегам