2017-04-12 49 views
2

我无法投泛型类型到另一种泛型类型,除了演员应该是有效的投泛型类型的失败

我想存档是在短期(为MyModel实施IModelMyImplementation实施IImplementation) :

IImplementation<IModel> implementation = new MyImplementation<MyModel>(); 
Assert.IsNull(implementation as IImplementation<IModel>); 

这有点令人困惑,因为类型应该是有效的。

完整的概念模型:

interface IModel {} 

class MyModel : IModel {} 

interface IImplementation<TModel> where TModel : IModel { } 

class MyImplementation<TModel> : IImplementation<TModel> 
    where TModel : IModel { } 

public void CallRegister() 
{ 
    var implementation = new MyImplementation<MyModel>(); 
    var instance = CastModel(implementation); 
    Assert.IsNotNull(instance); //this assert fails! 
} 

private object CastModel<TModel>(IImplementation<TModel> implementation) where TModel : IModel 
{ 
    return implementation as IImplementation<IModel>; 
} 

我需要这个转换,使我能够多IImplementation S保存到同一Dictionary<Type, IImplementation<IModel>>,这里的关键是通过做typeof(TModel)获得。 要做到这一点类型安全我不想使用Dictionary<Type, object>

  • 为什么演员表失败?有没有额外的资源呢?它与Invalid Cast of Type Constrained C# Generic类似的问题,但它不解释为什么只是它不起作用。
  • 如果这种类型的转换是不可能的,如上所述的归档类似于字典的功能的最佳方式是什么?

回答

4

这种转换不允许有很好的理由。我们举一个问题更为明显的例子。我们有类Animal,Cat : AnimalDog : Animal。现在让我们这样做:

List<Animal> list = new List<Cat>(); // Seems to be possible at first glance. 
// An now comes the problem: 
list.Add(new Dog()); // Seems to be possible as well. 

但是等等!这份名单实际上是猫的名单!我们正在尝试添加一条狗。即使将new Animal()添加到list(静态类型为List<Animal>)也不起作用。

因此,两种类型T<A>T<B>在C#中不是分配兼容的,即使AB都是!

您需要另一种方法。


你可以做的是用带泛型类型约束的泛型方法来包装你的字典。

public class MyImplementationDict 
{ 
    private readonly Dictionary<Type, object> _internalDict = new Dictionary<Type, object>(); 

    public void Add<T>(IImplementation<T> item) 
     where T : IModel 
    { 
     _internalDict.Add(typeof(T), item); 
    } 

    ... 
} 
+0

很好的回答!有限制的字典是一个不错的主意,可能是我的出路。我只使用'Add','Remove'和'Get',所以它应该很容易做到。非常感谢! –

6

虽然奥利弗的回答有想法跨越为什么这通常出现问题,有一种方法,使你的程序这项工作。

你想要的功能被称为通用接口协方差。协方差是如果CatAnimal,那么IFoo<Cat>IFoo<Animal>

协方差在C#只能在以下情况下:

  • 的“外”类型是一个接口,委托或阵列。没有类或结构。
  • 如果接口或委托,类型必须在编译时标记为支持协方差。数组可以免费得到(不安全!)协方差。
  • “内部”类型 - 变化的类型 - 都是参考类型。即使intobject,也不能说IFoo<int>IFoo<object>,因为它们不是两种引用类型。

要标记的接口协变,你把out你希望允许改变类型参数的声明之前:

interface IImplementation<out TModel> where TModel : IModel { } 

如果你这样做,你的程序将开始工作。

无论其out是提醒你,如果T在输出位置使用协是唯一安全的。这是合法的:

interface I<out T> { 
    T M(); 
} 

这不是:

interface I<out T> { 
    void M(T t); 
} 

在第一,T是唯一通过出来的东西。在第二个,它通过

在第一种情况下,我们不能使用协方差来引入类型空洞。我们有一个I<Cat>,我们将它转​​换为I<Animal>,现在M返回一个Animal,但没关系,因为我们已经知道它会返回Cat,并且CatAnimal

但在第二种情况下,我们有相反的情况。如果我们允许I<Cat>转换为I<Animal>,那么我们有一个M,它可以采取Turtle,但真正的实现只能处理Cat s。这就是为什么C#会使这非法。

因此,请继续使用协变,但请记住您必须向编译器证明您需要它,并且在任何情况下它都是安全的。如果你不想要它,或者它不安全,那么你就不会有协变,你必须为你的问题找到一个不同的解决方案。

+0

不错!在这个问题出现时,我将无法使用协变,但我一定会读到它,它很快就会派上用场。谢谢! –

+0

@FlorianMoser:不客气。如果这个主题引起您的兴趣,我已经写了大量关于该功能的设计和实现的文章,您可以在我的旧MSDN博客上找到这些文章。 –

+1

@FlorianMoser:另请参阅Eric的这个[answer](http://stackoverflow.com/a/4318510/1846281),它解释了为什么在C#数组中是不安全的co-variant。 –