Подтвердить что ты не робот

Generics и casting - не могут передавать унаследованный класс в базовый класс

Я знаю, что это старо, но я все еще не очень хорошо разбираюсь в этих проблемах. Может ли кто-нибудь сказать мне, почему следующее не работает (выбрасывает исключение runtime о кастинге)?

public abstract class EntityBase { }
public class MyEntity : EntityBase { }

public abstract class RepositoryBase<T> where T : EntityBase { }
public class MyEntityRepository : RepositoryBase<MyEntity> { }

И теперь линия литья:

MyEntityRepository myEntityRepo = GetMyEntityRepo(); // whatever
RepositoryBase<EntityBase> baseRepo = (RepositoryBase<EntityBase>)myEntityRepo;

Итак, может ли кто-нибудь объяснить, как это недопустимо? И, я не в настроении объяснить - есть ли строка кода, которую я могу использовать, чтобы на самом деле сделать это?

4b9b3361

Ответ 1

RepositoryBase<EntityBase> не является базовым классом MyEntityRepository. Вы ищете обобщенную дисперсию, которая существует в С# в ограниченной степени, но не будет применяться здесь.

Предположим, что у вашего класса RepositoryBase<T> был такой метод:

void Add(T entity) { ... }

Теперь рассмотрим:

MyEntityRepository myEntityRepo = GetMyEntityRepo(); // whatever
RepositoryBase<EntityBase> baseRepo = (RepositoryBase<EntityBase>)myEntityRepo; 
baseRepo.Add(new OtherEntity(...));

Теперь вы добавили другой тип объекта в MyEntityRepository... и это не может быть прав.

В принципе, общая дисперсия является безопасной только в определенных ситуациях. В частности, общая ковариация (именно то, что вы здесь описываете) безопасна только тогда, когда вы только когда-либо получаете значения "API"; общая контравариантность (которая работает наоборот) безопасна только тогда, когда вы только когда-либо добавляете значения в API (например, общее сравнение, которое может сравнивать любые две формы по площади, можно рассматривать как сравнение квадратов).

В С# 4 это доступно для общих интерфейсов и общих делегатов, а не для классов - и только для ссылочных типов. См. MSDN для получения дополнительной информации, прочитайте <plug> прочитайте С# в Глубина, 2-е издание, глава 13 </plug> или Эрик Липперт серия блога по этой теме. Кроме того, я рассказал об этом в NDC в июле 2010 года - видео доступно здесь; просто найдите "дисперсию".

Ответ 2

Всякий раз, когда кто-то задает этот вопрос, я пытаюсь взять их пример и перевести его на что-то, используя более известные классы, которые явно незаконны (это то, что Джон Скит сделал в своем ответ, но я делаю это еще дальше, выполняя этот перевод).

Позвольте заменить MyEntityRepository на MyStringList следующим образом:

class MyStringList : List<string> { }

Теперь вы, похоже, хотите, чтобы MyEntityRepository был спрятан до RepositoryBase<EntityBase>, аргументация заключалась в том, что это должно быть возможным, поскольку MyEntity происходит от EntityBase.

Но string происходит от object, не так ли? Таким образом, по этой логике мы сможем сделать a MyStringList до List<object>.

Посмотрим, что может произойти, если мы допустим это...

var strings = new MyStringList();
strings.Add("Hello");
strings.Add("Goodbye");

var objects = (List<object>)strings;
objects.Add(new Random());

foreach (string s in strings)
{
    Console.WriteLine("Length of string: {0}", s.Length);
}

О-оу. Внезапно мы перечислим List<string>, и мы наталкиваемся на объект Random. Это нехорошо.

Надеюсь, это затруднит понимание проблемы.

Ответ 3

Это требует ковариации или контравариантности, поддержка которых ограничена в .Net и не может использоваться в абстрактных классах. Вы можете использовать дисперсию на интерфейсах, так что возможным решением вашей проблемы является создание IRepository, который вы используете вместо абстрактного класса.

    public interface IRepository<out T> where T : EntityBase { //or "in" depending on the items.
    }
    public abstract class RepositoryBase<T> : IRepository<T> where T : EntityBase {
    }
    public class MyEntityRepository : RepositoryBase<MyEntity> {
    }

    ...

    IRepository<EntityBase> baseRepo = (IRepository<EntityBase>)myEntityRepo;