Ignoring parentheses

Ignoring parentheses

Rate This
  • Comments 16

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.

  • 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

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

    Very low. -- Eric

  • 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

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

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

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

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

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

  • @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).

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

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

  • > 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?

  • 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

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

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

Page 1 of 2 (16 items) 12