简体   繁体   English

泛型类型转换失败

[英]cast of generic type fails

I am unable to cast a generic type to another generic type, besides the cast should be valid我无法将泛型类型转换为另一种泛型类型,除了转换应该是有效的

What I want to archive is in short (for MyModel implementing IModel , and MyImplementation implementing IImplementation ):我想存档是在短期(用于MyModel实施IModelMyImplementation实施IImplementation ):

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

This is a bit confusing, as the type should be valid.这有点令人困惑,因为类型应该是有效的。

Complete conceptual model:完整的概念模型:

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>;
}

I need this cast to enable me to save multiple IImplementation s to the same Dictionary<Type, IImplementation<IModel>> , where the key is obtained by doing typeof(TModel) .我需要这个转换来让我能够将多个IImplementation保存到同一个Dictionary<Type, IImplementation<IModel>> ,其中的键是通过typeof(TModel) To do this type safe I don't want to use a Dictionary<Type, object> .为了做到这种类型安全,我不想使用Dictionary<Type, object>

  • Why does the cast fail?为什么演员表失败? Are there additional resources to this?是否有额外的资源? Its a similar question to Invalid Cast of Type Constrained C# Generic , but it is not explained why there just that it does not work.它与Invalid Cast of Type Constrained C# Generic类似,但没有解释为什么它不起作用。
  • What is the best way to archive a functionality similar to the dictionary as explained above if this kind of cast is not possible?如果这种类型的转换是不可能的,那么存档类似于上述字典的功能的最佳方法是什么?

Though Olivier's answer gets the idea across about why this usually goes wrong, there is a way to make this work in your program.尽管 Olivier 的回答说明了为什么这通常会出错,但有一种方法可以在您的程序中实现这一点。

The feature you want is called generic interface covariance .您想要的功能称为通用接口协方差 Covariance is the property that if a Cat is an Animal , then an IFoo<Cat> is an IFoo<Animal> .协方差是一个属性,如果CatAnimal ,则IFoo<Cat>IFoo<Animal>

Covariance in C# only works in the following situations: C# 中的协方差仅适用于以下情况:

  • The "outer" type is an interface, delegate or array. “外部”类型是接口、委托或数组。 No classes or structs.没有类或结构。
  • If an interface or delegate, the type must be marked at compile time as supporting covariance.如果是接口或委托,则必须在编译时将该类型标记为支持协变。 Arrays get (unsafe!) covariance for free.数组免费获得(不安全!)协方差。
  • The "inner" types -- the types that are varying -- are both reference types. “内部”类型——变化的类型——都是引用类型。 You can't say that an IFoo<int> is an IFoo<object> even though an int is an object , because they are not both reference types.即使intobject ,也不能说IFoo<int>IFoo<object> ,因为它们不是两种引用类型。

To mark an interface as covariant, you put out before the declaration of the type parameter which you wish to allow to vary:要标记的接口协变,你把out你希望允许改变类型参数的声明之前:

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

If you do that, your program will start to work.如果你这样做,你的程序就会开始工作。

HOWEVER , out is a reminder to you that covariance is only safe if T is used in output positions.但是out提醒您,只有在输出位置使用T协方差才是安全的。 This is legal:这是合法的:

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

This is not:这不是:

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

In the first, T is only passed out of things.在第一种情况下,T 只是事物中传递出去 In the second, it is passed in .在第二个,被传递。

In the first scenario, we cannot use covariance to introduce a type hole.在第一种情况下,我们不能使用协方差来引入类型漏洞。 We have an I<Cat> and we cast it to I<Animal> , and now M returns an Animal , but that's OK, because we already know that it will return a Cat , and a Cat is an Animal .我们有一个I<Cat>并将它转换为I<Animal> ,现在M返回一个Animal ,但没关系,因为我们已经知道它会返回一个Cat ,而Cat是一个Animal

But in the second scenario, we have the opposite situation.但在第二种情况下,我们有相反的情况。 If we allowed an I<Cat> to be converted to I<Animal> then we have an M that can take a Turtle , but the real implementation can only handle Cat s.如果我们允许将I<Cat>转换为I<Animal>那么我们就有一个可以接受TurtleM ,但真正的实现只能处理Cat That's why C# will make this illegal.这就是为什么 C# 将使其成为非法的原因。

So go forth and use covariance, but remember that you have to certify to the compiler that you want it, and that it is safe under all circumstances .所以继续使用协方差,但请记住,您必须向编译器证明您想要它,并且它在任何情况下都是安全的 If you don't want it, or it is not safe, then you don't get to have covariance, and you'll have to find a different solution to your problem.如果你不想要它,或者它不安全,那么你就没有协方差,你必须为你的问题找到不同的解决方案。

This kind of conversion is not allowed for good reasons.出于充分的理由,不允许进行这种转换。 Let's take an example where the problem is more obvious.让我们举一个问题更明显的例子。 We have the classes Animal , Cat : Animal and Dog : Animal .我们有类AnimalCat : AnimalDog : Animal Now let's do this:现在让我们这样做:

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.

But wait!可是等等! The list is in reality a list of cats!该列表实际上是一个猫列表! And we are trying to add a dog.我们正在尝试添加一只狗。 Even adding new Animal() to list , which is statically typed as List<Animal> , would not work.即使添加new Animal()list ,这是静态类型的List<Animal> ,是行不通的。

Therefore two types T<A> and T<B> are not assignment compatible in C#, even if A and B are!因此,两种类型T<A>T<B>在 C# 中不是赋值兼容的,即使AB是!

You need another approach.你需要另一种方法。


What you can do is to wrap your dictionary in a class with a generic method having a generic type constraint.您可以做的是使用具有泛型类型约束的泛型方法将字典包装在一个类中。

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);
    }

    ...
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM