Можно ли обрабатывать возвращаемое значение метода C #?Какова хорошая практика в этом примере? - PullRequest
65 голосов
/ 29 июля 2011

Из любопытства ... что происходит, когда мы вызываем метод, который возвращает какое-то значение, но мы не обрабатываем / не используем его? И мы также ожидаем, что иногда это возвращаемое значение может быть очень большим. Куда идет это значение? Это даже создано? Если это так, могут ли возникнуть проблемы с производительностью или другие проблемы? (Какова наилучшая практика в такой ситуации?)

Допустим, у нас есть метод, который выполняет некоторые операции с базой данных (вставка, обновление) и возвращает некоторые данные в объект DataTable. И я также знаю, что этот объект DataTable иногда может быть очень большим:

public static Datatable InsertIntoDB(...) 
{
      // executing db command, getting values, creating & returning Datatable object...
      ...
      return myDataTable;
}

И затем, когда этот метод используется, он вызывается так:

DataTable myDataTable = InsertIntoDB(...);
// this Datatable object is handled in some way

Но иногда просто так:

InsertIntoDB(...);
// returned value not handled; Problem???

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

Ответы [ 10 ]

94 голосов
/ 29 июля 2011

Возвращенное значение (или ссылка, если это ссылочный тип) помещается в стек, а затем извлекается снова.

Не важно.

Если возвращаемое значение не имеет значения, вы можете сделать это безопасно.

Но будьте уверены, что это не актуально, на всякий случай.

Вот код:

    static string GetSomething()
    {
        return "Hello";
    }

    static void Method1()
    {
        string result = GetSomething();
    }

    static void Method2()
    {
        GetSomething();
    }

Если мы посмотрим на IL:

Method1:

.locals init ([0] string result)
IL_0000:  nop
IL_0001:  call       string ConsoleApplication3.Program::GetSomething()
IL_0006:  stloc.0
IL_0007:  ret

Method2:

IL_0000:  nop
IL_0001:  call       string ConsoleApplication3.Program::GetSomething()
IL_0006:  pop
IL_0007:  ret

Точно такое же количество инструкций. В Method1 значение сохраняется в локальном строковом результате (stloc.0), который удаляется, когда выходит из области видимости. В Method2 операция pop просто удаляет ее из стека.

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

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

Но самое главное - быть уверенным в том, что возвращаемое значение, которое вы игнорируете, не является чем-то, над чем вам следует действовать.

33 голосов
/ 29 июля 2011

РЕДАКТИРОВАТЬ: Смягчить язык очень немного и уточнить.

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

Один пример, где я видел, что это будет хорошо:

int foo;
int.TryParse(someText, out foo);

// Keep going

Здесь foo будет 0, если либо someTextсодержал "0", или это не могло быть проанализировано. Нам может быть все равно, в каком случае в этом случае возвращаемое значение метода для нас не имеет значения.

Другой пример приведен в словаре - предположим, вы пытаетесь подсчитать числовхождений каждой строки.Вы можете использовать:

int count;
dictionary.TryGetValue(word, out count);
dictionary[word] = count + 1;

Если слово не было в словаре для начала, это эквивалентно тому, что счетчик равен 0 - что уже произойдет в результате вызова TryGetValue.

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

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

РЕДАКТИРОВАТЬ: Другие примеры, где можно игнорировать возвращаемое значение:

  • Некоторые беглые интерфейсы, включая StringBuilder;в то время как StringBuilder.Append(x).Append(y); использует первое возвращаемое значение для второго вызова, очень часто возвращаемое значение вызова игнорируется, например, при добавлении в цикл
  • Некоторые вызовы коллекции могут давать возвращаемые значения, которые иногда игнорируются -например, HashSet<T>.Add, который указывает, было ли значение на самом деле добавлено или уже присутствовало.Иногда вам просто все равно.

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

9 голосов
/ 29 июля 2011

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

В этом конкретном случае DataTable реализует IDisposable так что не все в порядке на 100%:

Если возвращаемый объект реализует IDisposable, тогда хорошей идеей будет избавиться от него, например:

using (var retVal = InsertIntoDB(...))
{
    // Could leave this empty if you wanted
}
4 голосов
/ 29 июля 2011

Это зависит от возвращаемого значения.

Компилятор сгенерирует это значение в методе вызывающей стороны. Поэтому, если значение равно IDispolable, или предоставлен метод Close, или если у него есть ресурсы, которые должны быть освобождены, то вы не должны игнорировать его и утилизировать его должным образом, или иначе вы можете страдать от проблем и утечек памяти ..

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

2 голосов
/ 04 августа 2011

Чтобы дать другой взгляд на вещи, я думаю, что метод должен быть переработан.Взгляните на разделение Command-Query .

. Кроме того, редко бывает хорошей идеей тихо игнорировать возвращаемое значение.Читатели кода могут не иметь оригинального контекста автора.Они могут подумать, что он просто забыл его использовать.Если возвращаемое значение не важно, лучше прояснить это решение:

var ignoredReturnValue = InsertIntoDB(...);

Интересно, что Nemerle фактически выдает предупреждение, если вы игнорируете возвращаемое значение.Чтобы не получить предупреждение, вы должны четко указать свое решение и написать:

_ = InsertIntoDB(...);
2 голосов
/ 29 июля 2011

Возвращаемое значение выбрасывается, если не используется, но оно создается.Вполне разумно не использовать его (хотя вы должны быть уверены, что это правильно), но если для его создания требуется много ресурсов, то это напрасно.

Возможно, вы захотитерассмотреть вопрос о том, будет ли другой метод более подходящим, если вообще не создается возвращаемый объект.

2 голосов
/ 29 июля 2011

Вполне нормально игнорировать возвращаемое значение.

Однако.Архитектурное проектирование, ИМХО, не очень хорошее.Метод вставки не должен возвращать вообще ничего (кроме MAYBE true или false в случае успеха или неудачи).Если нужно получить новый, обновленный набор данных, нужно запросить его, т. Е. Вызвать другой метод для этого.

1 голос
/ 29 июля 2011

Я уверен, что это не вызывает никаких проблем, иначе C # не был бы очень надежным языком.

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

Как утверждали другие, игнорирование возвращаемого значения в конструктивном представлении действительно указывает на проблему, и, скорее всего, вы должны смотреть на возвращаемое значение.

0 голосов
/ 25 мая 2017

Все эти разговоры о том, нормально ли игнорировать возвращаемые типы, не нужны, мы все равно делаем это все время в C #. Многие функции, которые вы используете, как будто они возвращают void, не возвращают void. Подумайте о такой общей функции, как Button1.Focus ()

Знаете ли вы, что функция .Focus () возвращает значение bool? Возвращает true, если ему удалось сфокусироваться на элементе управления. Таким образом, вы можете проверить это как bool, сказав:

if (Button1.Focus == true) MessageBox.Show («Кнопка успешно сфокусирована.»); еще MessageBox.Show («Не могу сфокусироваться на кнопке, извините.»);

Но обычно вы этого не делаете. Вы просто говорите: Button1.Focus ();

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

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

0 голосов
/ 29 июля 2011

Если ваша функция вносит некоторые изменения в другие объекты (например, в БД), я думаю, что можно не обрабатывать возвращаемый объект, если он вам не нужен.

...