Що краще, параметр повернення чи вихід?


147

Якщо ми хочемо отримати значення з методу, ми можемо використовувати або повернене значення, наприклад, таке:

public int GetValue(); 

або:

public void GetValue(out int x);

Я не дуже розумію відмінностей між ними, і так, не знаю, що краще. Ви можете мені це пояснити?

Дякую.


3
Мені б хотілося, щоб у C # було кілька повернених значень, наприклад, як Python.
Пастка

12
@Trap Ви можете повернути a, Tupleякщо хочете, але загальний консенсус полягає в тому, що якщо вам потрібно повернути більше ніж одне, речі зазвичай якимось чином пов’язані між собою, і це відношення зазвичай найкраще виражається як клас.
Фарап

2
@Pharap Tuples в C # в його нинішньому вигляді просто некрасивий, але це лише моя думка. З іншого боку, "загальний консенсус" нічого не означає з точки зору зручності використання та продуктивності. Ви б не створили клас для повернення декількох значень з тієї ж причини, що ви не створили б клас, щоб повернути пару значень як параметр ref / out.
Пастка

@Trap return Tuple.Create(x, y, z);Хіба це не потворно. Крім того, пізно вдень їх запровадити на мовному рівні. Причиною того, що я не створив би клас для повернення значень з параметра ref / out, є те, що парами ref / out мають справжній сенс для великих змінних структур (наприклад, матриць) або необов'язкових значень, а останнє є дискусійним.
Фарап

Команда @Pharap C # активно прагне представити кортежі на мовному рівні. Незважаючи на те, що зараз вітається ціла кількість варіантів у .NET переважній зараз - анонімний тип, .NET Tuple<>та C # кортежі !. Я просто побажав, щоб C # дозволив повернути анонімні типи з методів із компіляційним типом (наприклад, autoу Dlang).
nawfal

Відповіді:


153

Значення повернення майже завжди є правильним вибором, коли методу більше нічого не повертати. (Насправді я не можу придумати жодних випадків, коли я хотів би колись захотіти недійсний метод з outпараметром, якби мав вибір. DeconstructМетоди C # 7 для деконструкції, що підтримується мовою, виступають як дуже-дуже рідкісне виняток із цього правила .)

Крім усього іншого, це не дозволяє абоненту оголошувати змінну окремо:

int foo;
GetValue(out foo);

проти

int foo = GetValue();

Вихідні значення також запобігають ланцюжком методу, як це:

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

(Дійсно, це також одна з проблем із налаштуваннями властивостей, і саме тому шаблон конструктора використовує методи, які повертають будівельника, наприклад myStringBuilder.Append(xxx).Append(yyy).)

Крім того, параметри з відбиттям трохи складніше використовувати і зазвичай також роблять складніше тестування. (Зазвичай докладається більше зусиль для полегшення макетування повернених значень, ніж параметрів). По суті, я нічого не можу придумати, щоб вони легше ...

Повернення значень FTW.

EDIT: Що стосується того, що відбувається ...

В основному, коли ви передаєте аргумент для параметра "out", ви повинні передавати змінну. (Елементи масиву класифікуються також як змінні.) Метод, який ви викликаєте, не містить "нової" змінної на своєму стеку для параметра - він використовує вашу змінну для зберігання. Будь-які зміни змінної відразу видно. Ось приклад, що показує різницю:

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): ");
    }
}

Результати:

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

Різниця полягає в кроці "пост" - тобто після зміни локальної змінної чи параметра. У тесті ReturnValue це не має значення для статичної valueзмінної. У тесті OutParameter valueзмінна змінюється лінієюtmp = 10;


2
Ви змусили мене повірити, що повернення вартості набагато краще :). Але мені все одно цікаво, що відбувається "в глибину". Я маю на увазі, значення повернення та параметр out, чи відрізняються вони тим, як створюються, призначаються та повертаються?
Quan Mai

1
TryParse - найкращий приклад, коли використання парами є відповідним та чистим. Хоча я використовував це в особливих випадках, наприклад, якщо if (WorkSucceeded (з списку <string> помилки)), який в основному є тим же шаблоном, що і TryParse
Чад Грант

2
Бідна - непомітна відповідь. Як пояснено в коментарях до цього питання , параметри мають декілька корисних переваг. Перевага між «поза» та «повернення» повинна залежати від ситуації.
aaronsnoswell

