Хорошо ли обрабатывать возвращаемое значение метода С#? Какая хорошая практика в этом примере?

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

Скажем, у нас есть метод, который выполняет некоторые операции с базой данных (вставка, обновление) и возвращает некоторые данные в объекте 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???

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

Ответ 1

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

Нет большой.

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

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

Вот код:

    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

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

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

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

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

Ответ 2

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

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

Один пример, где я видел, что все в порядке:

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 (и предполагая, что ему удалось прочитать все запрошенные вами данные), является распространенной ошибкой.

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

EDIT: Другие примеры, где можно игнорировать возвращаемое значение:

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

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

Ответ 3

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

В этом конкретном случае DataTable реализует IDisposable, поэтому его не все 100% штрафа:

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

using (var retVal = InsertIntoDB(...))
{
    // Could leave this empty if you wanted
}

Ответ 4

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

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

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

Ответ 5

Совершенно верно, чтобы игнорировать возвращаемое значение.

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

Ответ 6

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

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

Ответ 7

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

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

var ignoredReturnValue = InsertIntoDB(...);

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

_ = InsertIntoDB(...);

Ответ 8

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

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

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

Ответ 9

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

Ответ 10

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

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

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

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

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

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