谁最终决定什么是通用类型?

发布于 2025-01-03 04:44:27 字数 662 浏览 2 评论 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 ?)

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

,如果第二个是橙子,他应该检查第一个也是橙子。

在此处输入图像描述

询问它似乎很奇怪,因为在编译时它们如果不一样就会失败。

仍然——谁决定类型?

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

I have this function

 public static T2 MyFunc<T1, T2>( T1 a, T1 b, T2 c)
        {
            return c;
        }     

I'm creating 2 Persons class instances:

 class Person
         {  }

            Person p = new Person();
            Person p2 = new Person();

I'm calling the function with :

 MyClass.MyFunc(p, p2, 5);

my Question is :

Who actually decide about the T1 type ? (p ? p2 ? )

Because if the left one is Apple so he checks that the second one is Also an apple

and if the second one is Orange - he should check that the first one is also an Orange.

enter image description here

It seems weird to ask it becuase at compile time they will fail if not the same.

Still - who decide about the type ?

And second - If i change it to dynamic - on runtime- who will decide what the T1 type should be ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

一身软味 2025-01-10 04:44:27

从高层次来看,方法类型推断的工作原理如下。

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

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

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,所以让我们忽略该细节。)对于我们所做的每个参数对形式参数类型的推论,并将我们推断出的关于该推论的事实添加到绑定集。因此,在查看第一个参数之后,我们推断出界限:

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/类型+推理/

At a high level, method type inference works like this.

First we make a list of all the arguments -- the expressions you supply -- and their corresponding formal parameter type.

Let's look at a more interesting example than the one you give. Suppose we have

class Person {}
class Employee : Person {}
...
Person p = whatever;
Employee p2 = whatever;

and the same call. So we make the correspondences:

p  --> T1
p2 --> T1
5  --> T2

Then we make a list of what "bounds" are on each type parameter and whether they are "fixed". We have two type parameters, and we start with no upper, lower or exact bounds.

T1: (unfixed) upper { }  lower { }  exact { }
T2: (unfixed) upper { }  lower { }  exact { }

(Recall our recent discussion in another question about the relative sizes of types being based on whether or not a type was more or less restrictive; a type that is more restrictive is smaller than one that is less restrictive. Giraffe is smaller than Animal because more things are Animals than are Giraffes. The "upper" and "lower" bound sets are exactly that: the solution to the type inference problem for a given type parameter must be larger than or identical to every lower bound and smaller than or identical to every upper bound, and identical to every exact bound.)

Then we look at each argument and its corresponding type. (If the arguments are lambdas then we might have to figure out the order in which we look at arguments, but you don't have any lambdas here so let's ignore that detail.) For each argument we make an inference to the formal parameter type, and add the facts that we deduce about that inference to the bound set. So after looking at the first argument, we deduce the bounds:

T1: (unfixed) upper { }  lower { Person }  exact { }
T2: (unfixed) upper { }  lower { }  exact { }

After the second argument we deduce the bounds

T1: (unfixed) upper { }  lower { Person, Employee }  exact { }
T2: (unfixed) upper { }  lower { }  exact { }

After the third argument we deduce the bounds:

T1: (unfixed) upper { }  lower { Person, Employee }  exact { }
T2: (unfixed) upper { }  lower { int }  exact { }

After we have made as much progress as we can, we "fix" the bounds by finding the best type in the bounds set that satisfies every bound.

For T1, there are two types in the bounds set, Person and Employee. Is there one of them that satisfies every bound in the bounds set? Yes. Employee does not satisfy the Person bound because Employee is a smaller type than Person; Person is a lower bound -- it means no type smaller than Person is legal. Person does satisfy all the bounds: Person is identical to Person and is larger than Employee, so it satisfies both bounds. The best type in the bounds set that satisfies every bound is for T1 is Person and for T2 obviously it is int because there is only one type in the bounds set for T2. So we then fix the type parameters:

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

Then we ask "do we have a fixed bound for every type parameter?" and the answer is "yes", so type inference succeeds.

If I change the first argument's type to dynamic then how is T1 inferred?

If any argument is dynamic then inference of T1 and T2 is deferred until runtime, at which point the semantic analyzer considers the most derived accessible runtime type of the value as the type for the lower bound supplied by the dynamic argument.


If this subject interest you and you want to learn more, there is a video of me explaining the C# 3 version of the algorithm here:

http://blogs.msdn.com/b/ericlippert/archive/2006/11/17/a-face-made-for-email-part-three.aspx

