David Arno
David Arno

Reputation: 43254

What are the rules the C# compiler uses to resolve types in a lambda expression?

With reference to Why can't an anonymous method be assigned to var?, I understand that the following is not supported in C#:

var func = (x,y) => Math.Log(x) + Math.Log(y);

However, I can create a method, Func of the form:

public static Func<T,T,T> Func<T>(Func<T,T,T> f) => f;

and then do:

var func = Func<double>((x,y) => Math.Log(x) + Math.Log(y));

that will compile just fine. However, for lambdas with different types for the parameters and return value, things get odd. For example, if I have a method:

public static Func<T1,T2,T3> Func<T1,T2,T3>(Func<T1,T2,T3> f) => f;

I can for example do:

var func = Func((double x, double y) => $"{x + y}");

and that too will compile. So the C# compiler seems to be able to infer the return type for a lambda. But, the following won't compile:

var func = Func((x,y) => Math.Log(x) + Math.Log(y));

as the compiler doesn't seem able to infer the types of x and y from the way they are used in the body.

So, my question: what are the definitive rules on type inference of lambda expressions; what will the compiler infer and what won't it?

Upvotes: 3

Views: 433

Answers (3)

Ciro Corvino
Ciro Corvino

Reputation: 2128

I agree with answers that have been given up now.. there is just needs to read the indicated reference for to know the exact rules of c#'s anonymous types's inference, and also I do admit to don't have the necessary knowledge for say how much broad is this question but I believe and I trust with who states that is a very broad question..

However I would have expected that, where the rules were unequivocal, then compiler should work, because the rules of some types of inferences are logical and not arbitrary.

In your example, the last compute cannot be carried out, because the type of each parameter could be everything else than a "double" type or some other compatible type, and this is right. But instead, if you had composed your lambda expression so that it were impossible to go wrong the inference of return type function, then, I would expected that it works, instead it doesn't work.

For example, the following code, I expect it works but doesn't:

public static Func<T1, T2, T3> Func<T1, T2, T3>(Func<T1, T2, T3> f) => f;
var func = Func((x, y) => string.Format("{0},{1}", x.ToString(), y.ToString()));

or again, this sould work, instead doesn't:

double x1 = 0, y1 = 0;
var func = Func((x, y) => Math.Log(double.TryParse(x.ToString(),x1)) + Math.Log(double.TryParse(y.ToString(), y1)));

So, it seems to me quite clear that compiler avoid totally to make every sort of inference. When it is safe, then it carries out compute, else not.

I hope I approached your question, or at least to have given a useful indication or trace..

Upvotes: 1

Eric Lippert
Eric Lippert

Reputation: 660138

what are the definitive rules on type inference of lambda expressions;

The definitive rules are in the specification. If you want to look at the implementation, you can find it easily enough in the Roslyn sources; I commented it pretty heavily, anticipating that there would be questions. Note that in particular the comment starting around line 110 is relevant to your question; study this carefully if you want a deep understanding of how lambda type inference works.

https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs

Consider compiling the compiler yourself in debug mode; you can then use the Dump method at breakpoints to describe the state of the type inference engine as it goes. I used this to facilitate more rapid debugging, and when considering extensions to the algorithm. (Some of which are still in the code, commented out.)

what will the compiler infer and what won't it?

That is far too broad a question to answer definitively. But the basic action with respect to the examples in your question is:

  • Bounds on type parameters are inferred from ordinary arguments matched with formal parameter types.
  • Lambdas where all formal parameter types of the lambda are inferred or known have their return types inferred
  • The previous step is repeated until the algorithm fails to make progress or deduces a contradiction.

I recorded a video -- ten years ago now -- explaining all this step by step but apparently it is no longer on MSDN. I am vexed.

Upvotes: 6

Xiaoy312
Xiaoy312

Reputation: 14477

You must provide the type all parameters and result, either :

  • Explicitly, by declaring it between the generic angle brackets(<T>) or within the lambda argument list(ex: (int a, int b) => { /*...*/ }).
  • Implicitly, where the compiler can infer the resulting type, the same way the type of var is inferred.

  • This compiles because the type are provided here, be double.

    public static Func<T,T,T> Func<T>(Func<T,T,T> f) => f;
    var func = Func<double>((x,y) => Math.Log(x) + Math.Log(y));
    
  • This also compiles because you've provided T1 and T2 as double, and T3 can be inferred as a string.

    public static Func<T1,T2,T3> Func<T1,T2,T3>(Func<T1,T2,T3> f) => f;
    var func = Func((double x, double y) => $"{x + y}");
    
  • However, this does not compile because the type of T1 and T2 is unknown. The compiler cannot their types from the way they are being used.

    public static Func<T1,T2,T3> Func<T1,T2,T3>(Func<T1,T2,T3> f) => f;
    var func = Func((x,y) => Math.Log(x) + Math.Log(y));
    

Upvotes: 0

Related Questions