Что лучше, возвращаемое значение или выходной параметр?

147

Если мы хотим получить значение из метода, мы можем использовать любое возвращаемое значение, например так:

public int GetValue(); 

или:

public void GetValue(out int x);

Я не очень понимаю разницу между ними, и поэтому не знаю, что лучше. Можете ли вы объяснить мне это?

Спасибо.

Куан Май
источник
3
Мне бы хотелось, чтобы в C # было несколько возвращаемых значений, например, в Python.
Ловушка
12
@Trap Вы можете вернуть a, Tupleесли хотите, но общий консенсус заключается в том, что если вам нужно вернуть более одной вещи, вещи обычно как-то связаны, и это отношение обычно лучше всего выражать в виде класса.
Pharap
2
@Pharap Tuples в C # в его нынешнем виде просто безобразны, но это только мое мнение. С другой стороны, «общий консенсус» ничего не значит с точки зрения удобства использования и производительности. Вы не создадите класс для возврата пары значений по той же причине, по которой вы не создадите класс, который будет возвращать пару значений в качестве параметра ref / out.
Ловушка
@Trap return Tuple.Create(x, y, z);Разве это не уродливо. Кроме того, уже поздно их вводить на уровне языка. Причина, по которой я не буду создавать класс для возврата значений из параметра ref / out, заключается в том, что параметры ref / out действительно имеют смысл только для больших изменяемых структур (например, матриц) или необязательных значений, а последний является дискуссионным.
Pharap
Команда @Pharap C # активно стремится представить кортежи на уровне языка. В то время как можно приветствовать огромное количество опций в .NET, подавляющее большинство - анонимный тип, .NET Tuple<>и C # кортежи !. Я просто хотел, чтобы C # позволял возвращать анонимные типы из методов с выводимым типом компилятора (как autoв Dlang).
nawfal

Ответы:

153

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

Помимо всего прочего, он не дает вызывающей стороне объявлять переменную отдельно:

int foo;
GetValue(out foo);

против

int foo = GetValue();

Выходные значения также предотвращают цепочку методов следующим образом:

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

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

Кроме того, параметры вывода немного сложнее использовать с отражением и обычно также усложняют тестирование. (Обычно делается больше усилий для упрощения имитации возвращаемых значений, чем параметров). В принципе, я ничего не могу придумать, чтобы им было легче ...

Возвращаемые значения FTW.

РЕДАКТИРОВАТЬ: с точки зрения того, что происходит ...

В основном, когда вы передаете аргумент для параметра 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

Разница заключается в шаге «post» - т.е. после изменения локальной переменной или параметра. В тесте ReturnValue это не имеет значения для статической valueпеременной. В тесте OutParameter valueпеременная изменяется строкойtmp = 10;

Джон Скит
источник
2
Вы заставили меня поверить, что возвращаемое значение намного лучше :). Но мне все еще интересно, что происходит "в глубине". Я имею в виду, возвращаемое значение и выходной параметр, они отличаются в том, как они создаются, назначаются и возвращаются?
Quan Mai
1
TryParse - лучший пример того, когда использование выходного параметра является подходящим и чистым. Хотя я использовал его в особых случаях, таких как if (WorkSucceeded (out List <string> error)), который в основном совпадает с шаблоном TryParse
Chad Grant
2
Бедный бесполезный ответ. Как поясняется в комментариях к этому вопросу , наши параметры имеют несколько полезных преимуществ. Предпочтение между выходом и возвратом должно зависеть от ситуации.
aaronsnoswell
2
@aaronsnoswell: какие точные комментарии? Имейте в виду, что пример Dictionary.TryGetValueздесь не применим, так как это не пустой метод. Можете ли вы объяснить, почему вы хотите outпараметр вместо возвращаемого значения? (Даже для TryGetValue, я бы предпочел возвращаемое значение, которое содержит всю выходную информацию лично. См. NodaTime's 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;
}

Так что одно по определению не лучше другого. Но обычно вы хотите использовать простой возврат, если, например, у вас нет описанной выше ситуации.

РЕДАКТИРОВАТЬ: это образец, демонстрирующий одну из причин, по которой ключевое слово существует. Вышеизложенное ни в коем случае нельзя считать лучшей практикой.

pyrocumulus
источник
2
Я не согласен с этим, почему бы вам не вернуть структуру данных с 4-мя целыми числами? Это действительно сбивает с толку.
Чад Грант
1
Очевидно, есть больше (и лучше) способов вернуть несколько значений, я просто даю ОП причину, почему существует в первую очередь.
Pyrocumulus
2
Я согласен с @Cloud. Тот факт, что это не лучший способ, не означает, что он не должен существовать.
Cerebrus
Ну, код даже не скомпилируется для одного ... и он должен по крайней мере упомянуть, что это считается плохой практикой / не предпочтительным.
Чад Грант
1
Это не скомпилируется? И почему так? Этот пример может скомпилироваться просто идеально. И, пожалуйста, я привожу пример того, почему существует определенный синтаксис / ключевое слово, а не урок в передовой практике.
Pyrocumulus
23

