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

时间:2010-08-20 07:06:11

标签: c# 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;

那么,任何人都可以解释这是如何无效的?并且,我没有心情去解释 - 是否有一行代码我可以用来实际演绎?

3 个答案:

答案 0 :(得分:31)

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的“out”值时才是安全的。通用逆向(其他方式相反)只有在你将值“放入”API时才是安全的(例如,一般比较可以比较任何两个形状的面积可以被视为一个比较广场)。

在C#4中,这可用于通用接口和通用委托,而不是类 - 并且仅适用于引用类型。有关详细信息,请参阅MSDN,阅读&lt; plug&gt;阅读C# in Depth, 2nd edition,第13章&lt; / plug&gt;或者Eric Lippert关于这个主题的blog series。此外,我在2010年7月在国家数据中心进行了一小时的讨论 - 视频可用here

答案 1 :(得分:17)

每当有人提出这个问题时,我都会尝试将他们的例子转化为使用明显非法的更为知名的类(这就是Jon Skeet has done in his answer;但是我更进一步了执行此翻译)。

我们将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);
}

糟糕,突然我们在List<string>上进行了枚举,我们遇到了Random个对象。那不好。

希望这会使问题更容易理解。

答案 2 :(得分:9)

这需要协方差或逆变,其支持仅限于.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;