Co jest lepsze, wartość zwracana czy parametr wyjściowy?

147

Jeśli chcemy uzyskać wartość z metody, możemy użyć jednej z wartości zwracanych, na przykład:

public int GetValue(); 

lub:

public void GetValue(out int x);

Naprawdę nie rozumiem różnic między nimi, więc nie wiem, co jest lepsze. Czy możesz mi to wyjaśnić?

Dziękuję Ci.

Quan Mai
źródło
3
Chciałbym, żeby C # miał na przykład wiele wartości zwracanych, takich jak Python.
Trap
12
@Trap Możesz zwrócić a, Tuplejeśli chcesz, ale ogólny konsensus jest taki, że jeśli chcesz zwrócić więcej niż jedną rzecz, rzeczy są zwykle w jakiś sposób powiązane i ta relacja jest zwykle najlepiej wyrażona jako klasa.
Pharap
2
Krotki @Pharap w C # w obecnej formie są po prostu brzydkie, ale to tylko moja opinia. Z drugiej strony „ogólny konsensus” nie oznacza nic z punktu widzenia użyteczności i produktywności. Nie utworzyłbyś klasy zwracającej kilka wartości z tego samego powodu, dla którego nie utworzyłbyś klasy zwracającej kilka wartości jako parametr ref / out.
Trap
@Trap return Tuple.Create(x, y, z);Czy to nie brzydkie. Poza tym jest już późna pora na wprowadzenie ich na poziomie językowym. Powodem, dla którego nie utworzyłbym klasy zwracającej wartości z parametru ref / out, jest to, że parametry ref / out mają sens tylko w przypadku dużych, zmiennych struktur (takich jak macierze) lub wartości opcjonalnych, a te ostatnie są dyskusyjne.
Pharap
Zespół @Pharap C # aktywnie stara się wprowadzić krotki na poziomie językowym. Chociaż jest mile widziane, cała masa opcji w .NET przytłaczająca teraz - anonimowy typ, Tuple<>krotki .NET i C # !. Chciałbym tylko, żeby C # pozwalał na zwracanie anonimowych typów z metod z wnioskiem o typie kompilatora (jak autow Dlang).
nawfal

Odpowiedzi:

153

