3

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?

Community
  • 1
  • 1
David Arno
  • 42,717
  • 16
  • 86
  • 131
  • I'm voting to close as too broad - type inference is covered in the C# spec. If you're asking why _your specific code_ won't compile that's one thing, but asking for all rules of type inference is too broad. – D Stanley Jun 06 '16 at 21:39
  • @DStanley, where in the spec? – David Arno Jun 06 '16 at 21:40
  • 1
    `var func = Func((double x, double y) => $"{x + y}");` - it's not inferring anything. It's accepting two doubles and outputting a string, so it's of type `Func`. – Joe Enos Jun 06 '16 at 21:43
  • 3
    @DavidArno 7.5.2 Type inference in [the C# 5 Spec](https://www.microsoft.com/en-us/download/details.aspx?id=7029) – AGB Jun 06 '16 at 21:43
  • It cannot infer the types of `x` and `y`, because it would compile for more than one type. It's not going to choose one for you. For the other examples, there is no type inference for the parameters, because you are explicitly saying the type is `double` – Dennis_E Jun 06 '16 at 21:43
  • @JoeEnos, it is inferring the return type as I haven't explicitly defined it. It's worked out that the return type is a string and thus a `Func`. – David Arno Jun 06 '16 at 21:48
  • @Dennis_E, that doesn't seem right. If I define `sealed class C { public string S; }` and `private static string X(C x) => x.S;`, then for `var func = Func((x, y) => X(x) + X(y));`, the types of `x` and `y` can only be `C`, but again the compiler won't infer this. So it doesn't seem to be a due to type ambiguity. – David Arno Jun 06 '16 at 21:55
  • @DavidArno The compiler is not as smart as you. You can infer the types of the parameters *by looking at the return type*. The compiler must be able to infer the type of each parameter by looking at that parameter only. – Dennis_E Jun 06 '16 at 22:07

3 Answers3

6

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.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Perfect. Thanks Eric.Shame your video has disappeared though; that sounds like it would have been worth watching. – David Arno Jun 07 '16 at 06:54
1

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..

Ciro Corvino
  • 2,038
  • 5
  • 20
  • 33
0

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));
    
Xiaoy312
  • 14,292
  • 1
  • 32
  • 44