可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am wondering why the C# 3.0 compiler is unable to infer the type of a method when it is passed as a parameter to a generic function when it can implicitly create a delegate for the same method.
Here is an example:
class Test
{
static void foo(int x) { }
static void bar<T>(Action<T> f) { }
static void test()
{
Action<int> f = foo; // I can do this
bar(f); // and then do this
bar(foo); // but this does not work
}
}
I would have thought that I would be able to pass foo
to bar
and have the compiler infer the type of Action<T>
from the signature of the function being passed but this does not work. However I can create an Action<int>
from foo
without casting so is there a legitimate reason that the compiler could not also do the same thing via type inference?
回答1:
Maybe this will make it clearer:
public class SomeClass
{
static void foo(int x) { }
static void foo(string s) { }
static void bar<T>(Action<T> f){}
static void barz(Action<int> f) { }
static void test()
{
Action<int> f = foo;
bar(f);
barz(foo);
bar(foo);
//these help the compiler to know which types to use
bar<int>(foo);
bar( (int i) => foo(i));
}
}
foo is not an action - foo is a method group.
- In the assignment statement, the compiler can tell clearly which foo you're talking about, since the int type is specified.
- In the barz(foo) statement, the compiler can tell which foo you're talking about, since the int type is specified.
- In the bar(foo) statement, it could be any foo with a single parameter - so the compiler gives up.
Edit: I've added two (more) ways to help the compiler figure out the type (ie - how to skip the inference steps).
From my reading of the article in JSkeet's answer, the decision to not infer the type seems to be based on a mutual infering scenario, such as
static void foo<T>(T x) { }
static void bar<T>(Action<T> f) { }
static void test()
{
bar(foo); //wut's T?
}
Since the general problem was unsolve-able, they choose to left specific problems where a solution exists as unsolved.
As a consequence of this decision, you won't be adding a overload for a method and getting a whole lot of type confusion from all the callers that are used to a single member method group. I guess that's a good thing.
回答2:
The reasoning is that if the type ever expands there should be no possibility of failure. i.e., if a method foo(string) is added to the type, it should never matter to existing code - as long as the contents of existing methods don't change.
For that reason, even when there is only one method foo, a reference to foo (known as a method group) cannot be cast to a non-type-specific delegate, such as Action<T>
but only to a type-specific delegate such as Action<int>
.
回答3:
That is slightly odd, yes. The C# 3.0 spec for type inference is hard to read and has mistakes in it, but it looks like it should work. In the first phase (section 7.4.2.1) I believe there's a mistake - it shouldn't mention method groups in the first bullet (as they're not covered by explicit parameter type inference (7.4.2.7) - which means it should use output type inference (7.4.2.6). That looks like it should work - but obviously it doesn't :(
I know that MS is looking to improve the spec for type inference, so it might become a little clearer. I also know that regardless of the difficulty of reading it, there are restrictions on method groups and type inference - restrictions which could be special-cased when the method group is only actually a single method, admittedly.
Eric Lippert has a blog entry on return type inference not working with method groups which is similar to this case - but here we're not interested in the return type, only on the parameter type. It's possible that other posts in his type inference series may help though.
回答4:
Keep in mind that the assignment
Action<int> f = foo;
already has lots of syntactic sugar. The compiler actually generates code for this statement:
Action<int> f = new Action<int>(foo);
The corresponding method call compiles without problem:
bar(new Action<int>(foo));
Fwiw, so does helping the compiler to deduce the type argument:
bar<int>(foo);
So it boils down to the question, why the sugar in the assignment statement but not in the method call? I'd have to guess that it's because the sugar is unambiguous in the assignment, there is only one possible substitution. But in the case of method calls, the compiler writers already had to deal with the overload resolution problem. The rules of which are quite elaborate. They probably just didn't get around to it.
回答5:
Just for completeness, this is not specific to C#: The same VB.NET code fails similarly:
Imports System
Module Test
Sub foo(ByVal x As integer)
End Sub
Sub bar(Of T)(ByVal f As Action(Of T))
End Sub
Sub Main()
Dim f As Action(Of integer) = AddressOf foo ' I can do this
bar(f) ' and then do this
bar(AddressOf foo) ' but this does not work
End Sub
End Module
error BC32050: Type parameter 'T' for 'Public Sub bar(Of T)(f As System.Action(Of T))' cannot be inferred.