2013-08-12 3 views
1

Скажем, у меня есть эти два класса«ковариации» в параметрах шаблона

class BaseClass 
{ 
    protected HashSet<BaseClass> container; 
} 


class DerivedClass : BaseClass 
{ 
    DerivedClass() 
    { 
     container = new HashSet<DerivedClass>(); 
    } 
}       

Тогда я получаю сообщение об ошибке: Не удается преобразовать.

Поскольку каждый DerivedClass (должен) быть BaseClass, я не совсем уверен, почему эта ошибка возникает, но это так.

Цель состоит в том, чтобы BaseClass выполнял множество операций на container, причем только особые особенности, связанные с DerivedClass, - среди тех, которые требуют, чтобы контейнер имел тип HashSet<DerivedClass>.

Как эта цель обычно достигается?

+0

Ковариация допускается только в интерфейсах. http://blogs.msdn.com/b/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx Я бы рекомендовал прочитать всю серию. – lukegravitt

+0

Это не похоже на великолепную архитектуру.Почему базовый класс будет работать со своими производными типами? Почему набор не может быть 'new HashSet ()'? –

+0

Ваш дизайн не безопасен по типу. Если метод в «BaseClass» пытается добавить произвольный экземпляр «BaseClass» в 'container', у вас возникнет конфликт типа. – recursive

ответ

3

каждый DevrivedClass является BaseClass, но не наоборот. HashSet<T> не может быть ковариантным, поскольку он позволяет выполнять операции записи (Add). Так что в вашем случае это будет возможно:

class BaseClass 
{ 
    protected HashSet<BaseClass> container; 

    public DoSomething() 
    { 
     container.Add(new BaseClass()); // not legal if container is really a List<DerivedClass> 
    } 
} 

Вы можете изменить тип контейнера в качестве ковариации:

class BaseClass 
{ 
    protected IEnumerable<BaseClass> container; 
} 

class DerivedClass : BaseClass 
{ 
    DerivedClass() 
    { 
     container = new HashSet<DerivedClass>(); 
    } 
} 

Но тогда только класс производный (а) может добавлять элементы в container (которые могут работать в вашем дизайне).

Вы также можете попробовать сделать базовый класс родовое:

class BaseClass<T> where T:BaseClass<T> 
{ 
    protected HashSet<T> container; 
} 

class DerivedClass : BaseClass<DerivedClass> 
{ 
    DerivedClass() 
    { 
     container = new HashSet<DerivedClass>(); 
    } 
} 

который выглядит немного странно, но опять же это выглядит странно для класса содержит список объектов одного и того же типа, так что, возможно, в ваш реальный сценарий имеет смысл.

2

Что вы могли бы сделать это:

class BaseClass<T> where T : BaseClass<T> 
{ 
    protected HashSet<T> container; 
} 


class DerivedClass : BaseClass<DerivedClass> 
{ 
    DerivedClass() 
    { 
     container = new HashSet<DerivedClass>(); 
    } 
} 
+0

Я исправил синтаксис для вас. – Femaref

+0

Такое отношение кажется хорошим примером того, когда есть, вероятно, лучший дизайн, который не использует наследование. – lukegravitt

0

Covariance поддерживается только для интерфейсов. То есть container должен быть объявлен как ISet (который наследует IEnumerable, который является ковариантным).

EDIT

Как говорит Нико, это не будет работать, как ожидалось. Чтобы выяснить, почему это невозможно, рассмотрите: HashSet<BaseClass>.Add(DerivedClassA) - действительная операция, но HashSet<DerivedClassB>.Add(DerivedClassA) - нет. Поэтому HashSet<DerivedClass>не может быть a HashSet<BaseClass>.

+6

'IEnumerable' является ковариантным,' ISet' не потому, что он позволяет выполнять операции чтения и записи –

0

A bit more expansion: the base class has a number of derived classes; the derived classes have a lot of commonality in functionality. But, critically, there only needs to be 1 of each derived class's variant information; no redundancy. So the question is- how to be as DRY as possible. Note that BaseClass in this example would have methods that operate on Container. It might be possible to have a FlyWeightDerivedClassFactory further out in the architecture where new DerivedClasses are instantiated to work around this, but that de-encapsulates things.

Рассмотрим создание обобщенного класса контейнера, который предоставляет интерфейс для базового класса для вызова своих операций:

interface IContainer { 
    void Operation1(); 
    void Operation2(); 
} 

class Container<T> : IContainer where T : BaseClass { 
    private HashSet<T> _internalContainer; 

    ... 
} 

class BaseClass { 
    protected IContainer container; 
} 

class DerivedClass : BaseClass { 
    DerivedClass() { 
     container = new Container<DerivedClass>(); 
    } 
} 

Все операции конкретного типа должны быть внутренними по отношению к Container<T> (или класса, производного от него если это необходимо), в то время как методы интерфейса неочевидны по типу.

Смежные вопросы