如何用继承类型推断类型参数

本文关键字:类型参数 类型 何用 继承 | 更新日期: 2023-09-27 18:18:21

我有以下设置:

public abstract class super { }
public class sub : super { }
public static void Foo<T>(T element, Action<T> action)
    where T : new()
{ }

我想这样做:

Action<super> superAction = (s) => { };
Foo(new sub(), superAction);

然而,这失败了,因为第二行试图调用Foo<super>而不是Foo<sub>

Foo<sub>(new sub(), superAction);
  1. 在这样的情况下是否有方法推断类型参数?
  2. 为什么它一开始就不能推断出正确的类型?

编辑:
问题归结为这样一个事实:这是可能的:

Action<sub> subAction = superAction;

但是编译器不使用这个事实作为它的推理逻辑。
所以问题1的答案是:

Foo(new sub(), superAction as Action<sub>);

问题2,为什么编译器不自己做这件事,仍然没有解决。

EDIT2:
问题2的简短答案是:
编译器根据给定的参数确定此方法调用的可能泛型类型。如果有多个选项,它会选择"最高"(或派生最少)的一个。它这样做或多或少是出于任意的原因。
这里不考虑泛型约束(where之后的约束,如T : new())。

如何用继承类型推断类型参数

为什么它一开始就不能推断出正确的类型?

编译器会推断出正确的类型。给定方法的正确类型是super。为什么?因为这就是类型推断算法的工作方式。

让我们看看。

给定以下方法签名:

public static void Foo<T>(T element, Action<T> action) where T : new()

算法从第一阶段开始,为我们总结为:

  • 否则,如果Ei具有类型U(在我们的示例中Ei是T element)和xi (xi是方法参数),那么Foo<T>是一个值形参,则a下界推理从U到Ti

现在我们来看下界推理:

    对于第一个类型参数T发生这种情况,对于Action<T>,适用如下:

    • 否则,如果V为C,则推论依赖于第i种类型C参数:
    • 如果它是协变的,则下界推断是。
    • 如果它是逆变的,则进行上界推理。
    • 如果它是不变的,则做出精确的推断。

    现在,第二阶段开始:

      现在让我们看看什么是fixing:

      • 候选类型集合Uj开始时是所有类型的集合

      • 所有与U不相同的类型Uj都被移除从候选集合中。其中没有一个隐式转换从U中移除候选集。并没有隐式地转换为U候选集合。
      • 如果在剩余的候选类型Uj中有一个唯一类型V,从它到所有其他类型的隐式转换

      • 否则,类型推断失败

      这里发生的事情是,我们基本上在T类型的有效候选集中同时拥有supersub。现在,当边界集合都产生"最佳匹配"时,类型推断算法选择"较大"类型。 Eric Lippert在一篇博客文章中谈到了这一点:

      一个"界"只不过是一种类型,一个界可以是"上","lower"或"exact"。例如,假设我们有一个类型参数T有三个界限:长颈鹿的下限,哺乳动物的精确界限,和动物的上界。假设Animal是一个"较大"的类型因为所有的哺乳动物都是动物,但不是所有的动物都是哺乳动物,因此动物必须是较大的类型),长颈鹿是一种比哺乳动物"小"的类型。已知这组积分限,我们知道T必须被推断为第一类型,长颈鹿或大于长颈鹿,因为长颈鹿是下界;你不能推断类型比长颈鹿小。第二,我们知道T一定是哺乳动物。第三,我们知道T一定是Animal或者小于动物,因为动物是上界。我们不能推断类型比动物大。c#编译器推断出哺乳动物是唯一的输入满足这三个条件的集合,所以T是固定到哺乳动物。如果集合中有多个类型满足所有要求(当然,如果有任何精确的边界!),然后选择最大的类型。 (*)

      Eric还解释了为什么选择" bigger "类型:

      选择最小值是有理由的,但是选择最大的似乎更符合人们对正确事物的直觉选择.

      归根结底是编译器能否推断出T的实际类型。

      当你经过:

      Foo(new sub(), superAction as Action<sub>);
      

      编译器可以清楚地判断T可以解析为sub,因为它可以从两个参数中推断出来。

      但是,当你只是通过:

      Foo(new sub(), superAction);
      

      编译器被迫确定T必须是super(两者的共同类型,否则Action<super>不能被接受作为参数),然后你回到最初的问题,即new约束。因为一旦T被解析为super,它就不符合new约束(因为它是abstract,编译器不能保证它可以是new)。

      现在,当你做:

      Foo<sub>(new sub(), superAction);
      

      它编译得很好这就是当方差开始起作用的时候。由于Action<T>是逆变的,编译器可以解决T解析为sub,因为Action<super>可以被视为Action<sub>