Jaka jest różnica między ref a out w środowisku uruchomieniowym?

15

C # oferuje refi outsłów kluczowych, aby argumenty mają być przekazywane przez referencję. Semantyka tych dwóch jest bardzo podobna. Jedyną różnicą jest inicjalizacja zmiennej flaged:

  • refwymaga zainicjowania zmiennej przed przekazaniem jej do funkcji, outnie.
  • outwymaga inicjalizacji zmiennej wewnątrz funkcji, refnie wymaga.

Przypadki użycia tych dwóch słów kluczowych są również prawie takie same, a ich zbyt częste użycie uważam za zapach kodu (chociaż istnieją prawidłowe przypadki użycia, takie jak TryParsei TryGetValuewzorce).

Z tego powodu ktoś mógłby wyjaśnić, dlaczego istnieją dwa bardzo podobne narzędzia w języku C # dla tak wąskich przypadków użycia?

Ponadto w witrynie MSDN stwierdzono, że mają one różne zachowanie w czasie wykonywania:

Chociaż słowa kluczowe ref i out powodują różne zachowanie w czasie wykonywania, nie są one uważane za część podpisu metody w czasie kompilacji.

Czym różni się ich zachowanie w czasie wykonywania?

Wniosek

Obie odpowiedzi wyglądają poprawnie, dziękuję wam obojgu. Zaakceptowałem jmoreno , ponieważ jest bardziej jednoznaczne.

Gábor Angyal
źródło
Domyślam się, że oba są zaimplementowane w taki sam sposób jak refw C ++; do wskaźników do wskazanego wskaźnika obiektu (lub prymitywu). tj. Int32.TryParse(myStr, out myInt)(C #) jest „wykonywany” w taki sam sposób, jak int32_tryParse(myStr, &myInt)zrobiłby to (C); jedyną różnicą są pewne ograniczenia narzucone przez kompilator w celu zapobiegania błędom. (Nie opublikuję tego jako odpowiedzi, ponieważ mogę się mylić co do tego, jak to działa za kulisami, ale tak sobie wyobrażam, że to działa [ponieważ ma to sens])
Cole Johnson

Odpowiedzi:

10

W momencie zadawania tego pytania artykuł MSDN był subtelnie niepoprawny ( od tego czasu został poprawiony ). Zamiast „powodować inne zachowanie” powinno być „wymaga innego zachowania”.

W szczególności kompilator wymusza różne wymagania dla dwóch słów kluczowych, nawet jeśli ten sam mechanizm (IL) jest używany do włączenia tego zachowania.

jmoreno
źródło
Rozumiem więc, że kompilator sprawdza takie rzeczy, jak dereferencja outparametru lub nieprzypisanie refparametru, ale IL, którą emituje, jest tylko odniesieniem w obu przypadkach. Czy to jest poprawne?
Andrew
Btw, można by powiedzieć to z faktu, że out Ti ref Tte są wykonywane identycznie w innych językach, takich jak CLI VB.Net lub C ++ / CLI.
ach
@AndrewPiliser: poprawnie. Zakładając, że kod kompiluje się w obie strony (nie próbujesz wyłuskiwać, kiedy nie powinieneś), IL byłaby identyczna.
jmoreno
4

oto ciekawy opis na ten temat, który może odpowiedzieć na twoje pytanie:

http://www.dotnetperls.com/ref

punkt zainteresowania:

„Różnica między ref i out nie jest w środowisku uruchomieniowym Common Language, ale w samym języku C #”.

aktualizacja:

starszy programista w mojej pracy właśnie potwierdził odpowiedź @ jmoreno, więc przeczytaj ją!

Dan Beaulieu
źródło
Więc jeśli dobrze rozumiem, nie ma różnicy na poziomie IL, co oznacza, że ​​nie mogą mieć różnych zachowań w czasie wykonywania. Byłoby to sprzeczne z tym, co mówi MSDN. Mimo to interesujący artykuł!
Gábor Angyal,
@ GáborAngyal przypadkowo nie masz linku do artykułu MSDN, prawda? Przyjemnie byłoby mieć to tutaj, jeśli opinie się różnią
Dan Beaulieu
Proszę bardzo: msdn.microsoft.com/en-gb/library/t3c3bfhx.aspx , ale to było moje pytanie przez cały czas :)
Gábor Angyal
1
Co to stringma wspólnego z tym wszystkim?
Robert Harvey
To tylko parafraza z artykułu. Zaktualizowano, aby usunąć drugą linię.
Dan Beaulieu
2

Runtime ? Absolutnie żaden. Nie można przeciążać metody o tych samych parametrach, różniących się tylko słowem kluczowym ref lub out.

Spróbuj to skompilować, a pojawi się błąd kompilacji „Metoda z tą samą sygnaturą jest już zadeklarowana”:

    private class MyClass
    {
        private void DoSomething(out int param)
        {
        }
        private void DoSomething(ref int param)
        {
        }
    }

Aby odpowiedzieć na to pytanie: „... dlaczego są dwa bardzo podobne narzędzia w C # dla tak wąskich przypadków użycia?”

Z punktu widzenia czytelności kodu i API istnieje ogromna różnica. Jako konsument interfejsu API wiem, że kiedy używane jest „wyjście”, API nie zależy od parametru wyjścia. Jako programista API wolę „out” i używam „ref” tylko wtedy, gdy jest to absolutnie (RZADKO!) Konieczne. Świetna dyskusja znajduje się w tym dokumencie:

/programming/1516876/when-to-use-ref-vs-out

Informacje dodatkowe: skompilowałem następującą metodę i ją zdemontowałem. Użyłem słów kluczowych ref i out (out w tym przykładzie), ale kod zestawu nie zmienił się, z wyjątkiem odwołania do adresu, jak się spodziewałbym:

    private class MyClass
    {
        internal void DoSomething(out int param)
        {
            param = 0;
        }
    }

00000000 push ebp
00000001 mov ebp, esp
00000003 push edi
00000004 push esi
00000005 push ebx
00000006 sub esp, 34h
00000009 xor eax, eax
0000000b mov dword ptr [ebp-10h], eax
0000000e mov dword ptr [ebp-
00C] mov dword ptr [ebp-3Ch], ecx
00000014 mov dword ptr [ebp-40h], edx
00000017 cmp dword ptr ds: [008B1710h], 0
0000001e je 00000025
00000020 połączenie 6E6B601E
00000025 nop
param = 0;
00000026 mov eax, dword ptr [ebp-40h]
00000029 xor edx, edx
0000002b mov dword ptr [eax], edx
}
0000002d nop
0000002e lea esp, [ebp-0Ch]
00000031 pop ebx
00000032 pop esi
00000033 pop edi
00000034 pop ebp
00000035 ret

Czy poprawnie czytam zestaw?

Shmoken
źródło
To, co piszesz, jest poprawne, ale pamiętaj, że nie jest to oczywista implikacja, że ​​nie ma różnicy w czasie wykonywania, ponieważ to przeciążenie jest niedozwolone.
Gábor Angyal
Ahh - dobra uwaga! Czy ktoś może zdemontować powyższy kod i udowodnić, że mam rację? Okropnie czytam zgromadzenia, ale ciekawi mnie to.
Shmoken
Okej - zdemontowałem kod używając „out” i „ref” i nie widzę żadnej różnicy w mojej metodzie DoSomething:
Shmoken