2
@aaronsnoswell: Які точні коментарі? Майте на увазі, що приклад Dictionary.TryGetValueтут не застосовується, оскільки це недійсний метод. Чи можете ви пояснити, чому ви хочете outпараметр замість повернутого значення? (Навіть для того TryGetValue, я б більше віддав перевагу поверненому значенню, яке містить всю вихідну інформацію. Особисто див. NodaTime ParseResult<T>для прикладу того, як я його сконструював.)
Джон Скіт

2
@Jim: Я думаю, нам доведеться погодитися не погодитися. У той час як я бачу свою точку зору про ковких людей по голові з цим, він також робить його менш дружнім для тих , хто дійсно знає , що вони роблять. Приємне, що стосується винятку, а не значення, що повертається, це те, що ви не можете легко проігнорувати його та продовжувати так, як ніби нічого не сталося ... тоді як і зі значенням повернення, і з outпараметром, ви просто не можете нічого робити зі значенням.
Джон Скіт

26

Що краще, залежить від вашої конкретної ситуації. Однією з причин outє полегшення повернення кількох значень з одного виклику методу:

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

    return input;
}

Тож одне за визначенням не краще, ніж інше. Але зазвичай ви хочете скористатися простим поверненням, якщо, наприклад, у вас немає вищевказаної ситуації.

EDIT: Цей зразок демонструє одну з причин існування ключового слова. Сказане ні в якому разі не вважається найкращою практикою.


2
Я не згоден з цим, чому б ти не повернув структуру даних з 4-ма входами? Це справді заплутано.
Чад Грант

1
Очевидно, що існує більше (і кращих) способів повернути кілька значень, я просто даю ОП причину, чому існує в першу чергу.
пірокумул

2
Я згоден з @Cloud. Тільки тому, що це не найкращий спосіб, не означає, що він не повинен існувати.
Церебр

Код навіть не буде компілюватися для одного ... і, принаймні, слід згадати, що це вважається поганою практикою / не бажаною.
Чад Грант

1
Він не компілюється? І чому це? Цей зразок може скласти просто ідеально. І, будь ласка, я навожу вибірку того, чому існує певний синтаксис / ключове слово, а не урок найкращих практик.
пірокумул

23

Ви, як правило, віддаєте перевагу зворотному значенню над вихідним параметром. Парами є необхідним злом, якщо ти описуєшся кодом, який повинен робити 2 речі. Хорошим прикладом цього є модель "Спробуйте" (наприклад, Int32.TryParse).

Давайте розглянемо, що повинен зробити абонент ваших двох методів. Для першого прикладу я можу написати це ...

int foo = GetValue();

Зауважте, що я можу оголосити змінну та призначити її за допомогою вашого методу в одному рядку. З другого прикладу це виглядає приблизно так ...

int foo;
GetValue(out foo);

Зараз я змушений оголосити свою змінну вперед і написати свій код у двох рядках.

оновлення

Хороше місце для запитання таких типів питань - .NET Framework Design Design Guidelines. Якщо у вас є версія книги, ви можете побачити анотації Андерса Хейльсберга та інших осіб на цю тему (стор. 184-185), але онлайн-версія тут ...

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

Якщо вам потрібно повернути дві речі з API, то загортання їх у структуру / клас було б краще, ніж параметр.


Чудова відповідь, особливо посилання на TryParse, (загальну) функцію, яка змушує розробників використовувати (нечасті) змінні.
Церебр

12

Є одна причина використання outпарами, про яку вже не було сказано: метод виклику зобов'язаний отримати його. Якщо ваш метод створює значення, яке абонент не повинен відкидати, outзмушує абонента спеціально прийняти його:

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

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

Звичайно, виклик все ще може ігнорувати значення в outпараметрі, але ви звернули на це їхню увагу.

Це рідкісна потреба; частіше слід використовувати виняток для справжньої проблеми або повертати об’єкт із інформацією про стан для "FYI", але можуть бути обставини, коли це важливо.


8

Це переважно перевагу

Я віддаю перевагу поверненням, і якщо у вас є кілька повернень, ви можете загорнути їх у DTO Result

public class Result{
  public Person Person {get;set;}
  public int Sum {get;set;}
}

5

Ви можете мати лише одне повернене значення, тоді як ви можете мати декілька параметрів.

Вам потрібно розглянути параметри лише в тих випадках.