Как правило, вы должны предпочесть возвращаемое значение перед выходным параметром. Выходные параметры - неизбежное зло, если вы обнаружите, что пишете код, который должен сделать 2 вещи. Хорошим примером этого является шаблон Try (например, Int32.TryParse).

Давайте рассмотрим, что должен делать вызывающий из ваших двух методов. Для первого примера я могу написать это ...

int foo = GetValue();

Обратите внимание, что я могу объявить переменную и назначить ее через ваш метод в одну строку. Для второго примера это выглядит так ...

int foo;
GetValue(out foo);

Теперь я вынужден объявить свою переменную заранее и написать код в две строки.

Обновить

При задании таких вопросов лучше всего ознакомиться с Руководством по разработке .NET Framework. Если у вас есть книжная версия, вы можете увидеть аннотации Андерса Хейлсберга и других на эту тему (стр. 184-185), но онлайн-версия здесь ...

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

Если вы обнаружите, что вам нужно вернуть две вещи из API, тогда лучше заключить их в структуру / класс, чем в out-параметр.

Мартин Пек
источник
Отличный ответ, особенно ссылка на TryParse, (общую) функцию, которая заставляет разработчиков использовать (необычные) переменные.
Cerebrus
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

Это предпочтение в основном

Я предпочитаю возвраты, и если у вас есть несколько возвратов, вы можете заключить их в Resto DTO

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, или в каком положении они находятся. Например, я хочу вернуть количество прочитанных байтов, а также значение. out или кортежи, которые названы были бы намного лучше. ArrayList или любой другой список будет полезен, только если вы хотите вернуть набор вещей, например список людей.
SLw
2

Я бы предпочел следующее вместо одного из них в этом простом примере.

public int Value
{
    get;
    private set;
}

Но они все очень похожи. Обычно можно использовать «out», только если им нужно передать несколько значений обратно из метода. Если вы хотите отправить значение в метод и из него, выберите «ref». Мой метод лучше всего подходит, если вы только возвращаете значение, но если вы хотите передать параметр и получить значение обратно, вы, вероятно, выбрали бы ваш первый выбор.

Kenny
источник
2

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

сянь
источник
2

Кроме того, возвращаемые значения совместимы с парадигмами асинхронного проектирования.

Вы не можете назначить функцию «асинхронная», если она использует параметры ref или out.

Таким образом, возвращаемые значения позволяют создавать цепочки методов, более чистый синтаксис (устраняя необходимость для вызывающей стороны объявлять дополнительные переменные) и позволяют создавать асинхронные схемы без существенных изменений в будущем.

firetiger77
источник
Отличный момент по обозначению "асинхронный". Думаю, кто-то еще упомянул бы это. Цепочка - а также использование возвращаемого значения (в действительности, самой функции) в качестве выражения - еще одно ключевое преимущество. Это действительно разница между рассмотрением только того, как просто вернуть материал из процесса («ведро» - обсуждение Tuple / class / struct) и обработкой самого процесса как выражения, которое можно заменить одним значением (полезность сама функция, потому что она возвращает только 1 значение).
user1172173
1

Оба они имеют различное назначение и не обрабатываются одинаково компилятором. Если ваш метод должен вернуть значение, вы должны использовать return. Out используется, когда ваш метод должен возвращать несколько значений.

Если вы используете return, то данные сначала записываются в стек методов, а затем в вызывающий метод. Хотя в случае out он напрямую записывается в стек вызывающих методов. Не уверен, есть ли еще различия.

датский язык
источник
Стек методов? Я не эксперт по c #, но x86 поддерживает только один стек на поток. «Кадр» метода освобождается во время возврата, и если произошло переключение контекста, то освобожденный стек может быть перезаписан. В c все возвращаемые значения отправляются в реестр eax. Если вам нравится возвращать объекты / структуры, они должны быть размещены в куче, и указатель будет помещен в eax.
Стефан Лундстрем
1

Как уже говорили другие: возвращаемое значение, а не парам.

Могу ли я порекомендовать вам книгу «Руководство по проектированию рамок» (2-е изд)? Страницы 184-185 раскрывают причины, по которым следует избегать использования параметров. Вся книга будет направлять вас в правильном направлении по всем вопросам кодирования .NET.

