我有以下设置:
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);
编辑:
问题归结为事实,这是可能的:
Action<sub> subAction = superAction;
但是编译器并没有将这个事实用于它的推理逻辑 所以问题1的答案是:
Foo(new sub(), superAction as Action<sub>);
问题2,为什么编译器本身不这样做,但仍然没有解决。
EDIT2:
问题2的简短回答是:
根据给定的参数,编译器确定此方法调用的可能泛型类型。如果有多个选项,则选择“最高”(或最少派生)。出于或多或少的任意原因,它会这样做
这里不考虑通用类型约束(where
之后的那些约束{/ 1}}。
答案 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
的有效候选集中基本上同时包含sub
和T
。现在,当边界集都产生“最佳匹配”时,类型推断算法选择“更大”类型。 Eric Lippert talks about this in a blog post:
Eric还解释了选择“更大”类型的原因:“绑定”只不过是一种类型,绑定可以是“上限”, “更低”或“确切”。例如,假设我们有一个类型参数T. 有三个界限:长颈鹿的下界,是哺乳动物的精确界限, 和动物的上限。让我们说动物是一种“更大”的类型 比哺乳动物(因为所有哺乳动物都是动物,但不是所有的动物都是 哺乳动物,因此动物必须是较大的类型),长颈鹿是一个 比哺乳动物“更小”的类型。鉴于这一组界限,我们知道T 必须推断是第一个,长颈鹿或大于 长颈鹿,因为长颈鹿是下限;你无法推断出一种类型 小于长颈鹿。其次,我们知道T必须是哺乳动物。 第三,我们知道T必须是Animal或小于的类型 动物,因为动物是一个上限。我们无法推断出一种类型 大于动物。 C#编译器推断出Mammal是唯一的 键入满足所有三个要求的集合,因此T将是 固定在哺乳动物身上。 如果集合中有多种类型符合所有类型 要求(如果有任何要求,当然不会发生 边界!)然后我们选择最大的这种类型。(*)
有一个论据要求选择最小的,但挑选 最大似乎匹配更多人的权利直觉 选择是。
答案 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>