Внедрение вложенных общих интерфейсов

У меня есть следующие классы/интерфейсы:

// Model
public class A : IA { }
// ModelLogic
public class B : IB<A> { }

// Model Interface
public interface IA { }
// ModelLogic Interface
public interface IB<T> where T : IA { }

Я пытаюсь создать новый экземпляр, используя следующий код:

IB<IA> foo = new B();

Я получаю следующую ошибку:

Cannot implicitly convert type 'B' to 'IB<IA>'. An explicit conversion exists (are you missing a cast?)

Может кто-нибудь объяснить, почему это невозможно?

Ответы

Ответ 1

ОК, замените A на Fish, IA на IAnimal, B на Aquarium и IB<T> на IContainer<T>. И мы добавим элемент в IContainer<T>, а вторую реализацию IAnimal:

// Model
public class Fish : IAnimal { }
public class Tiger : IAnimal { }
// ModelLogic
public class Aquarium : IContainer<Fish> 
{ 
    public Fish Contents { get; set; }
}

// Model Interface
public interface IAnimal { }
// ModelLogic Interface
public interface IContainer<T> where T : IAnimal 
{ 
    T Contents { get; set; }
}

IContainer<IAnimal> foo = new Aquarium(); // Why is this illegal?
foo.Contents = new Tiger(); // Because this is legal!

Вы можете поместить Tiger в foo - foo вводится как контейнер, который может содержать любое животное. Но вы можете поместить Рыбу в Аквариум. Поскольку операции, которые вы можете законно выполнять на Aquarium, отличаются от операций, которые вы можете выполнять на IContainer<IAnimal>, типы несовместимы.

Функция, которую вы хотите, называется общей ковариацией интерфейса, и она поддерживается С# 4, но вам нужно доказать, что вы никогда не ставите тигра в свой аквариум. Что вы хотите сделать:

// Model
public class A : IA { }
// ModelLogic
public class B : IB<A> { }

// Model Interface
public interface IA { }
// ModelLogic Interface
public interface IB<out T> where T : IA { }

Обратите внимание на аннотацию ковариации на IB. Этот out означает, что T может использоваться только как выходной, а не как вход. Если T - это только выход, то нет возможности кому-то поставить тигра в этот аквариум, потому что нет возможности "вставить" или способ.

Я написал несколько статей в блогах, пока мы добавляли эту функцию в С#; если вас интересуют соображения дизайна, которые вошли в эту функцию, см.:

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/

Ответ 2

Чтобы исправить ваш код, просто измените

public interface IB<T> where T : IA { }

к

public interface IB<out T> where T : IA { }

Ответ 3

Нелегко увидеть, когда у вас пустые интерфейсы. У вас есть один метод M в интерфейсе IB:

public interface IB<T> where T : IA 
{ 
    void M(T t); 
}

И вот реализация B:

public class B : IB<A>
{
    public void M(A t)
    {
        // only object of type A accepted 
    }
}

Тогда у вас есть объект C, который также реализует IA:

public class C : IA { } 

Итак, если ваш код будет возможен, вы можете позвонить:

IB<IA> foo = new B();
foo.M(new C());

Проблема в том, что класс B принимает только объекты типа A. Ошибка!