¿Hay alguna diferencia entre return myVar y return (myVar)?

87

Estaba mirando un ejemplo de código C # y noté que un ejemplo envolvía la devolución en ().

Siempre acabo de hacer:

return myRV;

¿Hay alguna diferencia al hacer:

return (myRV);
chris
fuente

Respuestas:

229

ACTUALIZACIÓN: Esta pregunta fue el tema de mi blog el 12 de abril de 2010 . ¡Gracias por la divertida pregunta!

En la práctica, no hay diferencia.

En teoría , podría haber una diferencia. Hay tres puntos interesantes en la especificación de C # donde esto podría presentar una diferencia.

Primero, la conversión de funciones anónimas para delegar tipos y árboles de expresión. Considera lo siguiente:

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

F1es claramente legal. Es F2? Técnicamente, no. La especificación dice en la sección 6.5 que hay una conversión de una expresión lambda a un tipo de delegado compatible. ¿Es esa una expresión lambda ? No. Es una expresión entre paréntesis que contiene una expresión lambda .

El compilador de Visual C # hace una pequeña infracción de especificación aquí y descarta el paréntesis por usted.

Segundo:

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

F3es legal. Es F4? No. La sección 7.5.3 establece que una expresión entre paréntesis no puede contener un grupo de métodos. Nuevamente, para su conveniencia, violamos la especificación y permitimos la conversión.

Tercero:

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

F5es legal. Es F6? No. La especificación establece que hay una conversión del literal cero a cualquier tipo enumerado. " (0)" no es el cero literal, es un paréntesis seguido del cero literal, seguido de un paréntesis. Aquí violamos la especificación y de hecho permitimos cualquier expresión constante de tiempo de compilación igual a cero , y no solo cero literal.

Entonces, en todos los casos, le permitimos salirse con la suya, aunque técnicamente hacerlo es ilegal.

Eric Lippert
fuente
12
@Jason: Creo que las violaciones de especificaciones en los dos primeros casos son simplemente errores que nunca se detectaron. Históricamente, el pase de enlace inicial ha sido muy agresivo en cuanto a optimizar prematuramente las expresiones, y una de las consecuencias de eso es que los paréntesis se descartan muy pronto, antes de lo que deberían. En casi todos los casos, todo esto hace que los programas que son intuitivamente obvios funcionen como deberían, así que no estoy muy preocupado por eso. El análisis del tercer caso está aquí: blogs.msdn.com/ericlippert/archive/2006/03/28/…
Eric Lippert
6
En teoría, en la práctica, no es una diferencia (no estoy seguro de si Mono permite a estos 3 casos, y no sé de cualquier otro compiladores de C #, por lo que puede haber o no haber una diferencia en la práctica en la práctica). Violar la especificación de C # significa que su código no será completamente portátil. Algunos compiladores de C # pueden, a diferencia de Visual C #, no violar la especificación en esos casos particulares.
Brian
18
@Bruno: Todo lo que se necesita son unas ocho o diez mil horas de estudio de un tema determinado y usted también puede ser un experto en él. Eso es fácilmente factible en cuatro años de trabajo a tiempo completo.
Eric Lippert
32
@Anthony: Cuando hago eso, le digo a la gente que mi título es en matemáticas , no en aritmética .
Eric Lippert
7
En teoría, la práctica y la teoría son lo mismo, pero en la práctica nunca lo son.
Sayed Ibrahim Hashimi
40

Hay casos extremos en los que la presencia de paréntesis puede afectar el comportamiento del 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 nunca veas esto en la práctica.

Vladimir Reshetnikov
fuente
No es exactamente una respuesta a mi pregunta, pero sigue siendo interesante, gracias.
chris
1
¿Puedes explicar qué está pasando en 2 aquí?
Eric
2
Debería explicar por qué ocurre este comportamiento.
Arturo Torres Sánchez
26

No, no hay otra diferencia que la sintáctica.

JaredPar
fuente
3

Una buena forma de responder preguntas como esta es usar Reflector y ver qué IL se genera. Puede aprender mucho sobre las optimizaciones del compilador y demás descompilando ensamblados.

Bryan
fuente
6
Eso ciertamente respondería la pregunta para un caso específico, pero eso no sería necesariamente representativo de la totalidad de la situación.
Beska
Discrepar. Le da a la persona una dirección para responder la pregunta.
Bryan