Однак, якщо вам потрібно повернути більше одного параметра зі свого методу, ви, ймовірно, хочете подивитися, що ви повертаєтесь із підходу до ОО і подумайте, чи краще вам повернути об’єкт чи структуру з цими параметрами. Тому ви знову повертаєтесь до значення повернення.


5

Ви майже завжди повинні використовувати зворотні значення. ' out' параметри створюють трохи тертя для багатьох API, композиційність тощо.

Найбільш примітним винятком, яке спадає на думку, є те, коли ви хочете повернути кілька значень (.Net Framework не має кортежів до 4.0), як, наприклад, з TryParseмалюнком.


Не впевнений, чи це хороша практика, але Arraylist також може бути використаний для повернення кількох значень.
BA

@BA ні, це не є хорошою практикою, інші користувачі не знають, що містить цей Arraylist або в якому становищі вони перебувають. Наприклад, я хочу повернути кількість прочитаних байтів, а також значення. або кортежі, які названі, були б набагато кращими. ArrayList або будь-який інший список буде корисним лише у тому випадку, якщо ви хочете повернути сукупність речей, наприклад, список осіб.
sLw

2

Я б віддав перевагу наступному замість будь-якого з цього простого прикладу.

public int Value
{
    get;
    private set;
}

Але, вони всі дуже однакові. Зазвичай можна використовувати "out" лише тоді, коли їм потрібно передати кілька методів назад із методу. Якщо ви хочете надсилати значення методу і виходити з нього, вибираєте "ref". Мій метод найкращий, якщо ви тільки повертаєте значення, але якщо ви хочете передати параметр і отримати значення назад, швидше за все, ви вибрали б ваш перший вибір.


2

Я думаю, що одним із небагатьох сценаріїв, коли це було б корисно, було б під час роботи з некерованою пам'яттю, і ви хочете зробити очевидним, що «повернене» значення слід утилізувати вручну, а не очікувати, що воно буде видалено самостійно .


2

Крім того, повернені значення сумісні з асинхронними парадигмами дизайну.

Ви не можете призначити функцію "async", якщо вона використовує параметри ref або out.

Підводячи підсумок, Значення повернення дозволяють ланцюжок методів, чистіший синтаксис (усуваючи необхідність заявника оголосити додаткові змінні), а також дозволяють створювати асинхронні конструкції без необхідності істотних змін у майбутньому.


Відмінна точка щодо позначення "асинхроніка". Думав, що хтось інший би це згадав. Ланцюжок - а також використання повернутого значення (справді самої функції) як вираження - ще одна ключова користь. Це дійсно різниця між розглядом лише того, як просто повернути речі з процесу ("відро" - обговорення Tuple / class / structure) і трактувати сам процес як вираз, який може бути замінений на одне значення (корисність сама функція, оскільки вона повертає лише 1 значення).
користувач1172173

1

Вони мають різну мету і компілятор не трактується однаково. Якщо вашому методу потрібно повернути значення, тоді ви повинні використовувати return. Out використовується там, де вашому методу потрібно повернути кілька значень.

Якщо ви використовуєте return, дані спочатку записуються у стек методів, а потім у метод виклику. Якщо у випадку виходу, він записується безпосередньо в стек методів виклику. Не впевнений, чи є ще якісь відмінності.


Методи укладаються? Я не експерт c #, але x86 підтримує лише один стек на потік. "Кадр" методу розміщується під час повернення, і якщо контекстний комутатор трапляється, тоді вичерпний стек може бути перезаписаний. У c всі повернені значення надходять у реєстр eax. Якщо ви хочете повертати об'єкти / структури, їх потрібно виділити в купу і вказівник буде поставлений в eax.
Стефан Лундстрем

1

Як говорили інші: повертайте значення, а не парам.

Чи можу я рекомендувати вам книгу "Настанови рамкового дизайну" (2-е видання)? Сторінки 184-185 висвітлюють причини уникнення парами. Вся книга спрямовує вас у правильному напрямку щодо всіляких проблем кодування .NET.

Поєднане з Рамковими рекомендаціями щодо дизайну - це використання інструменту статичного аналізу FxCop. Ви знайдете це на сайтах Microsoft як безкоштовне завантаження. Запустіть це на своєму складеному коді і подивіться, що він говорить. Якщо вона скаржиться на сотні і сотні речей ... не панікуйте! Дивіться спокійно і уважно на те, що йдеться про кожен конкретний випадок. Не поспішайте якнайшвидше виправляти речі. Дізнайтеся, що це вам говорить. Вас поставлять на шлях майстерності.


