Возвращая два значения, Tuple vs 'out' vs 'struct'

Рассмотрим функцию, которая возвращает два значения. Мы можем написать:

// Using out:
string MyFunction(string input, out int count)

// Using Tuple class:
Tuple<string, int> MyFunction(string input)

// Using struct:
MyStruct MyFunction(string input)

Какая из них - лучшая практика и почему?

Ответ 1

У каждого из них свои плюсы и минусы.

Параметры параметров бывают быстрыми и дешевыми, но требуют, чтобы вы проходили переменную и полагались на мутацию. Практически невозможно правильно использовать параметр out с LINQ.

Кортежи производят давление на сбор и не самодокументируются. "Item1" не очень описателен.

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

Я был бы склонен к тому, чтобы пользовательское структурное решение при прочих равных условиях. Еще лучше, однако, сделать функцию, которая возвращает только одно значение. Почему вы в первую очередь возвращаете два значения?

UPDATE: обратите внимание, что кортежи в С# 7, которые были отправлены через шесть лет после написания этой статьи, являются типами значений и, следовательно, менее вероятны для создания давления в коллекции.

Ответ 2

Я думаю, что ответ зависит от семантики того, что выполняет функция, и от отношения между этими двумя значениями.

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

Если, однако, ваша функция возвращает координаты X/Y некоторого объекта на экране, то два значения семантически принадлежат друг другу, и было бы лучше использовать struct.

Я бы лично избегал использования tuple для всего, что будет видно внешнему коду из-за неудобного синтаксиса для извлечения членов.

Ответ 3

Добавляя к предыдущим ответам, С# 7 добавляет кортежи типа значений, в отличие от System.Tuple, который является ссылочным типом, а также предлагает улучшенную семантику.

Вы все равно можете оставить их неназванными и использовать синтаксис .Item*:

(string, string, int) getPerson()
{
    return ("John", "Doe", 42);
}

var person = getPerson();
person.Item1; //John
person.Item2; //Doe
person.Item3;   //42

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

(string FirstName, string LastName, int Age) getPerson()
{
    return ("John", "Doe", 42);
}

var person = getPerson();
person.FirstName; //John
person.LastName; //Doe
person.Age;   //42

Поддерживается также деструктуризация:

(string firstName, string lastName, int age) = getPerson()

Ответ 4

Пойду с подходом к использованию параметра Out, потому что во втором подходе вам понадобится создать и объект класса Tuple, а затем добавить к нему значение, которое, по моему мнению, является дорогостоящей операцией по сравнению с возвратом значения из параметра out. Хотя, если вы хотите вернуть несколько значений в Tuple Class (который не может быть достигнут, просто вернув один параметр out), я пошлю второй подход.

Ответ 5

Вы не указали еще один параметр, который имеет вместо класса тип пользовательского класса. Если у данных есть семантика, связанная с ним, на которую могут воздействовать функции, или размер экземпляра достаточно велик ( > 16 байт как правило), может быть предпочтительным пользовательский класс. Использование "out" не рекомендуется в публичном API из-за его ассоциации с указателями и требует понимания того, как работают ссылочные типы.

https://msdn.microsoft.com/en-us/library/ms182131.aspx

Tuple хорош для внутреннего использования, но его использование неудобно в публичном API. Итак, мой голос зависит от структуры и класса для публичного API.

Ответ 6

Нет "лучшей практики". Это то, с чем вам удобно, и что лучше всего работает в вашей ситуации. Если вы согласны с этим, нет никаких проблем с любыми решениями, которые вы опубликовали.