Ignoring parentheses


Yet another amusing question from StackOverflow: is there a difference between “return something;” and “return (something);” in C#?

In practice, there is no difference.

In theory there could be a difference. There are three interesting points in the C# specification where this could present a problem.

First, conversion of anonymous functions to delegate types and expression trees. Consider the following:

Func<int> F1() { return ()=>1; }
Func<int> F2() { return (()=>1); }

F1 is clearly legal. Is F2? Technically, no. The spec says in section 6.5 that there is a conversion from a lambda expression to a compatible delegate type. Is that a lambda expression? No. It’s a parenthesized expression that contains a lambda expression.

The compiler makes a small spec violation here and discards the parenthesis for you.

Second:

int M() { return 1; }
Func<int> F3() { return M; }
Func<int> F4() { return (M); }

F3 is legal. Is F4? No. Section 7.5.3 states that a parenthesized expression may not contain a method group. Again, for your convenience we (accidentally!) violate the specification and allow the conversion.

Third:

enum E { None }
E F5() { return 0; }
E F6() { return (0); }

F5 is legal. Is F6? No. The spec states that there is a conversion from the literal zero to any enumerated type. “(0)” is not the literal zero, it is a parenthesis followed by the literal zero, followed by a parenthesis. We violate the specification here and actually allow any compile time constant expression equal to zero, and not just literal zero.

So in every case, we allow you to get away with it, even though technically doing so is illegal.

Comments (16)

  1. Lenny says:

    Is there any reason other than convenience that was the spec violated?

    The spec was not violated deliberately in order to be more convenient; these were all bugs. The semantic analyzer throws away the “meaningless” parentheses. — Eric

    Will this ever be “fixed” introducing breaking changes?

    Hopefully not. We try to only introduce breaking changes when there is a clear benefit to doing so. — Eric 

    Basically what I’m getting at is, is it better to avoid those scenarios.

    Yes. — Eric

  2. Stuart says:

    What are the odds that the spec will be updated to match the compiler’s actual behavior?

    Very low. — Eric

  3. blodbath says:

    I for one am glad that the spec was violated here.  When you’re programming you shouldn’t be fighting the grammar (as is often the case in C++); you should be focused on solving the problem at hand.

    Since the implemented behavior is clearly the desirable behavior I’m curious as to the answer to your question also Stuart. I also wonder if Mono deviates from the spec to provide these services…

    You’ll have to ask someone who is an expert on Mono. Or try it yourself. I wouldn’t know. — Eric

  4. Gabe says:

    Is there any chance the spec will be updated to reflect the more-desirable behavior?

  5. Jonathan Pryor says:

    @blodbath: Mono appears to follow .NET here: all of the methods in this blog entry compile without error under gmcs from Mono 2.4 and current trunk.

  6. mstrobel says:

    I would have thought the current compiler behavior existed for the sake of consistency, as the parentheses are required when casting a lambda to a delegate type.

    "(Action)() => DoSomething()" is not legal, but "(Action)(() => DoSomething())" is.

  7. mattzink says:

    I agree with Mike, though I really don’t understand why that cast isn’t implicit.

  8. Jonathan Pryor says:

    @mattzink Primarily, the cast can’t be implicit because there could be any number of matching delegate types, and the compiler doesn’t know which version to use.  For example, Action, CrossAppDomainDelegate, and ThreadStart all have the same prototype (no return type, no arguments).  Which should the compiler choose?

    A solution would be to always use Action/Func unless otherwise specified (or, instead, to always make lambda expressions Action/Func types which then implicitly convert to the final type, though this can’t support delegate types with ref/out parameters).  I don’t know why they didn’t provide a "default" type.

  9. mstrobel says:

    @Jonathan: They "sort of" did provide a "default" delegate type: System.Delegate.  This is used in a few places to accept "any" delegate, i.e. in WPF’s Dispatcher.Invoke() APIs.  However, the value you provide must have a concrete delegate type (e.g. you must cast it, construct a new delegate, or pass in an explicitly-typed member or variable).

  10. Stilgar says:

    I wonder why the specfication was written like this in the first place.

  11. Pavel Minaev says:

    > A solution would be to always use Action/Func unless otherwise specified (or, instead, to always make lambda expressions Action/Func types which then implicitly convert to the final type, though this can’t support delegate types with ref/out parameters).  I don’t know why they didn’t provide a "default" type.

    Because Action/Func didn’t appear until .NET 3.5, I guess.

  12. Mark Knell says:

    > Because Action/Func didn’t appear until .NET 3.5, I guess.

    And because Action/Func only cover, what, up to 4 method arguments?  Actually, I just looked in the spec and couldn’t find an upper limit.  Seems like Eric’s mentioned one semi-recently, that’s actually been encountered in the field (via codegen), but I couldn’t find that either. Not my day for research skills.

    <i>Is</i> there a limit in the spec, or is that an implementation detail?

  13. Mark Knell says:

    Clarifying last question: is there a limit on the number of formal arguments a method can have? Not asking if there’s a spec on Action/Func.

    I do not know of any restriction in C# or in the CLI on the number of formal parameters or generic type parameters. We’ve tested generic types with hundreds of generic type parameters with no problems. (Of course, methods or types with more than a handful of parameters are a bad idea anyway.) — Eric

  14. Mark Knell says:

    Thanks!  E. Lippert 1, Google 0.

    Apropos of nothing, I’d like to request that you never change your tag scheme to include anything that comes alphabetically after "What’s The Difference?"  Every time I visit, I look at the tag cloud and enjoy the conclusion:

    What’s The Difference? Zombies

    If you ever have a need to print T-shirts–perhaps in support of a North American tour?–you could do worse than putting that on them.

  15. Pavel Minaev says:

    > And because Action/Func only cover, what, up to 4 method arguments?

    It was 4 in .NET 3.5 (which was just enough to account for all extension methods in Enumerable). It’s up to 16 in .NET 4 (e.g. see http://msdn.microsoft.com/en-us/library/dd402872.aspx).

    > Actually, I just looked in the spec and couldn’t find an upper limit.

    The limit on number of template parameters is not directly relevant here, because neither Func nor Action are "magic" types – they are just normal generic delegate types manually declared in System.Core.dll, like so:

     delegate void Action();

     delegate void Action<T1>(T1 arg1);

     delegate void Action<T1, T2>(T1 arg1, T2 arg2);

     …

    Consequently, the only thing that matters here is however many parameters the library designers have decided to stop at.

    Anyway, there is no explicit constraint in the CLR spec on number of type parameters. As for implicit constraints – their definitions are referenced in the metadata tables via int32 indices.

  16. Mark Knell says:

    > The limit on number of template parameters is not directly relevant here

    That’s surprising.  I’d have said the upper limit on formal parameters *is* relevant, in a hairsplitting kind of way–and *because* Func and Action are "normal generic delegate types manually declared in System.Core.dll" and not magical.  The framework can only declare a finite number of Action/Func delegates, while the spec allows arbitrarily many formal parameters on a method.  Therefore, an algorithm that maps lambda expressions to pre-existing delegates built into the framework, is provably out of spec.  

    I suppose the algorithm could be tweaked to choose Action/Func when an appropriate match exists, and fail to compile otherwise, but that’d be a surprising kind of compile error, to me at least.  I’m making a generalization, but my sense of the C# compiler philosophy (almost entirely learned here) is to fail to compile if it can detect even a *potential* problem, even if an easily and effective workaround might exist.