В союзе с Framework Design Guidelines используется инструмент статического анализа FxCop. Вы найдете это на сайтах Microsoft для бесплатной загрузки. Запустите это на своем скомпилированном коде и посмотрите, что он говорит. Если он жалуется на сотни и сотни вещей ... не паникуйте! Посмотрите спокойно и внимательно на то, что говорится о каждом конкретном случае. Не спешите исправлять вещи как можно скорее. Учитесь на том, что это говорит вам. Вы будете поставлены на путь к мастерству.

Эндрю Уэбб
источник
1

Использование ключевого слова out с возвращаемым типом bool иногда может уменьшить раздувание кода и повысить читабельность. (Прежде всего, когда дополнительная информация в выходном параметре часто игнорируется.) Например:

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

против:

var result;
if (DoThing(out result))
{
    if (DoOtherThing(out result))
    {
        if (DoFinalThing(out result))
        {
            success = true;
        }
    }
}
Пол Смит
источник
1

Там нет никакой разницы. Выходные параметры в C # позволяют методу возвращать более одного значения, вот и все.

Однако есть небольшие отличия, но они не очень важны:

Использование параметра out заставит вас использовать две строки, такие как:

int n;
GetValue(n);

в то время как использование возвращаемого значения позволит вам сделать это в одну строку:

int n = GetValue();

Другое отличие (корректное только для типов значений и только в том случае, если C # не включает функцию) в том, что использование возвращаемого значения обязательно создаст копию значения при возврате функции, тогда как использование параметра OUT не обязательно сделает это.

user88637
источник
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

возвращаемое значение - это нормальное значение, которое возвращается вашим методом.

Где в качестве параметра out , well out и ref являются 2 ключевыми словами C #, они позволяют передавать переменные в качестве ссылки .

Большая разница между рефами и отказом есть ссылка должна быть инициализирована до и вне этого не

bizimunda
источник
-2

Я подозреваю, что не собираюсь заглядывать в этот вопрос, но я очень опытный программист, и я надеюсь, что некоторые из более открытых читателей обратят внимание.

Я считаю, что объектно-ориентированные языки программирования лучше подходят для того, чтобы их процедуры возврата значений (VRP) были детерминированными и чистыми.

«VRP» - это современное академическое название для функции, которая вызывается как часть выражения и имеет возвращаемое значение, которое условно заменяет вызов во время вычисления выражения. Например, в таком утверждении, как x = 1 + f(y)функцияf , выступает в качестве VRP.

«Детерминистический» означает, что результат функции зависит только от значений ее параметров. Если вы вызовете его снова с теми же значениями параметров, вы обязательно получите тот же результат.

«Чистый» означает отсутствие побочных эффектов: вызов функции ничего не делает, кроме вычисления результата. Это может быть интерпретировано не значит не важно побочных эффектов на практике, поэтому, если VRP выводит сообщение отладки каждый раз, когда оно вызывается, например, это, вероятно, можно игнорировать.

Таким образом, если в C # ваша функция не является детерминированной и чистой, я говорю, что вы должны сделать ее voidфункцией (другими словами, не VRP), и любое значение, которое она должна вернуть, должно быть возвращено либо в outa, либо в a.ref параметре, параметре.

Например, если у вас есть функция для удаления некоторых строк из таблицы базы данных, и вы хотите, чтобы она возвращала количество удаленных строк, вы должны объявить ее примерно так:

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

Если вы иногда хотите вызвать эту функцию, но не получите count , вы всегда можете объявить перегрузку.

Возможно, вы захотите узнать, почему этот стиль лучше подходит для объектно-ориентированного программирования. В целом, он вписывается в стиль программирования, который можно (немного неточно) назвать «процедурным программированием», и это стиль процедурного программирования, который лучше подходит для объектно-ориентированного программирования.

Зачем? Классическая модель объектов состоит в том, что они имеют свойства (или атрибуты), и вы запрашиваете и манипулируете объектом (главным образом) посредством чтения и обновления этих свойств. Стиль процедурного программирования облегчает эту задачу, поскольку вы можете выполнять произвольный код между операциями, которые получают и устанавливают свойства.

Недостатком процедурного программирования является то, что, поскольку вы можете выполнять произвольный код повсюду, вы можете получить очень тупые и уязвимые для взаимодействия взаимодействия через глобальные переменные и побочные эффекты.

Итак, довольно просто, хорошей практикой является сигнализировать кому-то, читающему ваш код, что у функции могут быть побочные эффекты, если она не возвращает значения.

спорщик
источник
> Если вы иногда хотите вызвать эту функцию, но не получить счетчик, вы всегда можете объявить перегрузку. В C # версии 7 (я думаю) и позже вы можете использовать_ символ сброса, чтобы игнорировать параметр out, например: DeleteBasketItems (category, out _);
спорщик