C# 到底是谁最后决定了什么是泛型类型?

C# 到底是谁最后决定了什么是泛型类型?,c#,generics,.net-4.0,C#,Generics,.net 4.0,我有这个功能 public static T2 MyFunc<T1, T2>( T1 a, T1 b, T2 c) { return c; } 我使用以下命令调用函数: MyClass.MyFunc(p, p2, 5); 我的问题是: 谁来决定T1类型?(p?p2?) 因为如果左边的是苹果,他会检查第二个也是苹果 如果第二个是橙色的,他应该检查第一个也是橙色的 问这个问题似乎很奇怪,因为在编译时,如果不一样,

我有这个功能

 public static T2 MyFunc<T1, T2>( T1 a, T1 b, T2 c)
        {
            return c;
        }     
我使用以下命令调用函数:

 MyClass.MyFunc(p, p2, 5);
我的问题是:


谁来决定T1类型?(p?p2?)

因为如果左边的是苹果,他会检查第二个也是苹果

如果第二个是橙色的,他应该检查第一个也是橙色的

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

仍然-谁决定类型


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

在调用中省略类型的可能性

MyClass.MyFunc(p1, p2, 5);
是一个语法糖果(除非您使用匿名类型),它的编译方式与

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

编译器根据参数
a
b
c
的类型推断出
T1
T2
的值。如果
p1
p2
是不兼容的类型(请参阅,编译器将无法推断
T1
,这将导致编译错误。

两者都没有优先级(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;
}     
但这是:

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

MyFunc<Apple, string>(a,b,c);
dynamic a=新橙色();
动态b=新苹果();
字符串c=“Doh。”;
MyFunc(a、b、c);
将抛出:

RuntimeBinderException: The best overloaded method match for 'UserQuery.MyFunc<UserQuery.Apple,string>(UserQuery.Apple, UserQuery.Apple, string)' has some invalid arguments
RuntimeBinderException:与“UserQuery.MyFunc(UserQuery.Apple,UserQuery.Apple,string)”匹配的最佳重载方法具有一些无效参数

然而,我似乎真的需要找到一本关于C#4.0中动态类型的好书或参考资料来理解这里发生的魔力。

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

无论如何,真正的检查是在“运行时”完成的。通用代码无论如何都会编译为通用的(与C++模板不同)。然后当JIT编译器编译该行时,它会检查它是否可以根据您所给的模板创建方法,以及发送的参数。


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

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

因此,例如
MyFunc(“a”,new object(),5)
MyFunc(“a,new object(),5)
相同,因为
string
可以隐式转换为
object
MyFunc(42L,42,4)
MyFunc(42L,42,4)相同
,因为
int
可隐式转换为
long

此外,在某些情况下,让编译器推断类型的能力不仅是好的,而且是必要的。特别是,在使用匿名类型时会发生这种情况。例如,
MyFunc(new{p=“p”},new{p=“p2”},5)
不能被重写以明确指定类型。

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

通常情况下,C#编译器会决定这一点。如果其中一个方法参数是动态的,则会在运行时(通过Microsoft.CSharp库)做出决定。 在这两种情况下,都应用了C#规范中描述的类型推断算法:
p
p2
这两种类型都被添加到
T1
的下限集合中(上限也是可能的,但仅当涉及逆变泛型时)

然后,编译器在边界集中选择一个类型,该类型也满足所有其他边界。 当由于
p
p2
具有相同的类型而只有一个边界时,这个选择是微不足道的。 否则(假设只涉及下限),这意味着编译器选择一个类型,以便所有其他候选类型都隐式转换为该类型(svick的答案描述了这一点)


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

在较高级别上,方法类型推断的工作方式如下所示

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

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

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 { }
(回想一下我们最近在另一个问题中的讨论,关于类型的相对大小取决于一个类型是否具有更多的限制性;限制性更强的类型比限制性较弱的类型小。长颈鹿比动物小,因为动物比长颈鹿多。“上”和“下”边界集正是这样:给定类型参数的类型推断问题的解决方案必须大于或等于每个下界,小于或等于每个上界,并且与每个精确界相同。)

然后我们看每个参数及其对应的类型。(如果参数是lambda,那么我们可能必须弄清楚我们看参数的顺序,但是这里没有lambda,所以让我们忽略这个细节。)对于每个参数,我们
p  --> T1
p2 --> T1
5  --> T2
T1: (unfixed) upper { }  lower { }  exact { }
T2: (unfixed) upper { }  lower { }  exact { }
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: (fixed) Person
T2: (fixed) int