使用通用方法将继承的通用类添加到字典中

时间:2019-07-12 10:27:53

标签: c# .net generics collections

我试图添加到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>()

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

3 个答案:

答案 0 :(得分:2)

您可能要检查co- and contravariance

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

void Accept(T t);

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

现在,让我们还假设DerivedEntityBaseEntity继承。现在,让我们将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());

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

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

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

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,在那里我对逆差的工作原理进行了解释。

答案 1 :(得分:-1)

您不能继承类型定义。即使TTResult从BaseEntity继承,也并不意味着List<BaseEntity> = new List<T>有效。否则,您可以List<object> list = new List<String>()执行此操作。不过,您可以创建List<BaseEntity>并用TTResult对象填充它。

您可以

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

并将TTResult对象插入DataEtlModelRegistration或

private readonly Dictionary<Type, DataEtlModelRegistration<T, TResult>> models = new Dictionary<Type, DataEtlModelRegistration<T, TResult>>();

如果对象的类型很重要,则很重要。

答案 2 :(得分:-1)

即使通用参数具有某种关系,

DataEtlModelRegistration也不是从DataEtlModelRegistration派生的。您不能在课堂上避免使用它。可以解决接口问题,因为它们支持协方差,但是我不确定接口是否适合您,因为您可能需要这些类中的数据和方法实现。

一个可能的选择是为DataEtlModelRegistration提供非通用基类,并带有所有必要的虚拟方法(甚至接口)。上面的字典将其作为值类型参数。