Why can't an anonymous method be assigned to var?

45,021

Solution 1

Others have already pointed out that there are infinitely many possible delegate types that you could have meant; what is so special about Func that it deserves to be the default instead of Predicate or Action or any other possibility? And, for lambdas, why is it obvious that the intention is to choose the delegate form, rather than the expression tree form?

But we could say that Func is special, and that the inferred type of a lambda or anonymous method is Func of something. We'd still have all kinds of problems. What types would you like to be inferred for the following cases?

var x1 = (ref int y)=>123;

There is no Func<T> type that takes a ref anything.

var x2 = y=>123;

We don't know the type of the formal parameter, though we do know the return. (Or do we? Is the return int? long? short? byte?)

var x3 = (int y)=>null;

We don't know the return type, but it can't be void. The return type could be any reference type or any nullable value type.

var x4 = (int y)=>{ throw new Exception(); }

Again, we don't know the return type, and this time it can be void.

var x5 = (int y)=> q += y;

Is that intended to be a void-returning statement lambda or something that returns the value that was assigned to q? Both are legal; which should we choose?

Now, you might say, well, just don't support any of those features. Just support "normal" cases where the types can be worked out. That doesn't help. How does that make my life easier? If the feature works sometimes and fails sometimes then I still have to write the code to detect all of those failure situations and give a meaningful error message for each. We still have to specify all that behaviour, document it, write tests for it, and so on. This is a very expensive feature that saves the user maybe half a dozen keystrokes. We have better ways to add value to the language than spending a lot of time writing test cases for a feature that doesn't work half the time and doesn't provide hardly any benefit in cases where it does work.

The situation where it is actually useful is:

var xAnon = (int y)=>new { Y = y };

because there is no "speakable" type for that thing. But we have this problem all the time, and we just use method type inference to deduce the type:

Func<A, R> WorkItOut<A, R>(Func<A, R> f) { return f; }
...
var xAnon = WorkItOut((int y)=>new { Y = y });

and now method type inference works out what the func type is.

Solution 2

Only Eric Lippert knows for sure, but I think it's because the signature of the delegate type doesn't uniquely determine the type.

Consider your example:

var comparer = delegate(string value) { return value != "0"; };

Here are two possible inferences for what the var should be:

Predicate<string> comparer  = delegate(string value) { return value != "0"; };  // okay
Func<string, bool> comparer = delegate(string value) { return value != "0"; };  // also okay

Which one should the compiler infer? There's no good reason to choose one or the other. And although a Predicate<T> is functionally equivalent to a Func<T, bool>, they are still different types at the level of the .NET type system. The compiler therefore cannot unambiguously resolve the delegate type, and must fail the type inference.

Solution 3

Eric Lippert has an old post about it where he says

And in fact the C# 2.0 specification calls this out. Method group expressions and anonymous method expressions are typeless expressions in C# 2.0, and lambda expressions join them in C# 3.0. Therefore it is illegal for them to appear "naked" on the right hand side of an implicit declaration.

Solution 4

Different delegates are considered different types. e.g., Action is different than MethodInvoker, and an instance of Action can't be assigned to a variable of type MethodInvoker.

So, given an anonymous delegate (or lambda) like () => {}, is it an Action or a MethodInvoker? The compiler can't tell.

Similarly, if I declare a delegate type taking a string argument and returning a bool, how would the compiler know you really wanted a Func<string, bool> instead of my delegate type? It can't infer the delegate type.

Solution 5

The following points are from the MSDN regarding Implicitly Typed Local Variables:

  1. var can only be used when a local variable is declared and initialized in the same statement; the variable cannot be initialized to null, or to a method group or an anonymous function.
  2. The var keyword instructs the compiler to infer the type of the variable from the expression on the right side of the initialization statement.
  3. It is important to understand that the var keyword does not mean "variant" and does not indicate that the variable is loosely typed, or late-bound. It just means that the compiler determines and assigns the most appropriate type.

MSDN Reference: Implicitly Typed Local Variables

Considering the following regarding Anonymous Methods:

  1. Anonymous methods enable you to omit the parameter list.

MSDN Reference: Anonymous Methods

I would suspect that since the anonymous method may actually have different method signatures, the compiler is unable to properly infer what the most appropriate type to assign would be.

Share:
45,021

Related videos on Youtube

Marlon
Author by

Marlon

Updated on July 08, 2022

