I have this function
public static T2 MyFunc( T1 a, T1 b, T2 c)
{
return c;
}
I\'m creatin
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.
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/
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.
"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).
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.
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.