Проблема с приведением List‹ClassA›, где ClassA:Generic‹Int32› к List‹Generic‹Int32››

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

public class EntityBase<T>
{
    public T Id { get; set; }

}

И это реализаторы:

public class ClassA : EntityBase<Int32>
{
     ...
}

public class ClassB : EntityBase<Int64>
{
     ...
}

А в коде, который не знает о классах - ClassA и ClassB он знает только о существовании EntityBase‹...>, я делаю примерно так:

     // Here for sure I get the list of `ClassA`
     object obj = GetSomeHowListOfClassA();
     List<EntityBase<Int32>> listOfEntityBases = (List<EntityBase<Int32>>)obj;

И я получаю ошибку:

Unable to cast object of type 'System.Collections.Generic.List`1[...ClassA]' to type 'System.Collections.Generic.List`1[...EntityBase`1[System.Int32]]'.

Я исправляю это так:

var listOfEntityBases = new List<EntityBase<Int32>>(obj);

Но мне это не нравится, потому что я создаю новый список‹>. Есть ли способ бросить его? Спасибо за любой аванс.


person Maris    schedule 04.04.2013    source источник
comment
попробуйте прочитать эти stackoverflow.com/questions/2184551/ stackoverflow.com/questions/245607/ stackoverflow. com/questions/4038125/ковариация-до-диез и т. д.   -  person Lanorkin    schedule 04.04.2013
comment
Проблема не в понимании ковариантности и контравариантности. Но проблема в том, что List‹› не поддерживает контравариантность. Вот почему я прошу любой обходной путь от этой проблемы.   -  person Maris    schedule 04.04.2013
comment
можете ли вы переделать его, чтобы использовать MyClass<T> : EntityBase<T> вместо ClassA и ClassB?   -  person Lanorkin    schedule 04.04.2013
comment
Не думаю, что это решит проблему.   -  person Maris    schedule 04.04.2013
comment
Это может позволить вам создавать универсальные методы, но это действительно зависит от вашего дизайна и использования. Итак, вы не хотите создавать новый список (как этот List<EntityBase<Int32>> listOfEntityBases = obj.Cast<EntityBase<Int32>>().ToList();) - вы ищете чистый состав?   -  person Lanorkin    schedule 04.04.2013
comment
да. Я ищу чистый актерский состав. ) А также в этой части кода я внесу некоторую модификацию и перекину ее в другую часть моей программы, в которой я верну ее к (ClassA).   -  person Maris    schedule 04.04.2013
comment
на самом деле я очень смущен, почему вам нужно получить объект. Рассматривали ли вы общее ограничение where T : EntityBase<TKey>   -  person Aron    schedule 04.04.2013
comment
Вам не нужно ЗНАТЬ о ClassA, если у вас есть общий аргумент, в который вы помещаете ClassA, И вы все еще можете иметь свой List<ClassA>   -  person Aron    schedule 04.04.2013


Ответы (2)


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

  • ковариация в C# не работает для классов;
  • интерфейсы IList<T> и ICollection<T> не являются ковариантными.

Единственный вариант, который вы можете сделать здесь (кроме создания копии списка), — это приведение к IEnumerabe<T>:

var listOfEntityBases = (IEnumerable<EntityBase<Int32>>)obj;
person Dennis    schedule 04.04.2013
comment
Я лично предпочитаю IEnumerable<EntityBase<Int32>> listOfEntityBases = obj. Компилятор будет вынужден выполнять неявное (безопасное) приведение, а не явное (потенциально небезопасное) приведение. Эвристически это, конечно, безопасно, но если мы когда-нибудь изменим ClassA, это может сломаться во время выполнения. - person Aron; 04.04.2013
comment
@Арон: IEnumerable<EntityBase<Int32>> listOfEntityBases = obj- что? Это даже не скомпилируется. - person Dennis; 04.04.2013
comment
Я имею в виду var obj = new List<ClassA>(); IEnumerable<EntityBase<Int32>> listOfEntityBases = obj; для кастинга... - person Aron; 04.04.2013
comment
Гм... Как я уже писал в своем вопросе - And in the code, which dont know about classes - ClassA and ClassB it knows only about existance of the EntityBase<...>, I do something like this: - person Maris; 04.04.2013
comment
@Aron: в вопросе говорится, что obj имеет тип System.Object. - person Dennis; 04.04.2013

Вы не можете этого сделать по понятным причинам. Предположим, что эта строка кода будет работать:

 List<EntityBase<Int32>> listOfEntityBases = (List<EntityBase<Int32>>)obj;

Это означает, что после этой строки вы можете сказать следующее

listOfEntityBases.Add(new EntityBase<Int32>());

но на самом деле эта строка одновременно добавит объект EntityBase<Int32> к вашему obj типа List<ClassA>, что определенно является InvalidCast.

Таким образом, вы просто не можете объявить одну и ту же переменную как List<ClassA> и List<EntityBase<Int32>> одновременно.

Хотя для IEnumerable<T> это легко допустимо, так как вы не можете добавлять новые значения для такой коллекции.

И именно поэтому у них есть in и out в объявлении дженериков.

person Lanorkin    schedule 04.04.2013
comment
То, что Ланоркин пытается описать, — это разница между ковариантностью и контравариантностью. Интерфейс не может быть одновременно Ковариантным и Контравариантным, не будучи одним и тем же интерфейсом. - person Aron; 04.04.2013
comment
То, что я пытаюсь описать, это то, почему это не может быть брошено. Различия четко описаны в ссылках, которые я посоветовал @Maris, в то время как они все еще хотят просто разыграть. - person Lanorkin; 04.04.2013