.NET通用方法问题

时间:2020-03-06 14:37:57  来源:igfitidea点击:

我试图掌握.NET泛型的概念,并在自己的代码中实际使用它们,但我一直遇到问题。

有人可以尝试向我解释为什么以下设置无法编译吗?

public class ClassA
{
    ClassB b = new ClassB();

    public void MethodA<T>(IRepo<T> repo) where T : ITypeEntity
    {
        b.MethodB(repo);
    }
}

public class ClassB
{
    IRepo<ITypeEntity> repo;

    public void MethodB(IRepo<ITypeEntity> repo)
    {
        this.repo = repo;
    }
}

我收到以下错误:
无法从IRepo <'T>转换为IRepo <'ITypeEntity>

使用IRepo <'DetailType>对象参数调用MethodA,其中DetailType继承自ITypeEntity。

我一直认为这应该编译,因为我将MethodA中的T约束为ITypeEntity类型。

任何想法或者反馈将非常有帮助。

谢谢。

编辑:尼克R有一个很好的建议,但不幸的是,在我的背景下,我没有选择使ClassA泛型。 ClassB可能是。

解决方案

问题是一个棘手的问题。 DetailType可以继承自ITypeEntity,但实际上不是ITypeEntity。DetailType实现可能会引入不同的功能,因此DetailType实现ITypeEntity,但不等于ITypeEntity。我希望这是有道理的...

如果B是A的子类,那并不意味着Class &lt;B>Class &lt;A>的子类。因此,出于同样的原因,如果我们说" T是ITypeEntity",这并不意味着" IRepo <T>是IRepo <ITypeEntity>"。如果要使此工作正常,我们可能必须编写自己的转换方法。

T是类型变量,在使用中将绑定到特定类型。限制确保该类型将表示实现ITypeEntity的类型的子集,但不包括实现该接口的其他类型。

在编译时,即使我们对其进行了约束,编译器也只知道MethodA中的T是引用类型。它不知道它被限制为哪种类型。

好吧,编译就可以了。我基本上重新定义了类以采用通用参数。在情况下,这可能没问题。

public interface IRepo<TRepo>
{
}

public interface ITypeEntity
{
}

public class ClassA<T> where T : ITypeEntity
{
    ClassB<T> b = new ClassB<T>();
    public void MethodA(IRepo<T> repo)
    {
        b.MethodB(repo);
    }
}
public class ClassB<T> where T : ITypeEntity
{
    IRepo<T> repo;
    public void MethodB(IRepo<T> repo)
    {
        this.repo = repo;
    }
}

这是泛型的多余用法,如果T只能是ITypeEntity的实例,则不应使用泛型。

泛型适用于我们可以将多种类型包含在某种东西中的情况。

I get the following error: cannot
  convert from IRepo<'T> to
  IRepo<'ITypeEntity>

我们收到此编译错误是因为IRepo &lt;T>IRepo &lt;ITypeEntity>不是同一件事。后者是前者的专业化。 " IRepo <T>"是泛型类型定义,其中类型参数T是占位符,而" IRepo <ITypeEntity>"是泛型类型定义的结构化泛型类型,其中类型参数T from被指定为。 ITypeEntity

I keep thinking that this should
  compile as I'm constraining T within
  MethodA to be of type ITypeEntity.

" where"约束在这里无济于事,因为它约束了我们可以在" MethodA"的调用站点上为T提供的类型。

这是MSDN文档中的术语(请参阅.NET Framework中的泛型),可能会有所帮助:

  • 通用类型定义是用作模板的类,结构或者接口声明,并带有可包含或者使用的类型的占位符。例如,Dictionary &lt;&lt; K,V>类可以包含两种类型:键和值。因为它只是模板,所以不能创建作为通用类型定义的类,结构或者接口的实例。
  • 泛型类型参数或者类型参数是泛型类型或者方法定义中的占位符。 " Dictionary <K,V>"泛型类型有两个类型参数,即K和V,代表其键和值的类型。
  • 构造的泛型类型或者构造的类型是为泛型类型定义的泛型类型参数指定类型的结果。
  • 泛型类型参数是可以替代泛型类型参数的任何类型。
  • 通用术语"通用类型"包括构造类型和通用类型定义。
  • 约束是对通用类型参数的限制。例如,我们可以将类型参数限制为实现" IComparer <T>"通用接口的类型,以确保可以对类型的实例进行排序。我们还可以将类型参数约束为具有特定基类,具有默认构造函数或者引用类型或者值类型的类型。泛型类型的用户不能替换不满足约束的类型参数。

请参阅@monoxide的问题

正如我在那儿所说的那样,查看Eric Lippert关于泛型的协方差和协方差的系列文章将使这一点变得更加清晰。

使用泛型时,继承的工作原理不同。正如Smashery指出的那样,即使TypeA继承自TypeB,myType <TypeA>也不继承自myType <TypeB>。

这样,我们就无法调用定义为MethodA(myType <TypeB> b)的方法,而该方法期望使用myType <TypeB>并给其提供myType <TypeA>。有问题的类型必须完全匹配。因此,以下内容将无法编译:

myType<TypeA> a; // This should be a myType<TypeB>, even if it contains only TypeA's

public void MethodB(myType<TypeB> b){ /* do stuff */ }

public void Main()
{
  MethodB(a);
}

因此,在情况下,即使IRepo <ITypeEntity>仅包含DetailType,也需要将其传递给MethodB。我们需要在两者之间进行一些转换。如果使用的是通用IList,则可以执行以下操作:

public void MethodA<T>(IList<T> list) where T : ITypeEntity
{
  IList<T> myIList = new List<T>();

  foreach(T item in list)
  {
    myIList.Add(item);
  }

  b.MethodB(myIList);
}

我希望这是有帮助的。

在笼罩通用方法的上下文中,让我给我们一个简单的通用函数。它与VB的IIf()(如果是,则立即)等效,这本身就是对C风格的三元运算符(?)的不良模仿。它对任何东西都没有用,因为真正的三元运算符更好,但是也许可以了解泛型函数的构建方式以及应在什么情况下应用它们。

T IIF<T>(bool Expression, T TruePart, T FalsePart)
{
    return Expression ? TruePart : FalsePart;
}