Дженерики и приведение - не могут привести наследуемый класс к базовому классу - PullRequest
26 голосов
/ 20 августа 2010

Я знаю, что это старо, но я все еще не очень хорошо понимаю эти проблемы. Может кто-нибудь сказать мне, почему следующее не работает (выдает исключение 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;

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

Ответы [ 3 ]

31 голосов
/ 20 августа 2010

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

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

void Add(T entity) { ... }

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

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

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

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

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

17 голосов
/ 20 августа 2010

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

Давайте заменим MyEntityRepository на MyStringList, например так:

class MyStringList : List<string> { }

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

Но string происходит от object, не так ли?Таким образом, по этой логике мы должны быть в состоянии привести 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);
}

Э-э-э,Внезапно мы перечислим более 1025 * и натолкнемся на 1026 * объект.Это нехорошо.

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

9 голосов
/ 20 августа 2010

Это требует ковариации или контравариантности, поддержка которой ограничена в .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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...