Zwracane wartości są prawie zawsze właściwym wyborem, gdy metoda nie ma nic innego do zwrócenia. (Właściwie nie przychodzą mi do głowy żadne przypadki, w których kiedykolwiek chciałbym zastosować metodę void z outparametrem, gdybym miał wybór. DeconstructMetody C # 7 do dekonstrukcji obsługiwanej przez język działają jako bardzo, bardzo rzadki wyjątek od tej reguły .)

Oprócz czegokolwiek innego, powstrzymuje wywołującego przed koniecznością oddzielnego deklarowania zmiennej:

int foo;
GetValue(out foo);

vs

int foo = GetValue();

Wartości out również zapobiegają tworzeniu łańcuchów metod w następujący sposób:

Console.WriteLine(GetValue().ToString("g"));

(Rzeczywiście, jest to również jeden z problemów związanych z ustawianiem właściwości i dlatego wzorzec konstruktora używa metod, które zwracają program budujący, np myStringBuilder.Append(xxx).Append(yyy).)

Ponadto nasze parametry są nieco trudniejsze do wykorzystania z odbiciem i zwykle utrudniają testowanie. (Zwykle wkłada się większy wysiłek w ułatwienie mockowania zwracanych wartości niż parametrów wyjściowych). W zasadzie nic nie przychodzi mi do głowy, że ułatwiają ...

Zwracane wartości FTW.

EDYCJA: Jeśli chodzi o to, co się dzieje ...

Zasadniczo kiedy przechodzą argument dla parametru „out”, to mają przekazać w zmiennej. (Elementy tablicy są również klasyfikowane jako zmienne.) Wywołana metoda nie ma „nowej” zmiennej na swoim stosie dla parametru - używa ona Twojej zmiennej do przechowywania. Wszelkie zmiany w zmiennej są natychmiast widoczne. Oto przykład pokazujący różnicę:

using System;

class Test
{
    static int value;

    static void ShowValue(string description)
    {
        Console.WriteLine(description + value);
    }

    static void Main()
    {
        Console.WriteLine("Return value test...");
        value = 5;
        value = ReturnValue();
        ShowValue("Value after ReturnValue(): ");

        value = 5;
        Console.WriteLine("Out parameter test...");
        OutParameter(out value);
        ShowValue("Value after OutParameter(): ");
    }

    static int ReturnValue()
    {
        ShowValue("ReturnValue (pre): ");
        int tmp = 10;
        ShowValue("ReturnValue (post): ");
        return tmp;
    }

    static void OutParameter(out int tmp)
    {
        ShowValue("OutParameter (pre): ");
        tmp = 10;
        ShowValue("OutParameter (post): ");
    }
}

Wyniki:

Return value test...
ReturnValue (pre): 5
ReturnValue (post): 5
Value after ReturnValue(): 10
Out parameter test...
OutParameter (pre): 5
OutParameter (post): 10
Value after OutParameter(): 10

Różnica dotyczy kroku „post” - tj. Po zmianie lokalnej zmiennej lub parametru. W teście ReturnValue nie ma to znaczenia dla valuezmiennej statycznej . W teście OutParameter valuezmienna jest zmieniana przez wiersztmp = 10;

Jon Skeet
źródło
2
Wmówiłeś mi, że wartość zwrotu jest dużo lepsza :). Ale wciąż zastanawiam się, co dzieje się „w głębi”. Mam na myśli, wartość zwracana i parametr out, czy różnią się sposobem ich tworzenia, przypisywania i zwracania?
Quan Mai
1
TryParse jest najlepszym przykładem tego, kiedy użycie parametru out jest odpowiednie i czyste. Chociaż użyłem go w specjalnych przypadkach, takich jak if (WorkSucceeded (out List <string> errors), który jest w zasadzie tym samym wzorcem co TryParse
Chad Grant
2
Słaba i nieprzydatna odpowiedź. Jak wyjaśniono w komentarzach do tego pytania , nasze parametry mają kilka użytecznych zalet. Preferencja między wyjściem a powrotem powinna zależeć od sytuacji.
aaronsnoswell
2
@aaronsnoswell: Które dokładne komentarze? Należy pamiętać, że przykład Dictionary.TryGetValuejest nie tu zastosowanie, jako że nie jest to metoda void. Czy możesz wyjaśnić, dlaczego chcesz mieć outparametr zamiast wartości zwracanej? (Nawet w przypadku TryGetValue, wolałbym wartość zwracaną, która zawiera wszystkie informacje wyjściowe, osobiście. Zobacz NodaTime, ParseResult<T>aby zobaczyć przykład, jak to zaprojektowałem.)
Jon Skeet,
2
@Jim: Myślę, że będziemy musieli się zgodzić, aby się nie zgodzić. Chociaż widzę swój punkt o wbijaniu ludzi nad głową z tym, ale również sprawia, że mniej przyjazny dla tych, którzy nie wiedzą, co robią. Zaletą wyjątku, a nie wartości zwracanej, jest to, że nie można go łatwo zignorować i kontynuować tak, jakby nic się nie stało ... podczas gdy zarówno w przypadku wartości zwracanej, jak i outparametru, nie można po prostu nic zrobić z wartością.
Jon Skeet,
26

Co jest lepsze, zależy od twojej konkretnej sytuacji. Jednym z powodów outjest ułatwienie zwracania wielu wartości z jednego wywołania metody:

public int ReturnMultiple(int input, out int output1, out int output2)
{
    output1 = input + 1;
    output2 = input + 2;

    return input;
}

Więc jeden z definicji nie jest lepszy od drugiego. Ale zwykle chcesz użyć prostego zwrotu, chyba że masz na przykład powyższą sytuację.

EDYCJA: To jest przykład przedstawiający jeden z powodów, dla których istnieje słowo kluczowe. Powyższego w żaden sposób nie należy uważać za najlepszą praktykę.

pyrocumulus
źródło
2
Nie zgadzam się z tym, dlaczego nie miałbyś zwrócić struktury danych z 4 intami? To jest naprawdę zagmatwane.
Chad Grant
1
Oczywiście istnieje więcej (i lepszych) sposobów zwracania wielu wartości, po prostu podaję OP powód, dla którego out istnieje w pierwszej kolejności.
pyrocumulus
2
Zgadzam się z @Cloud. To, że nie jest to najlepszy sposób, nie oznacza, że ​​nie powinno istnieć.
Cerebrus
Cóż, kod nawet się nie skompiluje ... i powinien przynajmniej wspomnieć, że jest uważany za złą praktykę / nie preferowany.
Chad Grant
1
To się nie skompiluje? A czemu to? Ta próbka może kompilować się idealnie. I proszę, podaję próbkę, dlaczego istnieje określona składnia / słowo kluczowe, a nie lekcję na temat najlepszych praktyk.
pyrocumulus
23

Generalnie powinieneś preferować wartość zwracaną zamiast parametru out. Nasze parametry są złem koniecznym, jeśli piszesz kod, który musi robić 2 rzeczy. Dobrym przykładem jest wzorzec Try (taki jak Int32.TryParse).

Zastanówmy się, co musiałby zrobić wywołujący z twoich dwóch metod. Dla pierwszego przykładu mogę to napisać ...

int foo = GetValue();

Zauważ, że mogę zadeklarować zmienną i przypisać ją za pomocą twojej metody w jednym wierszu. W drugim przykładzie wygląda to tak ...

int foo;
GetValue(out foo);

Jestem teraz zmuszony do zadeklarowania mojej zmiennej z góry i napisania kodu w dwóch wierszach.

aktualizacja

Dobrym miejscem do zadawania tego typu pytań są wytyczne dotyczące projektowania .NET Framework. Jeśli masz wersję książkową, możesz zobaczyć adnotacje Andersa Hejlsberga i innych na ten temat (strony 184-185), ale wersja online jest tutaj ...

http://msdn.microsoft.com/en-us/library/ms182131(VS.80).aspx

Jeśli okaże się, że musisz zwrócić dwie rzeczy z interfejsu API, zawinięcie ich w strukturę / klasę byłoby lepsze niż parametr wyjściowy.

Martin Peck
źródło
Świetna odpowiedź, zwłaszcza odniesienie do TryParse, (powszechnej) funkcji, która zmusza programistów do używania (nietypowych) zmiennych wyjściowych.
Cerebrus
12

Jest jeden powód, aby użyć outparametru, o którym jeszcze nie wspomniano: metoda wywołująca musi go odebrać. Jeśli twoja metoda daje wartość, której obiekt wywołujący nie powinien odrzucić, zmuszając go outdo jej akceptacji:

 Method1();  // Return values can be discard quite easily, even accidentally

 int  resultCode;
 Method2(out resultCode);  // Out params are a little harder to ignore

Oczywiście rozmówca może nadal ignorować wartości w outparam, ale pan nazywa swoje uwagi.

To rzadka potrzeba; częściej należy użyć wyjątku dla prawdziwego problemu lub zwrócić obiekt z informacją o stanie dla „FYI”, ale mogą zaistnieć okoliczności, w których jest to ważne.


źródło
8

To głównie preferencje

Wolę zwroty, a jeśli masz wiele zwrotów, możesz je opakować w wynikowe DTO

public class Result{
  public Person Person {get;set;}
  public int Sum {get;set;}
}
Scott Cowan
źródło
5

Możesz mieć tylko jedną wartość zwracaną, podczas gdy możesz mieć wiele parametrów wyjściowych.

W takich przypadkach wystarczy wziąć pod uwagę parametry.

Jeśli jednak chcesz zwrócić więcej niż jeden parametr ze swojej metody, prawdopodobnie zechcesz przyjrzeć się temu, co zwracasz z podejścia obiektowego i rozważyć, czy lepiej jest zwrócić obiekt lub strukturę z tymi parametrami. Dlatego znowu wracasz do wartości zwracanej.

Robin Day
źródło
5

Prawie zawsze powinieneś używać wartości zwracanej. ' out' powodują trochę tarcia w wielu API, kompozycjach itp.

Najbardziej godnym uwagi wyjątkiem, który przychodzi na myśl, jest sytuacja, gdy chcesz zwrócić wiele wartości (.Net Framework nie ma krotek do 4.0), na przykład ze TryParsewzorcem.

Brian
źródło
Nie jestem pewien, czy to dobra praktyka, ale Arraylist może również służyć do zwracania wielu wartości.
BA
@BA nie, to nie jest dobra praktyka, inni użytkownicy nie wiedzieliby, co zawiera ta Arraylist ani na jakiej pozycji się znajdują. Na przykład chcę zwrócić ilość odczytanych bajtów, a także wartość. out lub krotki, które są nazwane, byłyby znacznie lepsze. ArrayList lub inna lista byłaby przydatna tylko wtedy, gdy chcesz zwrócić zbiór rzeczy, na przykład listę osób.
sLw
2

Wolałbym następujące zamiast któregokolwiek z tych w tym prostym przykładzie.

public int Value
{
    get;
    private set;
}

Ale wszystkie są bardzo podobne. Zwykle używa się „out” tylko wtedy, gdy trzeba przekazać wiele wartości z powrotem z metody. Jeśli chcesz wysłać wartość do iz metody, należy wybrać „ref”. Moja metoda jest najlepsza, jeśli zwracasz tylko wartość, ale jeśli chcesz przekazać parametr i odzyskać wartość, prawdopodobnie wybierzesz pierwszy wybór.

Kenny
źródło
2

Myślę, że jednym z nielicznych scenariuszy, w których przydałoby się to podczas pracy z pamięcią niezarządzaną, i chcesz, aby było oczywiste, że „zwrócona” wartość powinna zostać usunięta ręcznie, zamiast oczekiwać, że zostanie ona usunięta samodzielnie .

Xian
źródło
2

Ponadto wartości zwracane są zgodne z asynchronicznymi paradygmatami projektowania.

Nie można oznaczyć funkcji „asynchronicznej”, jeśli używa ona parametrów ref lub out.

Podsumowując, wartości zwracane pozwalają na tworzenie łańcuchów metod, czystszą składnię (eliminując konieczność deklarowania przez obiekt wywołujący dodatkowych zmiennych) i pozwalają na asynchroniczne projekty bez konieczności wprowadzania znacznych modyfikacji w przyszłości.

firetiger77
źródło
Świetna uwaga dotycząca oznaczenia „async”. Pomyślałem, że ktoś inny by o tym wspomniał. Tworzenie łańcuchów - a także używanie wartości zwracanej (a właściwie samej funkcji) jako wyrażenia to kolejna zaleta. To naprawdę różnica między rozważeniem tylko tego, jak po prostu odzyskać rzeczy z procesu („wiadro” - dyskusja o krotkach / klasie / strukturze) a traktowaniem samego procesu jako wyrażenia, które można zastąpić pojedynczą wartością (użyteczność funkcji funkcji, ponieważ zwraca tylko 1 wartość).
user1172173
1

Oba mają inny cel i nie są traktowane tak samo przez kompilator. Jeśli twoja metoda musi zwrócić wartość, musisz użyć return. Out jest używane, gdy metoda musi zwrócić wiele wartości.

Jeśli używasz return, dane są najpierw zapisywane na stosie metod, a następnie w metodzie wywołującej. W przypadku out, jest on bezpośrednio zapisywany na stosie metod wywołujących. Nie jestem pewien, czy jest więcej różnic.

duński
źródło
Stosuje się metody? Nie jestem ekspertem od języka C #, ale x86 obsługuje tylko jeden stos na wątek. Ramka metody jest zwalniana podczas powrotu, a jeśli nastąpi przełączenie kontekstu, zwolniony stos może zostać nadpisany. W c wszystkie zwracane wartości trafiają do rejestru eax. Jeśli chcesz zwracać obiekty / struktury, muszą one zostać przydzielone do sterty, a wskaźnik zostanie umieszczony w eax.
Stefan Lundström
1

Jak powiedzieli inni: zwracana wartość, a nie out param.

Czy mogę polecić książkę „Wytyczne dotyczące projektowania ram” (wyd. 2)? Na stronach 184-185 opisano powody unikania parametrów. Cała książka poprowadzi Cię we właściwym kierunku we wszelkich kwestiach związanych z kodowaniem .NET.

Sprzymierzeńcem z wytycznymi dotyczącymi projektowania ram jest użycie narzędzia do analizy statycznej FxCop. Znajdziesz to w witrynach firmy Microsoft do bezpłatnego pobrania. Uruchom to na skompilowanym kodzie i zobacz, co mówi. Jeśli narzeka na setki rzeczy ... nie panikuj! Spójrz spokojnie i uważnie, co mówi o każdym przypadku. Nie spiesz się, aby naprawić rzeczy jak najszybciej. Dowiedz się, co ci mówi. Zostaniesz skierowany na drogę do mistrzostwa.

Andrew Webb
źródło
1

Używanie słowa kluczowego out ze zwracanym typem bool może czasami zmniejszyć rozdęcie kodu i zwiększyć czytelność. (Przede wszystkim, gdy dodatkowe informacje w parametrze out są często ignorowane). Na przykład:

var result = DoThing();
if (result.Success)
{
    result = DoOtherThing()
    if (result.Success)
    {
        result = DoFinalThing()
        if (result.Success)
        {
            success = true;
        }
    }
}

vs:

var result;
if (DoThing(out result))
{
    if (DoOtherThing(out result))
    {
        if (DoFinalThing(out result))
        {
            success = true;
        }
    }
}
Paul Smith
źródło
1

Nie ma żadnej różnicy. Parametry wyjściowe są w języku C #, aby umożliwić zwracanie przez metodę więcej niż jednej wartości, to wszystko.

Istnieją jednak drobne różnice, ale żadna z nich nie jest naprawdę ważna:

Użycie parametru out wymusi użycie dwóch linii, takich jak:

int n;
GetValue(n);

natomiast użycie wartości zwracanej pozwoli Ci to zrobić w jednej linii:

int n = GetValue();

Inną różnicą (poprawną tylko dla typów wartości i tylko wtedy, gdy C # nie wbudowuje funkcji) jest to, że użycie wartości zwracanej koniecznie spowoduje utworzenie kopii wartości, gdy funkcja powróci, podczas gdy użycie parametru OUT niekoniecznie będzie to robić.

user88637
źródło
0

out jest bardziej przydatne, gdy próbujesz zwrócić obiekt zadeklarowany w metodzie.

Przykład

public BookList Find(string key)
{
   BookList book; //BookList is a model class
   _books.TryGetValue(key, out book) //_books is a concurrent dictionary
                                     //TryGetValue gets an item with matching key and returns it into book.
   return book;
}
Taera Kwon
źródło
0

zwracana wartość to normalna wartość zwracana przez Twoją metodę.

Gdzie jako parametr out , well out i ref są 2 słowami kluczowymi języka C #, pozwalają one na przekazywanie zmiennych jako referencji .

Duża różnica między ref i out polega na tym, że ref należy zainicjalizować przed, a out nie

bizimunda
źródło
-2

Podejrzewam, że nie zajrzę do tego pytania, ale jestem bardzo doświadczonym programistą i mam nadzieję, że niektórzy czytelnicy o bardziej otwartych umysłach zwrócą na to uwagę.

Uważam, że lepiej pasuje do języków programowania zorientowanych obiektowo, ponieważ ich procedury zwracania wartości (VRP) są deterministyczne i czyste.

„VRP” to nowoczesna nazwa akademicka funkcji, która jest wywoływana jako część wyrażenia i ma wartość zwracaną, która teoretycznie zastępuje wywołanie podczas oceny wyrażenia. Np. W instrukcji takiej jak x = 1 + f(y)funkcja fsłuży jako VRP.

„Deterministyczna” oznacza, że ​​wynik funkcji zależy tylko od wartości jej parametrów. Jeśli wywołasz go ponownie z tymi samymi wartościami parametrów, na pewno uzyskasz ten sam wynik.

„Czysty” oznacza brak skutków ubocznych: wywołanie funkcji nie robi nic poza obliczeniem wyniku. W praktyce można to interpretować jako brak istotnych skutków ubocznych, więc jeśli VRP wysyła komunikat debugowania za każdym razem, gdy jest wywoływany, prawdopodobnie można to zignorować.

Tak więc, jeśli w C # twoja funkcja nie jest deterministyczna i czysta, mówię, że powinieneś uczynić ją voidfunkcją (innymi słowy, nie VRP), a każda wartość, którą musi zwrócić, powinna zostać zwrócona w parametrze outlub refparametrze.

Na przykład, jeśli masz funkcję usuwania niektórych wierszy z tabeli bazy danych i chcesz, aby zwracała liczbę usuniętych wierszy, powinieneś zadeklarować ją mniej więcej tak:

public void DeleteBasketItems(BasketItemCategory category, out int count);

Jeśli czasami chcesz wywołać tę funkcję, ale nie możesz jej uzyskać count, zawsze możesz zadeklarować przeciążenie.

Możesz chcieć wiedzieć, dlaczego ten styl lepiej pasuje do programowania obiektowego. Ogólnie rzecz biorąc, pasuje do stylu programowania, który można (trochę nieprecyzyjnie) nazwać „programowaniem proceduralnym” i jest to styl programowania proceduralnego, który lepiej pasuje do programowania obiektowego.

Czemu? Klasyczny model obiektów polega na tym, że mają one właściwości (czyli atrybuty), a Ty odpytujesz i manipulujesz obiektem (głównie) poprzez czytanie i aktualizowanie tych właściwości. Proceduralny styl programowania zwykle ułatwia to, ponieważ między operacjami pobierającymi i ustawiającymi właściwości można wykonać dowolny kod.

Wadą programowania proceduralnego jest to, że ponieważ możesz wykonać dowolny kod w dowolnym miejscu, możesz uzyskać bardzo tępe i podatne na błędy interakcje za pośrednictwem zmiennych globalnych i efektów ubocznych.

Tak więc po prostu dobrą praktyką jest zasygnalizowanie komuś czytającemu kod, że funkcja może mieć skutki uboczne, sprawiając, że nie zwraca wartości.

dyskutant
źródło
> Jeśli czasami chcesz wywołać tę funkcję, ale nie otrzymujesz liczby, zawsze możesz zadeklarować przeciążenie. W C # wersji 7 (chyba) i nowszych można użyć _symbolu odrzucenia, aby zignorować parametr wyjściowy, np .: DeleteBasketItems (category, out _);
debatant