我试图将其值添加到具有两个泛型的类的Dictionary中。这两个泛型必须来自抽象类BaseEntity

internal abstract class BaseEntity
{
    ...
}

internal class DataEtlModelRegistration<T, TResult> where T : BaseEntity where TResult : BaseEntity
{
    ...
}

internal class DataEtlContext : IDataEtlContext
{
    private readonly Dictionary<Type, DataEtlModelRegistration<BaseEntity, BaseEntity>> models = new Dictionary<Type, DataEtlModelRegistration<BaseEntity, BaseEntity>>();

    public void RegisterModelType<T, TResult>() where T : BaseEntity where TResult : BaseEntity
    {
        models.Add(typeof(T), new DataEtlModelRegistration<T, TResult>());
    }
}


我希望这是有效的,因为RegisterModelType方法可确保TTResult都根据类型约束的性质从BaseEntity类派生。

但是,我收到以下错误:


  参数2:无法从'... DataEtlModelRegistration '转换为'... DataEtlModelRegistration '。


错误棉绒在以下代码上:

new DataEtlModelRegistration<T, TResult>()


谁能解释这是为什么,并提出可能的解决方案?

最佳答案

您可能要检查co- and contravariance

您尚未向我们展示您的DataEtlModelRegistration<T, TResult>类的定义,但让我们想象一下它有一个带有签名的方法:

void Accept(T t);


(任何接受T作为参数的方法都可以)。

现在,我们还要想象DerivedEntity是从BaseEntity继承的。现在,让我们将Universe更改为其中models.Add(typeof(T), new DataEtlModelRegistration<T, TResult>());是有效代码并调用RegisterModelType<DerivedEntity, TAnything>的世界,其中TAnything可以是任何源自BaseEntity的东西。

因此,类型为DataEtlModelRegistration<DerivedEntity, TAnything>的对象现在在字典中,键为typeof(DerivedEntity)。让我们尝试提取它:

DataEtlModelRegistration<BaseEntity, BaseEntity> model = models[typeof(DerivedEntity)];


现在,由于entity的类型为DataEtlModelRegistration<BaseEntity, BaseEntity>,此代码应该可以工作(提供的BaseEntity具有可用的默认ctor):

model.Accept(new BaseEntity());


砰,类型系统坏了。您已将BaseEntity传递给接受DerivedEntity作为参数的方法。 BaseEntity不是DerivedEntity,您不能这样做。

因此,默认情况下泛型类型是不变的。基本上,这意味着List<DerivedEntity>不是List<BaseEntity>,因为您不能将任何BaseEntity添加到DerivedEntity的列表中。因此,如果您的类包含一个接受T(或TResult,相同逻辑的方法)作为参数的方法,则您将无法做您想做的事情。

但是,如果没有这样的方法,则可以使用接口使类型协变:

interface IModelRegistration<out T, out TResult> where T : BaseEntity where TResult : BaseEntity
{
    ...
}

internal class DataEtlModelRegistration<T, TResult> : IModelRegistration<T, TResult> where T : BaseEntity where TResult : BaseEntity
{
    ...
}


基本上,您是在告诉编译器“嘿,此接口将永远不会接受任何泛型类型的对象,只会返回它们”。如果接口IModelRegistration包含以TTResult作为参数的方法,则该代码将无法编译。现在可以说:

private readonly Dictionary<Type, IModelRegistration<BaseEntity, BaseEntity>> models = new Dictionary<Type, IModelRegistration<BaseEntity, BaseEntity>>();

models.Add(typeof(DerivedEntity), new DataEtlModelRegistration<DerivedEntity, DerivedEntity>());


您将能够从字典中提取对象作为IModelRegistration接口的实例。

IModelRegistration<BaseEntity, BaseEntity> model = models[typeof(DerivedEntity)];


现在没有办法破坏类型系统,因为我们知道IModelRegistration接口没有可以接受任何类型参数对象的方法。

您还可以查看this question,我在其中解释了逆差的工作原理。

08-05 19:24