Использование базового класса в качестве универсального для IEnumerable <T> - PullRequest
4 голосов
/ 17 ноября 2011

У меня хорошее понимание ООП в целом, наследования и полиморфизма, интерфейсов и т. Д. Я столкнулся со странной ситуацией и не понимаю, почему она вообще не работает ...

РЕДАКТИРОВАТЬ: Хорошо, я обнаружил, что ковариация (или контравариантность?) Может решить эту проблему, но принципиально

мы все еще используем .NET 2.0

Как я могу решить эту проблему, не переходя на C # 4.0?

Вот ситуация. Учитывая эти два класса:

public class CustomCollectionType<T> : IEnumerable<T>
{
    /* Implementation here, not really important */
}

public class Entity : EntityBase
{
    /* Implentation here, not important */
}

Компилятор жалуется, когда я пытаюсь использовать этот общий метод

public void LoopThrough(IEnumerable<EntityBase> entityList)
{
    foreach(EntityBase entity in entityList) 
    {
        DoSomething(entity);  
    }
}

И попробуйте использовать это так:

CustomCollectionType<Entity> entityList;
/* Add items to list */

LoopThrough(entityList);

Ошибка говорит, что я не могу конвертировать из CustomCollectionType<Entity> в IEnumerable<EntityBase>.

Однако я могу сделать это:

public void Foo(EntityBase entity)
{
    entity.DoSomething();
}

Foo(new Entity());

А это:

public void Bar(IEnumerable<Entity> entityList)
{ ... }

CustomCollectionType<Entity> entityList;

Bar(entityList);

Почему я не могу создать свой метод с самыми высокими классами в иерархии? Типы, очевидно, совместимы ... Я что-то упустил?

РЕДАКТИРОВАТЬ: Я хочу решить эту проблему без каких-либо изменений существующих классов, поэтому о создании нового метода в любом из классов или реализации дополнительного интерфейса не может быть и речи.

Ответы [ 4 ]

15 голосов
/ 17 ноября 2011

Давайте рассмотрим ваш первый случай.У вас есть:

class Bowl<T> : IEnumerable<T> {}
class Apple : Fruit {}
...
void LoopThrough(IEnumerable<Fruit> fruits) ...

и вы звоните

Bowl<Apple> apples = whatever;
LoopThrough(apples);

Это не удается в C # 3.0;это успешно в C # 4.0, потому что IEnumerable<T> теперь ковариантен в T;последовательность яблок может быть использована как последовательность фруктов.

Чтобы заставить ее работать в C # 3.0, вы можете использовать оператор последовательности Cast.

Bowl<Apple> apples = whatever;
LoopThrough(apples.Cast<Fruit>());

Чтобы заставить ее работать вC # 2.0, самостоятельно реализуйте оператор последовательности Cast .Это всего лишь пара строк кода.

Обратите внимание, что в C # 4.0 все еще будет недопустимо говорить:

Bowl<Fruit> fruits = new Bowl<Apples>();

, поскольку, конечно, вы можете сказать:

fruits.Add(new Orange());

и вы просто кладете апельсин в миску, которая может содержать только яблоки.

4 голосов
/ 17 ноября 2011

Да, .NET может раздражать, потому что он не может привести все ваши общие параметры за один выстрел. Вместо этого, возможно, попробуйте общий подход, подобный этому, чтобы облегчить проблему.

public void LoopThrough<T>(IEnumerable<T> entityList) where T : EntityBase
{
    foreach(T entity in entityList) 
    {
        DoSomething(entity as EntityBase);  
    }
}
0 голосов
/ 17 ноября 2011

Возможно, я что-то упускаю, но если предполагается, что ваше намерение CustomCollectionType должно быть базой сущностей, но при этом разрешено использовать IEnumerable, разве у вас не должно быть ИТ в качестве основы базы сущностей?например ...

public class CustomCollectionType<T> : EntityBase, IEnumerable<T>
{
    /* Implementation here, not really important */
}

Тогда ваш LoopThrough ДОЛЖЕН работать, так как пользовательский тип коллекции выводится из EntityBase и имеет любые ожидаемые методы, свойства и т. д., доступные ... или в худшем случае, вы бы набралиприведите его при вызове функции, такой как

Bowl<Apple> apples = whatever;
LoopThrough((EntityBase)apples);
0 голосов
/ 17 ноября 2011

Типы являются совместимыми, но в некотором смысле несовместимыми, основная причина здесь заключается в том, что вы используете базовый тип в параметре как IEnumerable, а не фактический тип, хотя база Entity является entitybase, потому что правила для параметров типа и ограничений имеют несколько последствий дляуниверсальное поведение класса, особенно в отношении наследования и доступности членов

универсальные классы инвариантны.Другими словами, если входной параметр задает List<BaseClass>,, вы получите ошибку во время компиляции, если попытаетесь указать List<DerivedClass>.

И вот почему вы получаете эту ошибку, где, как вВаш последний, например, T такой же.

Однако он бы работал абсолютно нормально, если бы вы использовали интерфейсы, потому что все интерфейсы совместимы

  public class Entity : IEntityBase  
  {      /* Implentation here, not important */  }  

public void LoopThrough(IEnumerable<IEntityBase> entityList)  
 {      foreach(IEntityBase entity in entityList)       
     {          DoSomething(entity);        }  }  

, и тогда ваш метод будет работать нормально

CustomCollectionType<Entity> entityList;      LoopThrough(entityList);  

, поскольку entitylist имеет тип IEntityBase

Другая вещь, которую вы можете попробовать, это typeof (чтобы получить тип) или использовать приведение, и это должно работать

...