На мой взгляд, Tuple - это ярлык для написания результирующего класса (я уверен, что есть и другие варианты использования).
Действительно, существуют и другие ценные варианты использованияTuple<>
- большинство из них включают абстрагирование семантики определенной группы типов, которые имеют сходную структуру, и трактование их просто как упорядоченный набор значений. Во всех случаях преимущество кортежей состоит в том, что они избегают загромождать ваше пространство имен классами только для данных, которые предоставляют свойства, но не методы.
Вот пример разумного использования для Tuple<>
:
var opponents = new Tuple<Player,Player>( playerBob, playerSam );
В приведенном выше примере мы хотим представить пару противников, кортеж является удобным способом объединения этих экземпляров без необходимости создания нового класса. Вот еще один пример:
var pokerHand = Tuple.Create( card1, card2, card3, card4, card5 );
Покерную комбинацию можно рассматривать как набор карт - и кортеж (может быть) является разумным способом выражения этой концепции.
оставляя в стороне возможность того, что я упускаю точку зрения Tuples, является ли пример с Tuple плохим выбором дизайна?
Возвращение строго типизированных Tuple<>
экземпляров как части открытого API для открытого типа редко является хорошей идеей. Как вы сами понимаете, кортежи требуют, чтобы вовлеченные стороны (автор библиотеки, пользователь библиотеки) заранее договорились о цели и интерпретации используемых типов кортежей. Достаточно сложно создать интуитивно понятные и понятные API-интерфейсы, которые Tuple<>
публично скрывают только намерения и поведение API.
Анонимные типы также являются своего рода кортежем, однако они строго типизированы и позволяют указывать понятные, информативные имена для свойств, принадлежащих типу. Но анонимные типы сложно использовать в разных методах - они были в основном добавлены для поддержки таких технологий, как LINQ, где проекции будут создавать типы, которым мы обычно не хотим назначать имена. (Да, я знаю, что анонимные типы с одинаковыми типами и именованными свойствами объединяются компилятором).
Мое эмпирическое правило таково: если вы вернете его из открытого интерфейса, сделайте его именованным типом .
Мое другое практическое правило использования кортежей: аргументы метода имени и переменные типа localc Tuple<>
как можно более понятны - заставьте имя представлять смысл отношений между элементами кортежа. Подумай о моем var opponents = ...
примере.
Вот пример реального случая, когда я использовал, Tuple<>
чтобы избежать объявления типа «только данные» для использования только в моей собственной сборке . Ситуация связана с тем, что при использовании общих словарей, содержащих анонимные типы, становится трудно использовать TryGetValue()
метод для поиска элементов в словаре, поскольку для метода требуется out
параметр, который не может быть назван:
public static class DictionaryExt
{
// helper method that allows compiler to provide type inference
// when attempting to locate optionally existent items in a dictionary
public static Tuple<TValue,bool> Find<TKey,TValue>(
this IDictionary<TKey,TValue> dict, TKey keyToFind )
{
TValue foundValue = default(TValue);
bool wasFound = dict.TryGetValue( keyToFind, out foundValue );
return Tuple.Create( foundValue, wasFound );
}
}
public class Program
{
public static void Main()
{
var people = new[] { new { LastName = "Smith", FirstName = "Joe" },
new { LastName = "Sanders", FirstName = "Bob" } };
var peopleDict = people.ToDictionary( d => d.LastName );
// ??? foundItem <= what type would you put here?
// peopleDict.TryGetValue( "Smith", out ??? );
// so instead, we use our Find() extension:
var result = peopleDict.Find( "Smith" );
if( result.First )
{
Console.WriteLine( result.Second );
}
}
}
PS Существует еще один (более простой) способ обойти проблемы, возникающие из-за анонимных типов в словарях, и заключается в использовании var
ключевого слова, чтобы позволить компилятору «вывести» тип за вас. Вот эта версия:
var foundItem = peopleDict.FirstOrDefault().Value;
if( peopleDict.TryGetValue( "Smith", out foundItem ) )
{
// use foundItem...
}