Comments

  • Marlon
    Marlon almost 2 years

    I have the following code:

    Func<string, bool> comparer = delegate(string value) {
        return value != "0";
    };
    

    However, the following does not compile:

    var comparer = delegate(string value) {
        return value != "0";
    };
    

    Why can't the compiler figure out it is a Func<string, bool>? It takes one string parameter, and returns a boolean. Instead, it gives me the error:

    Cannot assign anonymous method to an implicitly-typed local variable.

    I have one guess and that is if the var version compiled, it would lack consistency if I had the following:

    var comparer = delegate(string arg1, string arg2, string arg3, string arg4, string arg5) {
        return false;
    };
    

    The above wouldn't make sense since Func<> allows only up to 4 arguments (in .NET 3.5, which is what I am using). Perhaps someone could clarify the problem. Thanks.

    • Anthony Pegram
      Anthony Pegram over 13 years
      Note about your 4 arguments argument, in .NET 4, Func<> accepts up to 16 arguments.
    • Marlon
      Marlon over 13 years
      Thanks for the clarification. I'm using .NET 3.5.
    • Ben Voigt
      Ben Voigt over 13 years
      Why would make the compiler think that's a Func<string, bool>? It looks like a Converter<string, bool> to me!
    • nawfal
      nawfal almost 11 years
    • Slai
      Slai about 8 years
      sometimes I miss VB .. Dim comparer = Function(value$) value <> "0"
    • Ciro Corvino
      Ciro Corvino almost 8 years
      see this response at the link stackoverflow.com/a/37681225/3762855
    • Brain2000
      Brain2000 about 6 years
      VB.net has done this for years. I think C# 7.0 allows you to now declare nested functions inline. Dim comparer = Function(value As String) As Boolean Return value <> "0" End Function
    • Martin
      Martin over 2 years
      Another reason why C++ is a superior language, despite its flaws.
  • Anthony Pegram
    Anthony Pegram over 13 years
    I'm sure quite a few other people at Microsoft also know for sure. ;) But yes, you allude to a chief reason, the compile time type cannot be determined because there is none. Section 8.5.1 of the language specification specifically highlights this reason for disallowing anonymous functions from being used in implicitly typed variable declarations.
  • Anthony Pegram
    Anthony Pegram over 13 years
    And this is underscored by section 8.5.1 of the language specification. "The initializer expression must have a compile-time type" in order to be used for a implicitly typed local variable.
  • Eric Lippert
    Eric Lippert over 13 years
    Yep. And even worse, for lambdas we don't even know if it is going to a delegate type; it might be an expression tree.
  • The Scrum Meister
    The Scrum Meister over 13 years
    +1 Great answer. With everyone calling your name we knew you'd show up.
  • Matt Greer
    Matt Greer over 13 years
    When are you going to compile your SO answers into a book? I'd buy it :)
  • Adam Rackis
    Adam Rackis about 13 years
    I second the proposal for an Eric Lippert book of SO answers. Suggested title: "Reflections From The Stack"
  • user6170001
    user6170001 about 13 years
    For anyone interested, I wrote up a bit more about this and how the C# and F# approaches contrast at mindscapehq.com/blog/index.php/2011/02/23/…
  • user541686
    user541686 almost 13 years
    @Eric: Good answer, but it's slightly misleading to illustrate this as something that's not possible, as this actually works completely fine in D. It's just that you guys didn't choose to give delegate literals their own type, and instead made them depend on their contexts... so IMHO the answer should be "because that's how we made it" more than anything else. :)
  • Jonathan Dickinson
    Jonathan Dickinson over 12 years
    What would probably be something interesting to see would be dynamic foo = x => x + y - you could probably turn this into a trampoline that returns runtime compiled delegates - although it would probably only be interesting and nothing else.
  • Manu Vats
    Manu Vats about 12 years
    That means that we still have to define the WorkItOut<> generic as many times as we need different numbers of parameters, right ?
  • turbanoff
    turbanoff almost 12 years
    @ Raphaël Saint-Pierre: Right. C# don't support variadic template.
  • Rune FS
    Rune FS about 11 years
    "...Or do we? Is the return int? long? short? byte?" int, long, short or byte seems more appropriate than the nullable alternatives :p (yeah yeah I know those are question marks and not part of the type)
  • nawfal
    nawfal almost 11 years
    //Now, you might say, well, just don't support any of those features. Just support "normal" cases where the types can be worked out. That doesn't help. How does that make my life easier? // There are a few scenarios in C# where compiler throws ambiguity error (which means C# is not oblivious to making things easier only at times). Well if this feature doesn't make your benefit to cost ratio, then its a fair point..
  • Eric Lippert
    Eric Lippert almost 11 years
    @nawfal: You're missing my point. Writing that ambiguity checker is also work. The hardest part of writing the overload resolution algorithm in C# is not writing it to get the correct cases working. The code that works out what error to give when the code is wrong is longer and more complicated than the code that works out the correct case. Ambiguity checkers are very difficult to get right because the code is ambiguous.
  • michael
    michael over 8 years
    @Eric: If we assume that Func is the most common intent when assigning a lambda to a 'var' local, then it seems relatively easy for the C# compiler to essentially employ the behavior of your "WorkItOut" template in variadic form, since it works today in the current C# compiler. When passed to WorkItOut, each of the problematic examples you list produces the error "type arguments cannot be inferred from usage" except the last one (+=), which returns the resulting sum. This all seems quite reasonable, and I will be using my own "variadic" form of WorkItOut (multiple overloads) in the meantime.
  • ATD
    ATD about 8 years
    var x2 = (int y) =>123; Why would this not work for a function when it works for standard variables such as: var x2 = 123; ?
  • Eric Lippert
    Eric Lippert about 8 years
    @ATD: This answer answers that question: sure, the feature could be made to work for simple cases. That does not make the compiler developer's job easier. It makes it harder because now a "simple case detector" must be designed, specified, written, tested and documented. All that work takes away effort from features that actually would make a difference to developers' lives.
  • AbstractDissonance
    AbstractDissonance almost 7 years
    But it should for for straightforward no param delegates () => { } ==> void delegate().After all, it is just an Action which is longer than var.
  • Eric Lippert
    Eric Lippert almost 7 years
    @AbstractDissonance: See my previous comment.
  • AbstractDissonance
    AbstractDissonance almost 7 years
    @EricLippert That isn't an answer, that is a cop out. If you want to use that logic, you can use it to justify not having anything. Sorry, it's true... even though you will try to squirm out of the truth.
  • Eric Lippert
    Eric Lippert almost 7 years
    @abstractdissonance I assure you it is an answer. If its an answer you don't like then why not post an answer that you do like? That way I and everyone else can benefit from your insights on a question that plainly you care about.
  • Eric Lippert
    Eric Lippert almost 7 years
    @abstractdissonance I note also that the compiler is open source. If you care about this feature then you can donate the necessary time and effort to make it happen. I encourage you to submit a pull request.
  • Eric Lippert
    Eric Lippert almost 7 years
    @AbstractDissonance: Finally, your general claim is that I can use the logic "features have costs and benefits, and we try to implement only those features that give good benefits for reasonable costs" to justify not doing any feature. That is 100% correct. I do use that logic to not justify doing any feature whose costs exceed its benefits. There's no truth to "squirm out of" there; I have said dozens -- hundreds? -- of times on this site that features have costs and must be justified by their benefits. I not only embrace that truth, I state it emphatically over and over again!
  • AbstractDissonance
    AbstractDissonance almost 7 years
    What you are really saying is simply: "It's not worth our time, we have better things WE want to do"... and that is not a logical argument but simply an emphatic choice. At least be honest with yourself instead of trying to justify your choice with logic.
  • Eric Lippert
    Eric Lippert almost 7 years
    @AbstractDissonance: We measured the cost in terms of the limited resources: developers and time. This responsibility was not granted by god; it was imposed by the vice president of the developer division. The notion that somehow the C# team could ignore a budget process is a strange one. I assure you, tradeoffs were and still are made by the careful, thoughtful consideration of experts who had the C# communities expressed wishes, the strategic mission for Microsoft, and their own excellent taste in design in mind.
  • AbstractDissonance
    AbstractDissonance almost 7 years
    Ok, then we agree. They are made up rules to be congruent with other made up rules. I will have to disagree though about Microsoft. Microsoft does not have excellent taste in design(many windows products are a prime example). If we are specifically talking about C#'s design, then I would agree in relatively terms as it is a well designed language as compared to most(Although the IL representation and performance can be issues at times).
  • Weipeng L
    Weipeng L almost 6 years
    why can't the compiler just fabricate a new unique type like C++ does for its lambda function
  • arao6
    arao6 over 4 years
    How do they differ "at the level of the .NET type system"?