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

时间:2015-10-19 07:50:36

标签: c# generics delegates type-inference

我有以下设置:

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. 为什么不首先推断出正确的类型?
  3. 编辑:
    问题归结为事实,这是可能的:

    Action<sub> subAction = superAction;
    

    但是编译器并没有将这个事实用于它的推理逻辑 所以问题1的答案是:

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

    问题2,为什么编译器本身不这样做,但仍然没有解决。

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

2 个答案:

答案 0 :(得分:3)

  

为什么不首先推断出正确的类型?

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

让我们看看它。

给出以下方法签名:

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

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

  
      
  • 否则,如果Ei的类型为U(我们示例中的Ei为T element),则xi(xi为方法参数Foo<T>为值参数,则为   下限推断是从U到Ti。
  •   

所以我们现在看下界推论:

  
      
  • 如果V是未固定的Xi之一,则将U添加到精确的集合中   习近平。
  •   

对于第一个类型参数T会发生这种情况,对于Action<T>,会出现以下情况:

  
      
  • 否则,如果V是C,那么推断取决于第i种类型   参数C:
  •   
  • 如果它是协变的,则下限推断是   制成。
  •   
  • 如果是逆变型,则会进行上限推断。
  •   
  • 如果它是不变的,则进行精确推断。
  •   

现在,第二阶段开始了:

  
      
  • 所有不固定的类型变量Xi,它们不依赖于(§7.5.2.5)任何Xj   是固定的(§7.5.2.10)。
  •   

现在让我们看看修复意味着什么:

  

具有一组边界的未固定类型变量Xi固定如下:

     
      
  • 候选类型集合Uj以全部集合开始   Xi的边界集中的类型。

  •   
  • 然后我们依次检查Xi的每个边界:对于每个确切的   结合U的Xi所有类型Uj都与U不相同   来自候选人集。对于Xi的每个下界U,所有类型Uj为   从U中删除没有隐式转换   候选人集。对于Xi的每个上界U,所有类型都是Uj   没有从U中删除的隐式转换   候选人集。

  •   
  • 如果剩下的候选类型Uj中有唯一   类型V,其中隐含转换为所有其他   候选类型,则Xi固定为V。

  •   
  • 否则,类型推断失败。

  •   

这里发生的事情是我们在类型super的有效候选集中基本上同时包含subT。现在,当边界集都产生“最佳匹配”时,类型推断算法选择“更大”类型。 Eric Lippert talks about this in a blog post

  

“绑定”只不过是一种类型,绑定可以是“上限”,   “更低”或“确切”。例如,假设我们有一个类型参数T.   有三个界限:长颈鹿的下界,是哺乳动物的精确界限,   和动物的上限。让我们说动物是一种“更大”的类型   比哺乳动物(因为所有哺乳动物都是动物,但不是所有的动物都是   哺乳动物,因此动物必须是较大的类型),长颈鹿是一个   比哺乳动物“更小”的类型。鉴于这一组界限,我们知道T   必须推断是第一个,长颈鹿或大于   长颈鹿,因为长颈鹿是下限;你无法推断出一种类型   小于长颈鹿。其次,我们知道T必须是哺乳动物。   第三,我们知道T必须是Animal或小于的类型   动物,因为动物是一个上限。我们无法推断出一种类型   大于动物。 C#编译器推断出Mammal是唯一的   键入满足所有三个要求的集合,因此T将是   固定在哺乳动物身上。 如果集合中有多种类型符合所有类型   要求(如果有任何要求,当然不会发生   边界!)然后我们选择最大的这种类型。(*)

Eric还解释了选择“更大”类型的原因:

  

有一个论据要求选择最小的,但挑选   最大似乎匹配更多人的权利直觉   选择是。

答案 1 :(得分:1)

归结为编译器是否可以(或不能)推断出T的实际类型。

当你通过时:

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

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

但是,当你只是传递时:

Foo(new sub(), superAction);

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

现在,当你在做的时候:

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

当方差发挥作用时,它编译精细和 。由于Action<T>是逆变的,编译器可以T结算为sub,因为Action<super>可以视为Action<sub>。< / p>