究竟谁最后决定什么是通用类型?

时间:2012-02-05 12:54:10

标签: c# generics .net-4.0

我有这个功能

 public static T2 MyFunc<T1, T2>( 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还是苹果

如果第二个是橙色 - 他应该检查第一个是橙色。

enter image description here

在编译时问这个问题似乎很奇怪,如果不一样,它们会失败。

仍然 - 谁决定类型?

第二 - 如果我将其更改为动态 - 在运行时 - 谁将决定T1类型应该是什么?

6 个答案:

答案 0 :(得分:13)

在高级别,方法类型推断就像这样。

首先,我们列出所有参数 - 您提供的表达式 - 及其相应的形式参数类型

让我们看一个比你给出的更有趣的例子。假设我们有

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,那么我们可能必须弄清楚 order ,我们在其中查看参数,但是你没有任何lambdas,所以让我们忽略那个细节。)对于我们制作的每个参数对于形式参数类型的推理,并将我们推断出的关于该推断的事实添加到绑定集。因此,在查看第一个参数后,我们推导出界限:

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,边界集中有两种类型PersonEmployee。是否有其中一个满足边界集中的每个边界?是。 Employee不符合Person限制因为Employee类型比Person小; Person下限 - 这意味着没有小于Person的类型是合法的Person确实满足所有范围:PersonPerson相同且大于Employee,因此它满足两个边界。满足每个边界的边界集中的最佳类型是T1 Person,而T2显然是int,因为在T2的边界集中只有一种类型。然后我们修复类型参数:

T1: (fixed) Person
T2: (fixed) int

然后我们问“我们对每个类型参数都有一个固定的界限吗?”答案是“是”,因此类型推断成功。

  

如果我将第一个参数的类型更改为dynamic,那么如何推断T1?

如果任何参数是动态的,那么T1和T2的推断将推迟到运行时,此时语义分析器将值的最多派生的可访问运行时类型视为提供的下限的类型通过动态论证。


如果您想要了解更多这个主题,那么有一个视频,我在这里解释了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/

答案 1 :(得分:7)

可以省略通话中的类型

MyClass.MyFunc(p1, p2, 5);

是一种语法糖果(除非你使用的是匿名类型),它的编译完全相同

MyClass.MyFunc<Person, int>(p1, p2, 5);

编译器根据参数T1T2a的类型推导出bc的值。如果p1p2具有不兼容的类型(请参阅svick's answer,则编译器将无法推断T1,这将导致编译错误。

答案 2 :(得分:2)

没有优先级,(a和b)都应该相同,即按设计,T1在编译时解析。如果更改为dynamic,则只需将类型解析推迟到运行时,如果类型不同,则会在编译时失败。如果您希望它们不同,则需要引入T3。

编辑:

有趣的部分:

Orange a = new Orange();
Apple b = new Apple();
string c = "Doh.";

MyFunc<dynamic, string>(a,b,c);

public static T2 MyFunc<T1, T2>( 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<Apple, string>(a,b,c);

将抛出:

RuntimeBinderException: The best overloaded method match for 'UserQuery.MyFunc<UserQuery.Apple,string>(UserQuery.Apple, UserQuery.Apple, string)' has some invalid arguments

然而,似乎我真的需要在C#4.0中找到关于动态类型的好书或资源来理解这里发生的魔术。

答案 3 :(得分:1)

  

谁真正决定T1类型? (p?p2?)

不是很明显吗?他们都。 pp2的类型必须兼容。与其他答案所说的相反,它们不必相同。实际的规则是必须从类型到另一个的隐式转换。

因此,例如MyFunc("a", new object(), 5)MyFunc<object, int>("a", new object(), 5)相同,因为string可隐式转换为object。另一个例子是,MyFunc(42L, 42, 4)MyFunc<long, int>(42L, 42, 4)相同,因为int可以隐式转换为long

此外,有些情况下让编译器推断类型的能力不仅仅是好的,这是必要的。具体而言,使用匿名类型时会发生这种情况。例如,无法重写MyFunc(new { p = "p" }, new { p = "p2" }, 5)以明确指定类型。

答案 4 :(得分:1)

“谁真正决定T1类型?(p?p2?)”

通常,C#编译器决定这一点。如果其中一个方法参数是动态的,则决定在运行时完成(由Microsoft.CSharp库完成)。 在这两种情况下,都应用了C#规范中描述的类型推断算法:  pp2的类型都添加到T1下界集上界也是可能的,但只有涉及逆变仿制药时才会这样做。)

然后,编译器选择一组边界中的一种类型,这些类型也满足所有其他边界。 当只有一个绑定因为pp2具有相同的类型时,这个选择是微不足道的。 否则(假设只涉及下限),这意味着编译器选择一个类型,以便所有其他候选类型可以隐式转换为该类型(svick的答案描述)。

如果没有唯一的此类选择,则类型推断失败 - 如果可能,将选择另一个重载,否则会发生编译器错误(当在运行时完成决策(动态)时,会抛出异常)。

答案 5 :(得分:0)

在编译时,如果类型是显式的,那么编译器将检查传递的参数类型,并查看它们是否对应,并且可以与泛型中的类型匹配(无冲突)。

无论如何,真正的检查是在&#34;运行时&#34;无论如何,通用代码将编译为通用(与c ++模板不同)。然后,当JIT编译器编译该行时,它将检查并查看它是否可以根据您给出的模板和发送的参数创建方法。