(C# 3 did not have upper bounds, only lower and exact bounds; other than that, the algorithms are pretty much the same.)

A number of articles I've written about type inference problems are here:

http://blogs.msdn.com/b/ericlippert/archive/tags/type+inference/

望喜 2025-01-10 04:44:27

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

MyClass.MyFunc(p1, p2, 5);

完全相同

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

是一个语法糖果(除非您使用匿名类型),并且它的编译方式与编译器推导出 T1T2根据参数abc的类型。如果 p1p2 是不兼容的类型(请参阅 svick 的回答 ,编译器将无法推导 T1 ,这将导致编译错误。

The possibility to omit the types in the call

MyClass.MyFunc(p1, p2, 5);

is a syntax candy (unless you're using anonymous types), and it compiles exactly identically to

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

The compiler deduces the values for T1 and T2 according to the types of the parameters a, b and c. If p1 and p2 are of incompatible types (see svick's answer, the compiler won't be able to deduce T1 and this will result in a compilation error.

感悟人生的甜 2025-01-10 04:44:27

没有优先级,两者(a 和 b)应该相同,即根据设计,T1 在编译时解析。如果更改为动态,则只需将类型解析推迟到运行时,如果类型不相同,它将在编译时失败。如果你想让它们有所不同,你需要引入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 中动态类型的好书或资源来理解这里发生的魔力。

There is no priority, both (a and b) should be the same, that is by design, T1 is resolved at compiling. If you change to dynamic, you just postpone type resolving to runtime and it will fail then instead at compiletime if the types are not the same. If you want them to be different, you need to introduce T3.

Edit:

The interesting part:

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;
}     

outputs:

I am an orange. I am an apple. Doh.

But this:

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

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

will throw:

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

However it seems I really need to find a good book or resource about dynamic types in C# 4.0 to understand the magic happening here.

莫相离 2025-01-10 04:44:27

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

这不是显而易见的吗?他们两个。 pp2 的类型必须兼容。与其他答案所说的相反,它们不必相同。实际规则是必须存在从一种类型到另一种类型的隐式转换。

因此,例如 MyFunc("a", new object(), 5)MyFunc("a", new object(), 5)< 相同/code>,因为string 可以隐式转换为object。再例如,MyFunc(42L, 42, 4)MyFunc(42L, 42, 4) 相同,因为 int< /code> 可隐式转换为 long

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

Who actually decide about the T1 type ? (p ? p2 ? )

Isn't it obvious? Both of them. The types of p and p2 have to be compatible. Contrary to what other answers are saying, they don't have to be the same. The actual rule is that there has to be an implicit conversion from on of the types to the other.

So, for example MyFunc("a", new object(), 5) is the same as MyFunc<object, int>("a", new object(), 5), because string is implicitly convertible to object. As another example, MyFunc(42L, 42, 4) is the same as MyFunc<long, int>(42L, 42, 4), because int is implicitly convertible to long.

Also, there are cases where the ability to let the compiler infer the types is not just nice, it's necessary. Specifically, that happens when using anonymous types. For example MyFunc(new { p = "p" }, new { p = "p2" }, 5) can't be rewritten to specify the types explicitly.

潦草背影 2025-01-10 04:44:27

“谁实际决定 T1 类型?(p ? p2 ?)”

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

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

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

"Who actually decide about the T1 type ? (p ? p2 ? )"

Normally, the C# compiler decides this. If one of the method arguments is dynamic, then the decision is done at runtime (by the Microsoft.CSharp library).
In both cases, the type inference algorithm described in the C# specification is applied:
Both the types of p and p2 are added to T1's set of lower bounds (upper bounds are also possible, but only when contravariant generics are involved).

Then, the compiler picks one of the types in the set of bounds that also satisfies all other bounds.
When there is only one bound because p and p2 have the same type, this choice is trivial.
Otherwise (assuming only lower bounds are involved), that means the compiler picks a type so that all other candidate types are implicitly convertible to that type (what svick's answer describes).

If there is no unique such choice, type inference fails - another overload gets chosen if possible, otherwise a compiler error occurs (when the decision is done at runtime (dynamic), an exception is thrown instead).

躲猫猫 2025-01-10 04:44:27

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

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

At compile time if the types are explicit then the compiler will check the types of parameters passed, and see if they correspond and can be matched to types in the generics (no conflicts).

Anyway, the real check is done at "runtime" the generic code will compile as generic anyway (unlike c++ templates). And then when the JIT compiler compiles the line it will check and see if it can create the method according to the templates you gave it, and the parameters sent.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文