Why Can't C# Infer Type from This Seemingly Simple, Obvious Case

Why can't C# infer type from this seemingly simple, obvious case


Test(A);

This fails because the only applicable method (Test<T>(Action<T>)) requires type inference, and the type inference algorithm requires that each each argument be of some type or be an anonymous function. (This fact is inferred from the specification of the type inference algorithm (§7.5.2)) The method group A is not of any type (even though it is convertable to an appropriate delegate type), and it is not an anonymous function.

Test<string>(A);

This succeeds, the difference being that type inference is not necessary to bind Test, and method group A is convertable to the required delegate parameter type void Action<string>(string).

Test((string a) => {});

This succeeds, the difference being that the type inference algorithm makes provision for anonymous functions in the first phase (§7.5.2.1). The parameter and return types of the anonymous function are known, so an explicit parameter type inference can be made, and a correspondense is thereby made between the types in the anonymous function (void ?(string)) and the type parameter in the delegate type of the Test method’s parameter (void Action<T>(T)). No algorithm is specified for method groups that would correspond to this algorithm for anonymous functions.

Test((Action<string>)A);

This succeeds, the difference being that the untyped method group parameter A is cast to a type, thereby allowing the type inference of Test to proceed normally with an expression of a particular type as the only argument to the method.

I can think of no reason in theory why overload resolution could not be attempted on the method group A. Then—if a single best binding is found—the method group could be given the same treatment as an anonymous function. This is especially true in cases like this where the method group contains exactly one candidate and it has no type parameters. But the reason it does not work in C#4 appears to be the fact that this feature was not designed and implemented. Given the complexity of this feature, the narowness of its application, and the existance of three easy work-arounds, I am not going to be holding my breath for it!

Generics: Why can't the compiler infer the type arguments in this case?

UPDATE: This answer was written over ten years ago; since then the type inference specification and implementation have been updated several times including changes to how constraints are used during inference. This answer should be considered of historical interest only; consult a recent copy of the C# specification to see how type inference works in current implementations.



I realize that type inference isn't an exact science

I'm not sure I agree. The spec is quite detailed.

I was wondering if there's some fundamental 'rule' I'm missing here

The fundamental rule that you're missing is probably that constraints are not part of the signature. Type inference works off of the signature.

There are in my opinion good reasons for that design decision. However, many people believe that I am morally wrong for believing that there are good reasons for that design decision. If you're interested in reading what feels like several million words on the topic of whether I'm right or wrong, see my article on the subject and the hundred or so comments telling me I'm wrong:

https://docs.microsoft.com/en-us/archive/blogs/ericlippert/constraints-are-not-part-of-the-signature

Is this a shortcoming of the inference process?

Arguably, yes. In my opinion, it is a reasonable choice given competing design requirements. (Those being "do what the user meant" and "give errors when things look ambiguous".)

is my expectation that the compiler should "figure it out" unreasonable in this case?

No. You seem like a reasonable person, and your expectation appears to be based on good reasoning. However, it is entirely possible to have a reasonable expectation that nevertheless is unmet. This would be one of those cases.

Can I change the method's signature in a way that would make it equally functional yet 'inferrable'?

That's going to be difficult, since the generic Dictionary type is not covariant or contravariant in its conversions. The concept you want to capture is not easily expressed in the type system in a manner that affords inference.

If you prefer using languages with more advanced type inference, consider using F#. If you prefer languages that skew towards "do what the user meant" rather than "report errors on ambiguity", consider using VB.

Why can't the C# constructor infer type?


Is there a philosophical reason why the constructor can't support type inference?

No. When you have

new Foo(bar)

then we could identify all types called Foo in scope regardless of generic arity, and then do overload resolution on each using a modified method type inference algorithm. We'd then have to create a 'betterness' algorithm that determines which of two applicable constructors in two types that have the same name but different generic arity is the better constructor. In order to maintain backwards compatibility a ctor on a non-generic type must always win.

Is there a practical reason why the constructor can't support type inference?

Yes. Even if the benefit of the feature outweighs its costs -- which are considerable -- that's not sufficient to have a feature implemented. Not only does the feature have to be a net win, it has to be a large net win compared to all the other possible features we could be investing in. It also has to be better than spending that time and effort on bug fixing, performance work, and other possible areas that we could put that effort. And ideally it has to fit in well to whatever the "theme" is of the release.

Furthermore, as you correctly note, you can get the benefits of this feature without actually having the feature itself, by using a factory pattern. The existence of easy workarounds makes it less likely that a feature will ever be implemented.

This feature has been on the list of possible features for a long time now. It's never been anywhere near high enough on the list to actually get implemented.

UPDATE March 2015

The proposed feature made it close enough to the top of the list for C# 6 to be specified and designed, but was then cut.

Type inference fails mysteriously

I think that the problem is not that the compiler if failing to infer R for the function CallWithEscapeContinuation, it is that it is failing to infer a type for the lambda:

ec =>
{
Enumerable.Range(0, 100).Select(x =>
{
// Called here, the compiler has no idea what signature to expect for `ec`
// Could be Action<Int>, could be Action<Decimal> (for example).
if (x == 40) ec(x);
return x;
}).ToList();
return -1;
}

Whereas when you provide the int hint, it can infer what type the lambda is from that and the signature of CallWithEscapeContinuation.

When you just have Action (as opposed to Action<R>) the above is irrelevant because there are no type parameters affecting the possible signatures of the lambda.

Why can't C# compiler infer generic-type delegate from function signature?

I suppose it probably has something to do with the fact that, at least from the compiler's perspective, Test is actually a 'method group' until the compiler has determined what it types of parameters it will have. This is true even if there is only one method in the group (only one Test method in the current scope).

Observe:

var composed = Compose<object>(Test, () => Console.WriteLine(" world"));

yields the error:

The best overloaded method match for 'Compose<object>(System.Action<object>, System.Action)' has some invalid arguments

Argument 1: cannot convert from 'method group' to 'System.Action<object>'

But this is fine:

var composed = Compose<string>(Test, () => Console.WriteLine(" world"));

My guess is that the compiler see the both method group expression (Test) and implicitly typed generic method invocation (Compose) as 'unbound' in a sense. It can't fully determine which method to select from the method group from the type 'unbound' signature of the parameter to Compose, and it can't determine which the type type parameter for Compose from the signature. It needs one or the other to be 'bound' in order to compile the whole statement.



Related Topics



Leave a reply



Submit