Existe uma diferença entre return myVar e return (myVar)?

87

Eu estava olhando um exemplo de código C # e percebi que um exemplo envolveu o retorno em ().

Eu sempre acabei de fazer:

return myRV;

Existe alguma diferença fazendo:

return (myRV);
Chris
fonte

Respostas:

229

ATUALIZAÇÃO: Esta questão foi o assunto do meu blog em 12 de abril de 2010 . Obrigado pela pergunta divertida!

Na prática, não há diferença.

Em teoria , pode haver uma diferença. Existem três pontos interessantes na especificação C # onde isso pode apresentar uma diferença.

Primeiro, a conversão de funções anônimas para delegar tipos e árvores de expressão. Considere o seguinte:

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

F1é claramente legal. É F2? Tecnicamente, não. A especificação diz na seção 6.5 que há uma conversão de uma expressão lambda em um tipo de delegado compatível. Isso é uma expressão lambda ? Não. É uma expressão entre parênteses que contém uma expressão lambda .

O compilador Visual C # faz uma pequena violação de especificação aqui e descarta o parêntese para você.

Segundo:

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

F3é legal. É F4? Não. A seção 7.5.3 afirma que uma expressão entre parênteses não pode conter um grupo de métodos. Novamente, para sua conveniência, violamos a especificação e permitimos a conversão.

Terceiro:

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

F5é legal. É F6? Não. A especificação afirma que há uma conversão do zero literal para qualquer tipo enumerado. " (0)" não é o zero literal, é um parêntese seguido pelo zero literal, seguido por um parêntese. Violamos a especificação aqui e permitimos qualquer expressão de constante de tempo de compilação igual a zero , e não apenas zero literal.

Portanto, em todos os casos, permitimos que você escape impune, mesmo que isso seja tecnicamente ilegal.

Eric Lippert
fonte
12
@Jason: Eu acredito que as violações das especificações nos dois primeiros casos são simplesmente erros que nunca foram detectados. O passe inicial de vinculação historicamente tem sido muito agressivo quanto à otimização prematura de expressões, e uma das consequências disso é que os parênteses são jogados fora muito cedo, mais cedo do que deveriam. Em quase todos os casos, tudo isso faz com que programas intuitivamente óbvios funcionem da maneira que deveriam, então não estou muito preocupado com isso. A análise do terceiro caso está aqui: blogs.msdn.com/ericlippert/archive/2006/03/28/…
Eric Lippert
6
Em teoria, na prática, não é uma diferença (não tenho certeza se Mono permite que estes 3 casos, e não sei de quaisquer outros compiladores C #, então não pode ou não haver uma diferença na prática, na prática). Violar a especificação C # significa que seu código não será totalmente portátil. Alguns compiladores C # podem, ao contrário do Visual C #, não violar a especificação nesses casos particulares.
Brian
18
@Bruno: Bastam oito ou dez mil horas de estudo de um determinado assunto e você também pode ser um especialista nisso. Isso é facilmente realizável em quatro anos de trabalho em tempo integral.
Eric Lippert,
32
@Anthony: Quando faço isso, apenas digo às pessoas que meu diploma é em matemática , não em aritmética .
Eric Lippert,
7
Em teoria, prática e teoria são iguais, mas, na prática, nunca são.
Disse Ibrahim Hashimi
40

Existem casos extremos em que a presença de parênteses pode afetar o comportamento do programa:

1

using System;

class A
{
    static void Foo(string x, Action<Action> y) { Console.WriteLine(1); }
    static void Foo(object x, Func<Func<int>, int> y) { Console.WriteLine(2); }

    static void Main()
    {
        Foo(null, x => x()); // Prints 1
        Foo(null, x => (x())); // Prints 2
    }
}

2

using System;

class A
{
    public A Select(Func<A, A> f)
    {
        Console.WriteLine(1);
        return new A();
    }

    public A Where(Func<A, bool> f)
    {
        return new A();
    }

    static void Main()
    {
        object x;
        x = from y in new A() where true select (y); // Prints 1
        x = from y in new A() where true select y; // Prints nothing
    }
}

3 -

using System;

class Program
{
    static void Main()
    {
        Bar(x => (x).Foo(), ""); // Prints 1
        Bar(x => ((x).Foo)(), ""); // Prints 2
    }

    static void Bar(Action<C<int>> x, string y) { Console.WriteLine(1); }
    static void Bar(Action<C<Action>> x, object y) { Console.WriteLine(2); }
}

static class B
{
    public static void Foo(this object x) { }
}

class C<T>
{
    public T Foo;
}

Espero que você nunca veja isso na prática.

Vladimir Reshetnikov
fonte
Não é exatamente uma resposta à minha pergunta, mas ainda interessante - obrigado.
Chris
1
Você pode explicar o que está acontecendo em 2 aqui?
Eric
2
Você deve explicar por que esse comportamento acontece.
Arturo Torres Sánchez
26

Não, não há outra diferença senão sintática.

JaredPar
fonte
3

Uma boa maneira de responder a perguntas como essa é usar o Reflector e ver o que IL é gerado. Você pode aprender muito sobre otimizações de compilador e descompilando assemblies.

Bryan
fonte
6
Isso certamente responderia à pergunta para um caso específico, mas não seria necessariamente representativo de toda a situação.
Beska
Discordo. Dá à pessoa uma direção para responder à pergunta.
Bryan