究竟谁最后决定什么是通用类型?
我有这个function
public static T2 MyFunc( T1 a, T1 b, T2 c) { return c; }
我正在创建2个Persons类实例:
class Person { } Person p = new Person(); Person p2 = new Person();
我正在调用函数:
MyClass.MyFunc(p, p2, 5);
我的问题是:
谁真正决定T1类型? (p?p2?)
因为如果左边是Apple,那么他会检查第二个是Apple也是苹果
如果第二个是橙色 – 他应该检查第一个是橙色。
在编译时问它是否会失败,如果不一样的话,这似乎很奇怪。
仍然 – 谁决定类型?
第二 – 如果我将其更改为动态 – 在运行时 – 谁将决定T1类型应该是什么?
在高级别,方法类型推断就像这样工作。
首先,我们列出所有参数 – 您提供的表达式 – 以及它们对应的forms参数类型 。
让我们看一个比你给出的更有趣的例子。 假设我们有
class Person {} class Employee : Person {} ... Person p = whatever; Employee p2 = whatever;
和同一个电话。 所以我们做了这些通信:
p --> T1 p2 --> T1 5 --> T2
然后我们列出每个类型参数的“边界”以及它们是否“固定”。 我们有两个类型参数,我们从没有上限,下限或精确边界开始。
T1: (unfixed) upper { } lower { } exact { } T2: (unfixed) upper { } lower { } exact { }
(回想一下我们最近在关于类型的相对大小的另一个问题中的讨论是基于类型是否具有更多或更少的限制;更严格的类型小于限制较少的类型。长颈鹿比动物小,因为更多事物是动物,而不是长颈鹿。“上”和“下”约束集正是这样:给定类型参数的类型推断问题的解决方案必须大于或等于每个下限, 小于或等于每个上限, 与每个精确界限相同 。)
然后我们看看每个参数及其相应的类型。 (如果参数是lambdas,那么我们可能必须弄清楚我们查看参数的顺序 ,但是你没有任何lambdas,所以让我们忽略那个细节。)对于每个参数,我们对forms参数类型进行推断 ,并将我们推断出的有关推理的事实添加到绑定集。 因此,在查看第一个参数后,我们推导出界限:
T1: (unfixed) upper { } lower { Person } exact { } T2: (unfixed) upper { } lower { } exact { }
在第二个参数之后我们推导出界限
T1: (unfixed) upper { } lower { Person, Employee } exact { } T2: (unfixed) upper { } lower { } exact { }
在第三个参数之后我们推导出界限:
T1: (unfixed) upper { } lower { Person, Employee } exact { } T2: (unfixed) upper { } lower { int } exact { }
在我们尽可能多地取得进展之后,我们通过在满足每个边界的边界集中找到最佳类型来 “修复”边界。
对于T1,边界集中有两种类型, Person
和Employee
。 是否有其中一个满足边界集中的每个边界? 是。 Employee
不满足Person
约束,因为Employee
比Person
更小; Person
是一个下限 – 这意味着没有比Person
更小的类型是合法的 。 Person
确实满足所有界限: Person
相同并且比Employee
,因此它满足两个界限。 满足每个边界的边界集中的最佳类型是T1是Person
,对于T2显然它是int
因为在T2的边界集中只有一种类型。 然后我们修复类型参数:
T1: (fixed) Person T2: (fixed) int
然后我们问“我们对每个类型参数都有一个固定的界限吗?” 答案是“是”,因此类型推断成功。
如果我将第一个参数的类型更改为
dynamic
那么T1是如何推断的?
如果任何参数是动态的,那么T1和T2的推断将延迟到运行时,此时语义分析器将值的最多可访问运行时类型视为动态参数提供的下限的类型。
如果您对这个主题感兴趣并希望了解更多内容,我可以在此video中解释C#3版本的算法:
http://blogs.msdn.com/b/ericlippert/archive/2006/11/17/a-face-made-for-email-part-three.aspx
(C#3没有上限,只有较低和精确的界限;除此之外,算法几乎相同。)
我在这里写了一些关于类型推断问题的文章:
http://blogs.msdn.com/b/ericlippert/archive/tags/type+inference/
可以省略呼叫中的类型
MyClass.MyFunc(p1, p2, 5);
是一个语法糖果(除非你使用匿名类型),它编译完全相同
MyClass.MyFunc(p1, p2, 5);
编译器根据参数a
, b
和c
的类型推导出T1
和T2
的值。 如果p1
和p2
是不兼容的类型(请参阅svick的答案 ,编译器将无法推断T1
,这将导致编译错误。
没有优先级,(a和b)都应该是相同的,即按设计,T1在编译时被解析。 如果更改为dynamic
,则只需将类型解析推迟到运行时,如果类型不同,它将在编译时失败。 如果您希望它们不同,则需要引入T3。
编辑:
有趣的部分:
Orange a = new Orange(); Apple b = new Apple(); string c = "Doh."; MyFunc(a,b,c); public static T2 MyFunc( T1 a, T1 b, T2 c) where T2 : class { return (a.ToString() + b.ToString() + c.ToString()) as T2; }
输出:
I am an orange. I am an apple. Doh.
但是这个:
dynamic a = new Orange(); dynamic b = new Apple(); string c = "Doh."; MyFunc(a,b,c);
将抛出:
RuntimeBinderException: The best overloaded method match for 'UserQuery.MyFunc(UserQuery.Apple, UserQuery.Apple, string)' has some invalid arguments
然而,似乎我真的需要在C#4.0中找到关于动态类型的好书或资源来理解这里发生的魔术。
谁真正决定T1类型? (p?p2?)
不是很明显吗? 他们都。 p
和p2
的类型必须兼容。 与其他答案所说的相反,它们不必相同。 实际的规则是必须从类型到另一个的隐式转换。
因此,例如MyFunc("a", new object(), 5)
与MyFunc
,因为string
可以隐式转换为object
。 另一个例子, MyFunc(42L, 42, 4)
, MyFunc(42L, 42, 4)
与MyFunc
,因为int
可以隐式转换为long
。
此外,有些情况下,编译器推断类型的能力不仅仅是好的,这是必要的。 具体而言,使用匿名类型时会发生这种情况。 例如MyFunc(new { p = "p" }, new { p = "p2" }, 5)
无法重写MyFunc(new { p = "p" }, new { p = "p2" }, 5)
以明确指定类型。
“谁真正决定T1类型?(p?p2?)”
通常,C#编译器决定这一点。 如果其中一个方法参数是动态的,那么决定是在运行时完成的(由Microsoft.CSharp库完成)。 在这两种情况下,都应用了C#规范中描述的类型推断算法: p
和p2
的类型都被添加到T1
的下界集中 ( 上限也是可能的,但仅在涉及逆变generics时)。
然后,编译器选择一组边界中的一种类型,这些类型也满足所有其他边界。 当只有一个绑定因为p
和p2
具有相同的类型时,这个选择是微不足道的。 否则(假设只涉及下限),这意味着编译器选择一个类型,以便所有其他候选类型可以隐式转换为该类型(svick的答案描述)。
如果没有唯一的此类选择,则类型推断失败 – 如果可能,将选择另一个重载,否则会发生编译器错误(当在运行时完成决策(动态)时,会抛出exception)。
在编译时,如果类型是显式的,那么编译器将检查传递的参数类型,并查看它们是否对应并且可以与generics中的类型匹配(无冲突)。
无论如何,真正的检查是在“运行时”完成的,通用代码无论如何都会编译为通用(与c ++模板不同)。 然后当JIT编译器编译该行时,它将检查并查看它是否可以根据您给出的模板和发送的参数创建方法。