Freigeben über


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

  • Anonymous
    April 11, 2010
    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

  • Anonymous
    April 11, 2010
    What are the odds that the spec will be updated to match the compiler's actual behavior? Very low. -- Eric

  • Anonymous
    April 11, 2010
    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

  • Anonymous
    April 12, 2010
    Is there any chance the spec will be updated to reflect the more-desirable behavior?

  • Anonymous
    April 12, 2010
    @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.

  • Anonymous
    April 12, 2010
    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.

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

  • Anonymous
    April 12, 2010
    @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.

  • Anonymous
    April 12, 2010
    @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).

  • Anonymous
    April 12, 2010
    I wonder why the specfication was written like this in the first place.

  • Anonymous
    April 12, 2010
    > 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.

  • Anonymous
    April 13, 2010
    > 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?

  • Anonymous
    April 13, 2010
    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

  • Anonymous
    April 13, 2010
    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.

  • Anonymous
    April 13, 2010
    The comment has been removed

  • Anonymous
    April 13, 2010
    > 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.