泛型和强制转换 - 不能将继承的类强制转换为基类

Posted

技术标签:

【中文标题】泛型和强制转换 - 不能将继承的类强制转换为基类【英文标题】:Generics and casting - cannot cast inherited class to base class 【发布时间】:2010-08-20 07:06:11 【问题描述】:

我知道这已经过时了,但我仍然不太了解这些问题。谁能告诉我为什么以下方法不起作用(抛出关于投射的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;

那么,任何人都可以解释这是无效的吗?而且,我没有心情解释 - 有没有一行代码我可以用来实际执行此转换?

【问题讨论】:

查看C# Covariance and Contravariance FAQ 感谢大家的回答。简而言之 - 我现在使用基本接口 (RepositoryBase : IRepository) 解决了这个问题。结果我只需要在我得到的实例上执行函数,让类本身处理其他事情。 【参考方案1】:

RepositoryBase&lt;EntityBase&gt; 不是 MyEntityRepository 的基类。您正在寻找在 C# 中有限存在但不适用于此处的 泛型变量

假设你的 RepositoryBase&lt;T&gt; 类有这样的方法:

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# in Depth, 2nd edition,第 13 章 或 Eric Lippert 的 blog series 关于该主题。另外,我在 2010 年 7 月的 NDC 上就这个问题进行了一个小时的讨论 - 视频可在 here 获得。

【讨论】:

感谢 Jon 的解释和非常有趣的材料的链接。我一定会考虑阅读这本书并观看视频。 你的视频链接好像坏了。 @ZacharyBurns:已修复,谢谢。我有点惊讶它仍然可用:)【参考方案2】:

每当有人问这个问题时,我都会尝试以他们的示例并将其转换为使用更知名的显然非法的类的东西(这就是Jon Skeet has done in his answer;但我通过执行此操作更进一步翻译)。

让我们将MyEntityRepository 替换为MyStringList,如下所示:

class MyStringList : List<string>  

现在,您似乎希望将MyEntityRepository 转换为RepositoryBase&lt;EntityBase&gt;,原因是这应该是可能的,因为MyEntity 派生自EntityBase

但是string 派生自object,不是吗?所以按照这个逻辑,我们应该能够将MyStringList 转换为List&lt;object&gt;

让我们看看如果我们允许的话会发生什么......

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&lt;string&gt; 时遇到了Random 对象。这不好。

希望这能让问题更容易理解。

【讨论】:

我已经开始在示例中更进一步 - 我曾经使用字符串和对象,但在 Eric Lippert 的建议下,我开始使用现实世界的对象......水果/苹果/香蕉效果很好用“你不能在一堆香蕉中加一个苹果”的说法。【参考方案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;

【讨论】:

以上是关于泛型和强制转换 - 不能将继承的类强制转换为基类的主要内容,如果未能解决你的问题,请参考以下文章

使用受保护的继承公开基类转换函数

C#通过反射将派生类转换为基类异常

<继承问题>可以把基类对象赋值给子类对象么?

无法将参数 1 从派生指针转换为基类指针引用

C++ 容器与继承

不明白java中的泛型和抽象类有啥区别,感觉他们作用一样啊,为啥要用2种方法呢