通用类型的强制转换失败

Flo*_*ser 3 c# generics casting

我无法将泛型类型转换为另一种泛型类型,除了转换应该是有效的

我想要归档的是简短的(用于MyModel实现IModelMyImplementation实现IImplementation):

IImplementation<IModel> implementation = new MyImplementation<MyModel>();
Assert.IsNull(implementation as IImplementation<IModel>);
Run Code Online (Sandbox Code Playgroud)

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

完整的概念模型:

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>;
}
Run Code Online (Sandbox Code Playgroud)

我需要这个强制转换才能让我将多个IImplementations 保存到相同的位置Dictionary<Type, IImplementation<IModel>>,其中密钥是通过执行来获得的typeof(TModel).要做到这种类型安全,我不想使用Dictionary<Type, object>.

  • 为什么演员会失败?这有额外的资源吗?它与类型约束C#Generic的无效转换类似的问题,但它没有解释为什么它只是它不起作用.
  • 如果无法进行此类演员,那么归档类似于字典的功能的最佳方法是什么?

Eri*_*ert 7

虽然Olivier的回答可以解释为什么这通常会出错,但是有一种方法可以在你的程序中完成这项工作.

您想要的功能称为通用接口协方差.协方差是属性,如果a Cat是a Animal,则a IFoo<Cat>是a IFoo<Animal>.

C#中的协方差仅适用于以下情况:

  • "外部"类型是接口,委托或数组.没有类或结构.
  • 如果是接口或委托,则必须在编译时将类型标记为支持协方差.数组免费获得(不安全!)协方差.
  • "内部"类型 - 变化的类型 - 都是引用类型.你不能说an IFoo<int>是一个IFoo<object>即使a int是a object,因为它们不是两种引用类型.

要将接口标记为协变,请out在声明允许变化的type参数之前放置:

interface IImplementation<out TModel> where TModel : IModel { }
Run Code Online (Sandbox Code Playgroud)

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

但是,out提醒您,如果T在输出位置使用协方差,则协方差才是安全的. 这是合法的:

interface I<out T> {
  T M();
}
Run Code Online (Sandbox Code Playgroud)

这不是:

interface I<out T> {
  void M(T t);
}
Run Code Online (Sandbox Code Playgroud)

在第一个,T只是传递出来的东西.在第二,它是通过.

在第一种情况下,我们不能使用协方差来引入类型洞.我们有一个I<Cat>并且我们将它投射到I<Animal>,现在M返回一个Animal,但是没关系,因为我们已经知道它将返回a Cat,而a Cat是一个Animal.

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

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


Oli*_*bes 5

出于充分原因,不允许进行这种转换。让我们举一个问题更明显的例子。我们有课程AnimalCat : 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.
Run Code Online (Sandbox Code Playgroud)

可是等等!该列表实际上是猫的列表!我们正在尝试加一条狗。即使添加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);
    }

    ...
}
Run Code Online (Sandbox Code Playgroud)