Emanuel Strömgren
Emanuel Strömgren

Reputation: 383

Why can't the generic type be inferred from a parameter defined by the same generic as part of a delegate method?

I do not understand why the call to GenericMethod<T>() cannot be inferred. My actual problem contains many more generic parameters, making the calling code very verbose and difficult to read.

GenericMethod<T>() can be called without explicit type arguments if the parameter is instead itself 'T', but constraints doesn't work with delegates, and as soon as you put in constrained type parameters, you're back to square one.

delegate void GenericDelegate<T>(T t);

static void GenericMethod<T>(GenericDelegate<T> _) { }

static void IntMethod(int _) { }

static void CallingMethod()
{
    // The type arguments for method 'GenericMethod<T>(GenericDelegate<T>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
    GenericMethod(IntMethod);
}

Is there another way to make the calling code as simple as described here? The GenericMethod<T>() is part of an internal library.

Upvotes: 2

Views: 672

Answers (3)

Eric Lippert
Eric Lippert

Reputation: 660533

I do not understand why the call to GenericMethod<T>() cannot be inferred.

The question is a bit vague. Let me try to crisp it up.

What does the C# specification say about generic method type inference where an argument is a method group?

Inference proceeds in two phases. In the first phase, deductions are made from anonymous function arguments where parameter types are known and arguments with types. Method groups do not have types, so no deductions are made from method groups in the first phase.

In the second phase, deductions are made from lambdas and method groups where the target type is a delegate type whose input types are already inferred.

In your example the input type is T, which has not been inferred, and therefore no inference happens.

That's why type inference fails.

That answer might be unsatisfactory, but that's because you asked a vague question. Let's try again to approach a crisper version of your question by asking a follow-up question:

What justifies the design decision that input types must be inferred before type inference considers evidence from method groups?

We made this decision because evidence from method groups is derived by first determining the exact method from the group that the user intends to refer to. How then do we determine that method? The same way we determine what method in a method group you mean in any other situation. We do overload resolution on that method group. But overload resolution needs to know the types of the arguments, which means that the input types must be known at the time that the method group is analyzed.

For example, if we have void M<A, B>(A a, Func<A, B> f){} and string N(int y), and M(123, N) then we first determine that A is int in phase one, and now the inputs in Func<A, B> are known, so overload resolution can determine that string N(int) is intended, and therefore B is string.

Now your follow-up question surely would be:

But in my situation the method group only has one member. Can't the compiler simply skip overload resolution and determine that I mean the only method in the group?

What you're really saying is that you want two different rules, one narrowly tailored for your scenario, and one that works in general.

First off, the C# design team tries to avoid situations where a rule is created for a narrowly-tailored, rare situation. The point of type inference is not to make all possible inferences using tricks and heuristics. It's intended to be principled and to use existing algorithms as much as possible. Moreover, the inference algorithm is already complicated; let's not make it more complicated.

But oh, it gets worse. Suppose the C# design team decided to implement the rule "overload resolution is skipped if the method group contains one method". That would satisfy you temporarily, I suppose, but what is the knock-on effect? We've just created a bomb that will go off later when you add a second method to the method group. It makes adding a second method a potentially breaking change in an unrelated location, which is unfortunate.

Moreover, you likely would not be satisfied for long. "I added a second method, but it was of a different arity, so change overload resolution again to discard methods of the wrong arity until there is only one left" ... and so on. Again, it just becomes more and more complicated and harder and harder for the algorithm to be designed, specified, implemented, tested, documented and explained. As these algorithms get more complicated, they become more likely to do what the user intends, sure, but when they don't, the user is then left with a morass of rules to try to understand. (One might argue that we are already there, and so let's not make it worse.)

In short: in principle, it is certainly possible to make the kinds of deductions you want. The compiler team chooses not to, in an effort to make the inference algorithm understandable by humans.

Why is it different when doing it with a lambda? This compiles: GenericMethod((int v) => IntMethod(v))

You should now have the necessary information to answer that question, but to spell it out:

  • Overload resolution on IntMethod inside the lambda knows that v is of type int, so overload resolution succeeds.

  • Now that overload resolution has succeeded, we know that the lambda is int => void, and an inference can be made to void GenericDelegate<T>(T t).

Had you instead written GenericMethod(v => IntMethod(v)) type inference would fail again. The body of the lambda cannot be analyzed until the type of v is known, and the type of v depends on the inferred type of T, which is what we're trying to work out in the first place, and so the inference algorithm makes no progress and fails.

Is there another way to make the calling code as simple as described here?

Nope. Just insert the <int> and skip type inference entirely. Or cast the argument to the appropriate GenericDelegate type, which will make overload resolution operate on the method group given the types in the generic delegate.

Upvotes: 9

Emanuel Str&#246;mgren
Emanuel Str&#246;mgren

Reputation: 383

Eric Lippert's answer provides all the details on why this doesn't work, and more of the design philosophy around it. I'll provide a shorter way of describing the problem with code, for the future googlers. With a slight modification to the question code, it should be obvious why the type can't be inferred:

delegate void GenericDelegate<T>(T t);

static void GenericMethod<T>(GenericDelegate<T> method) { }

static void Method(int _) { }
static void Method(float _) { }

static void CallingMethod()
{
    GenericMethod(Method);
}

When method group Method has two overloads, the compiler can't infer the type of T. The issue is actually exactly the same even if that method group only contains one overload!

Now, to answer my other question, can the calling code be as simple as described above? Well, not without major restructuring. Something like this works for me, though:

static void GenericMethod<T>(IMethod<T> method) { }

interface IMethod<T>
{
    void Method(T _);
}

class IntMethod: IMethod<int>
{
    public void Method(int _) { }
}

static void CallingMethod()
{
    GenericMethod(new IntMethod());
}

I'll admit, it's vastly different from the code example above, but achieves what I was aiming for: A clean calling site. This solution comes at the cost of a slightly messier declaration. Whether this is a win or loss in readability and usage is highly dependent on the use-case.

Upvotes: 1

Kit
Kit

Reputation: 21769

Take a look at Eric Lippert's series about type inference.

It comes down to reasons that any language feature

  • Can be expensive (time, resources) to implement.
  • Must be well considered, especially if it is a breaking change.

Upvotes: 1

Related Questions