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

5

73

Given this code:

class C
{
    C()
    {
        Test<string>(A); // fine
        Test((string a) => {}); // fine
        Test((Action<string>)A); // fine

        Test(A); // type arguments cannot be inferred from usage!
    }

    static void Test<T>(Action<T> a) { }

    void A(string _) { }
}

The compiler complains that Test(A) can't figure out T to be string.

This seems like a pretty easy case to me, and I swear I've relied far more complicated inference in other generic utility and extension functions I've written. What am I missing here?

Update 1: This is in the C# 4.0 compiler. I discovered the issue in VS2010 and the above sample is from a simplest-case repro I made in LINQPad 4.

Update 2: Added some more examples to the list of what works.

Coraciiform answered 3/6, 2011 at 15:28 Comment(11)
Which version of C# are you using? The C# 4 compiler has better type inference with respect to method groups than the C# 3 compiler.Zloty
I verified that under C# 4 this is still a compiler error.Almeida
@Jon - but even C# 4 doesn't resolve thatScamander
Right, that's a shame. Sorry I can't verify it myself - I'm on holiday at the moment and only have the hotel's laptop. I suppose I could install .NET 4 on that :)Zloty
@Scott: Does it work if A is static? (I can't test it right now, sorry.)Chape
@Jon Skeet - I don't believe for a moment that you own a laptop without .NET installed. =)Almeida
@Mehrdad, no, that does not change the error.Flummox
@Yuck: I don't own the laptop I'm typing on. It's in the hotel room I'm staying in. Although there's also my CR-48 which doesn't have .NET installed... strictly speaking I think Google still owns that though...Zloty
@JonSkeet Are computers in rooms commonplace where you are? I've never seen that before in the USTherianthropic
@Michael: Nope, but I'm in a very nice suite in Florence.Zloty
Possible duplicate of Why can't the C# constructor infer type?Heptangular
A
40
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!

Almagest answered 3/6, 2011 at 19:45 Comment(6)
Thank you for such a thoroughly explained walk through all of the cases. This settles the question for me!Coraciiform
Overload resolution cannot be attempted because overload resolution determines a method given a method group and arguments. But it is the argument types we are attempting to work out! That's a chicken-and-egg problem which we do not attempt to solve.Kendallkendell
Saying that "the method group contains only one method so let's provisionally say that overload resolution succeeds even if we don't know the arguments" is essentially adding a new, weird overload resolution algorithm that only succeeds in the rare case of the method group containing one method. We really don't want to go there; do you really want a situation where adding a second overload severely changes how type inference works?Kendallkendell
Finally, note that in C# 4, if the thing you are attempting to infer is a return type and all the argument types have already been inferred successfully then overload resolution will proceed.Kendallkendell
@EricLippert "a new, weird overload resolution algorithm that only succeeds in the rare case of the method group containing one method?" If that allows me to more easily compose Funcs, then sure, give me all the compiler support that can be squeezed out. I currently can't write collection.Where(Not(PredicateMethodGroup)), but have to go with collection.Where(Not<TItem>(PredicateMethodGroup)), which is a shame really.Melicent
@EricLippert From a user's point of view, it seems that if it can be inferred, it should be inferred. There's no reason the compiler can't figure out that the method group contains exactly one method that matches the requirements. From a compiler-dev's point of view, I completely understand that it's probably a narrow case to specially support. However my point is that I don't think any user would feel that adding a second overload "severely changes how type inference works". It would make perfect sense. New overload means types can no longer unambiguous and can't be inferred. That's all.Frustum
C
8

I think it's because it's a two-step inference:

  • It has to infer that you want to convert A to a generic delegate

  • It has to infer what the type of the delegate parameter should be

I'm not sure if this is the reason, but my hunch is that a two-step inference isn't necessarily easy for the compiler.


Edit:

Just a hunch, but something is telling me the first step is the problem. The compiler has to figure out to convert to a delegate with a different number of generic parameters, and so it can't infer the types of the parameters.

Chape answered 3/6, 2011 at 15:33 Comment(4)
This is sounding right to me. I can do Test((string a) => {}) and it resolves fine, or Pierre-Alain's example of Test((Action<string>)A) and it also works. But why? Is this a limitation of the compiler? A quirk in the rules? Now that I think about it, I haven't ever run into this because 99% of the time I pass lambdas into functions I write like Test, so this problem would never occur.Coraciiform
@Scott: I think your first example is really good evidence that this is true; I didn't think of that test. Regarding the "why": I think it's because a two-step inference is normally very hard and time-consuming, and no one thought to special-case this (although I guess that's not too convincing for compiler writers haha, since they have to worry about far worse special cases).Chape
@Scott: Actually, I seem to be wrong -- or else this first case should have compiled, right?Chape
@Scott: Or maybe not -- that's still a two-step conversion, one to infer StringAction to be Action<T>, one to infer T to infer T to be string...Chape
S
5

This looks like a vicious circle to me.

Test method expects a parameter of delegate type constructed from generic type Action<T>. You pass in a method group instead: Test(A). This means compiler has to convert your parameter to a delegate type (method group conversion).

But which delegate type? To know the delegate type we need to know T. We didn't specify it explicitly, so compiler has to infer it to figure out the delegate type.

To infer the type parameters of the method we need to know the types of the method arguments, in this case the delegate type. Compiler doesn't know the argument type and thus fails.

In all other cases either type of argument is apparent:

// delegate is created out of anonymous method,
// no method group conversion needed - compiler knows it's Action<string>
Test((string a) => {});

// type of argument is set explicitly
Test((Action<string>)A); 

or type parameter is specified explicitly:

Test<string>(A); // compiler knows what type of delegate to convert A to

P.S. more on type inference

Shotten answered 3/6, 2011 at 19:8 Comment(1)
Specifically, neither an anonymous method nor a method group has a type of its own, but an anonymous method causes type inference to do some extra work to utilize the parameter types of the method (§7.5.2.1, 7.5.2.7) whereas a method group (even a method group with only one member) does not.Arcature
C
2

You're passing the name of the Method A. The .Net framework CAN convert it to an Action, but it's implicit and it will not take responsibility for it.

But still, a method name is NOT an explicit Action<> Object. And therefor it won't infer the type as an Action type.

Carbineer answered 3/6, 2011 at 17:40 Comment(2)
What do you mean by "will not take responsibility for it"? Do you mean that the compiler stops after doing such a conversion and the option is not open for inference at that point?Coraciiform
No, I mean that the compiler will not implicitly infer a type from its own conversions. And that is because it can't safely infer what you meant to do (In general, although in this case it might seem obvious). That's why in specific cases (like this) you need to have an explicit cast to tell it what type exactly you meant.Carbineer
A
2

I could be wrong, but I imagine the real reason C# cannot infer the type is due to method overloading and the ambiguity that arises. For example, suppose I have the following methods: void foo (int) and void foo (float). Now if I write var f = foo. Which foo should the compiler pick? Likewise, the same problem happens with your example using Test(foo).

Authenticate answered 30/7, 2012 at 16:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.