1

Використання ключового слова із типом повернення bool, іноді може зменшити розширення коду та збільшити читабельність. (В першу чергу, коли зайву інформацію в параметрі часто ігнорують.) Наприклад:

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

1

Реальної різниці немає. Вихідні параметри знаходяться в C #, щоб метод міг повернути більше одного значення, ось і все.

Однак є деякі незначні відмінності, але не з них дійсно важливі:

Використання параметра змусить використовувати два рядки типу:

int n;
GetValue(n);

при використанні значення повернення дозволить зробити це в одному рядку:

int n = GetValue();

Ще одна відмінність (правильна лише для типів значень і лише у тому випадку, якщо функція C # не вбудовує функцію) полягає в тому, що використання поверненого значення обов’язково зробить копію цього значення, коли функція повернеться, при використанні параметра OUT не обов'язково робити це.


0

out корисніше, коли ви намагаєтесь повернути об'єкт, який ви заявляєте в методі.

Приклад

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

0

return value - це нормальне значення, яке повертається вашим методом.

Якщо параметр out , well and ref - це два ключові слова C #, вони дозволяють передавати змінні як посилання .

Велика різниця між перемиканням та відхиленням полягає в тому, що посилання повинно бути ініціалізовано до того , як не


-2

Я підозрюю, що не збираюся розбиратися в цьому питанні, але я дуже досвідчений програміст, і сподіваюся, що хтось із більш відкритих читачів зверне увагу.

Я вважаю, що це відповідає об'єктно-орієнтованим мовам програмування, щоб їх процедури повернення цінностей (VRP) були детермінованими та чистими.

'VRP' - сучасна академічна назва для функції, яка називається частиною виразу, і має зворотне значення, яке умовно замінює виклик під час оцінки виразу. Наприклад, в операторі, такому як x = 1 + f(y)функція, fвиконується функція VRP.

"Детермінований" означає, що результат функції залежить лише від значень його параметрів. Якщо ви знову зателефонуєте з тими ж значеннями параметрів, ви впевнені, що отримаєте той самий результат.

"Чистий" означає відсутність побічних ефектів: виклик функції не робить нічого, крім обчислення результату. Це може бути витлумачено на увазі відсутністю важливих побічних ефектів на практиці, тому якщо VRP видає повідомлення про налагодження кожного разу, коли воно викликається, наприклад, це, ймовірно, може бути проігноровано.

Таким чином, якщо у C # ваша функція не є детермінованою та чистою, я кажу, що ви повинні зробити її voidфункцією (іншими словами, не VRP), і будь-яке значення, яке потрібно повернути, повинно бути повернене або в, outабо в refпараметрі.

Наприклад, якщо у вас є функція видалити деякі рядки з таблиці бази даних, і ви хочете, щоб вона повернула кількість рядків, які вона видалила, слід оголосити це приблизно так:

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

Якщо ви хочете іноді викликати цю функцію, але не отримуєте count, ви завжди можете оголосити про перевантаження.

Можливо, ви хочете знати, чому цей стиль краще підходить об'єктно-орієнтованому програмуванню. Загалом, він вписується в стиль програмування, який можна (трохи неточно) назвати "процедурне програмування", і це стиль процедурного програмування, який краще відповідає об'єктно-орієнтованому програмуванню.

Чому? Класична модель об'єктів полягає в тому, що вони мають властивості (ака атрибути), і ви допитуєтесь та маніпулюєте об'єктом (головним чином) шляхом читання та оновлення цих властивостей. Процедурний стиль програмування, як правило, полегшує це, оскільки ви можете виконувати довільний код між операціями, які отримують і встановлюють властивості.

Мінус процедурного програмування полягає в тому, що, оскільки ви можете виконувати довільний код всюди, ви можете отримати дуже тупі та вразливі до помилок взаємодії за допомогою глобальних змінних та побічних ефектів.

Так, досить просто, це хороша практика , щоб сигнал кому - то читає код , що функція може мати побічні ефекти, що робить його не-значення повертається.


> Якщо ви хочете іноді викликати цю функцію, але не отримуєте підрахунок, ви завжди можете оголосити про перевантаження. У C # версії 7 (я думаю) та пізніших версіях ви можете використовувати _символ відкидання, щоб ігнорувати параметр «out», наприклад: DeleteBasketItems (категорія, вихід _);
дебат
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.