Czy istnieje różnica między return myVar a return (myVar)?

87

Patrzyłem na przykładowy kod C # i zauważyłem, że jeden przykład zawinął zwrot w ().

Zawsze robiłem:

return myRV;

Czy jest różnica w wykonywaniu:

return (myRV);
chris
źródło

Odpowiedzi:

229

AKTUALIZACJA: To pytanie było tematem mojego bloga 12 kwietnia 2010 roku . Dzięki za zabawne pytanie!

W praktyce nie ma różnicy.

W teorii może istnieć różnica. W specyfikacji C # są trzy interesujące punkty, w których może to stanowić różnicę.

Po pierwsze, konwersja funkcji anonimowych na typy delegowane i drzewa wyrażeń. Rozważ następujące:

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

F1jest wyraźnie legalne. Jest F2? Technicznie nie. Specyfikacja mówi w sekcji 6.5, że następuje konwersja wyrażenia lambda na zgodny typ delegata. Czy to wyrażenie lambda ? Nie. To wyrażenie w nawiasach zawierające wyrażenie lambda .

Kompilator Visual C # dokonuje tutaj niewielkiego naruszenia specyfikacji i odrzuca za Ciebie nawias.

Druga:

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

F3jest legalne. Jest F4? Nie. Sekcja 7.5.3 stwierdza, że ​​wyrażenie w nawiasach nie może zawierać grupy metod. Ponownie, dla Twojej wygody naruszamy specyfikację i zezwalamy na konwersję.

Trzeci:

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

F5jest legalne. Jest F6? Nie. Specyfikacja stwierdza, że ​​następuje konwersja z literalnego zera do dowolnego typu wyliczeniowego. „ (0)” nie jest dosłownym zerem, jest to nawias, po którym następuje dosłowne zero, po którym następuje nawias. Naruszamy tutaj specyfikację i faktycznie zezwalamy na dowolne wyrażenie stałej czasu kompilacji równe zero , a nie tylko dosłowne zero.

Dlatego w każdym przypadku pozwalamy ci ujść na sucho, nawet jeśli technicznie jest to nielegalne.

Eric Lippert
źródło
12
@Jason: Uważam, że naruszenia specyfikacji w pierwszych dwóch przypadkach to po prostu błędy, których nigdy nie wykryto. Historycznie, początkowe przejście wiążące było bardzo agresywne, jeśli chodzi o przedwczesną optymalizację wyrażeń, a jedną z konsekwencji tego jest to, że nawiasy są wyrzucane bardzo wcześnie, wcześniej niż powinno. W prawie każdym przypadku wszystko to sprawia, że ​​programy, które są intuicyjnie oczywiste, działają tak, jak powinny, więc nie martwię się tym zbytnio. Analiza trzeciego przypadku jest tutaj: blogs.msdn.com/ericlippert/archive/2006/03/28/…
Eric Lippert
6
W teorii, w praktyce jest różnica (nie jestem pewien, czy Mono zezwala na te 3 przypadki i nie znam żadnych innych kompilatorów C #, więc może być różnica w praktyce lub nie). Naruszenie specyfikacji języka C # oznacza, że ​​kod nie będzie w pełni przenośny. Niektóre kompilatory C # mogą, w przeciwieństwie do Visual C #, nie naruszać specyfikacji w tych szczególnych przypadkach.
Brian
18
@Bruno: Wystarczy około ośmiu lub dziesięciu tysięcy godzin nauki danego przedmiotu i Ty też możesz być w nim ekspertem. Jest to łatwe do wykonania w ciągu czterech lat pełnoetatowej pracy.
Eric Lippert,
32
@Anthony: Kiedy to robię, po prostu mówię ludziom, że mam dyplom z matematyki , a nie arytmetyki .
Eric Lippert
7
W teorii praktyka i teoria są tym samym, ale w praktyce nigdy nie są.
Ibrahim Hashimi
40

Istnieją przypadki narożne, w których obecność nawiasów może mieć wpływ na zachowanie programu:

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;
}

Mam nadzieję, że nigdy nie zobaczysz tego w praktyce.

Vladimir Reshetnikov
źródło
Niezupełnie odpowiedź na moje pytanie, ale wciąż interesująca - dzięki.
chris
1
Czy możesz wyjaśnić, co się dzieje w 2 tutaj?
Eric
2
Powinieneś szczegółowo wyjaśnić, dlaczego tak się dzieje.
Arturo Torres Sánchez
26

Nie, nie ma innej różnicy niż składnia.

JaredPar
źródło
3

Dobrym sposobem na udzielenie odpowiedzi na takie pytania jest użycie Reflectora i sprawdzenie, jakie IL jest generowane. Możesz dowiedzieć się wiele o optymalizacji kompilatora i tym podobnych, dekompilując zestawy.

Bryan
źródło
6
To z pewnością odpowiadałoby na pytanie dotyczące jednego konkretnego przypadku, ale niekoniecznie byłoby to reprezentatywne dla całej sytuacji.
Beska
Nie zgadzać się. Daje osobie wskazówki, jak odpowiedzieć na pytanie.
Bryan