Является ли использование кортежей .NET 4.0 в моем коде С# неправильным решением?
С добавлением класса Tuple в .net 4, я пытался решить, использовать ли его в моем проекте как плохой выбор или нет. То, как я это вижу, Tuple может быть ярлыком для записи класса результата (я уверен, что есть и другие виды использования).
Итак, это:
public class ResultType
{
public string StringValue { get; set; }
public int IntValue { get; set; }
}
public ResultType GetAClassedValue()
{
//..Do Some Stuff
ResultType result = new ResultType { StringValue = "A String", IntValue = 2 };
return result;
}
Это эквивалентно этому:
public Tuple<string, int> GetATupledValue()
{
//...Do Some stuff
Tuple<string, int> result = new Tuple<string, int>("A String", 2);
return result;
}
Итак, оставляя в стороне возможность того, что мне не хватает точки Tuples, является примером с Tuple плохим выбором дизайна? Для меня это кажется менее беспорядочным, но не как самодокументирующимся и чистым. Это означает, что с типом ResultType
, очень ясно, что каждая часть класса означает, но у вас есть дополнительный код для поддержки. С помощью Tuple<string, int>
вам нужно будет найти и выяснить, что представляет каждый Item
, но вы пишете и поддерживаете меньше кода.
Любой опыт, который у вас был с этим выбором, будет с благодарностью.
Ответы
Ответ 1
Кортежи великолепны, если вы контролируете создание и использование их - вы можете поддерживать контекст, который необходим для их понимания.
Однако в публичном API они менее эффективны. Потребитель (а не вы) должен либо догадаться, либо искать документацию, особенно для таких вещей, как Tuple<int, int>
.
Я бы использовал их для частных/внутренних членов, но использовал классы результатов для общедоступных/защищенных членов.
Этот ответ также содержит некоторую информацию.
Ответ 2
Как я вижу, Tuple - это ярлык для написания класса результатов (I я уверен, что есть и другие виды использования).
Существуют и другие ценные виды использования Tuple<>
- большинство из них связано с абстрагированием семантики определенной группы типов, которые имеют сходную структуру, и рассматривают их просто как упорядоченный набор значений, Во всех случаях преимущество кортежей заключается в том, что они избегают загромождения вашего пространства имен классами данных, которые выставляют свойства, но не методы.
Вот пример разумного использования для Tuple<>
:
var opponents = new Tuple<Player,Player>( playerBob, playerSam );
В приведенном выше примере мы хотим представить пару противников, кортеж - удобный способ спаривания этих экземпляров без необходимости создания нового класса. Вот еще один пример:
var pokerHand = Tuple.Create( card1, card2, card3, card4, card5 );
Покерную руку можно рассматривать как просто набор карт - и кортеж (может быть) разумный способ выражения этой концепции.
исключая возможность того, что я я пропустил пункт "Кортежи", это пример с плотным дизайном Tuple выбор?
Возвращение строго типизированных экземпляров Tuple<>
как часть общедоступного API для общедоступного типа редко бывает хорошей идеей. Как вы сами понимаете, кортежи требуют участия сторон (автор библиотеки, пользователь библиотеки) заранее договориться о назначении и интерпретации используемых типов кортежей. Это достаточно сложно для создания API-интерфейсов, которые являются интуитивно понятными и понятными, используя Tuple<>
публично только скрывает намерение и поведение API.
Анонимные типы также являются своего рода кортежем, однако они строго типизированы и позволяют вам указывать четкие, информативные имена для свойств, принадлежащих типу. Но анонимные типы трудно использовать по разным методам - они были в основном добавлены для поддержки технологий, таких как LINQ, где прогнозы будут создавать типы, к которым мы обычно не хотели бы назначать имена. (Да, я знаю, что анонимные типы с одинаковыми типами и именованными свойствами объединяются компилятором).
Мое правило:, если вы вернете его из своего открытого интерфейса - сделайте его именованным.
Мое другое эмпирическое правило для использования кортежей: аргументы метода name и 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...
}
Ответ 3
Кортежи могут быть полезны... но они также могут быть болью позже. Если у вас есть метод, который возвращает Tuple<int,string,string,int>
, как вы знаете, каковы эти значения позже. Были ли они ID, FirstName, LastName, Age
или были ли они UnitNumber, Street, City, ZipCode
.
Ответ 4
Кортежи довольно впечатляющие дополнения к CLR с точки зрения программиста на С#. Если у вас есть набор элементов, которые различаются по длине, вам не нужны они, чтобы иметь уникальные статические имена во время компиляции.
Но если у вас есть коллекция постоянной длины, это означает, что фиксированные местоположения в коллекции имеют определенный предопределенный смысл. И всегда лучше дать им соответствующие статические имена в этом случае, вместо того, чтобы помнить о значении Item1
, Item2
и т.д.
Анонимные классы на С# уже обеспечивают превосходное решение для наиболее распространенного частного использования кортежей, и они дают значимые имена для элементов, поэтому они в действительности превосходят в этом смысле. Единственная проблема заключается в том, что они не могут просачиваться из названных методов. Я бы предпочел, чтобы это ограничение было отменено (возможно, только для частных методов), чем для поддержки кортежей в С#:
private var GetDesserts()
{
return _icecreams.Select(
i => new { icecream = i, topping = new Topping(i) }
);
}
public void Eat()
{
foreach (var dessert in GetDesserts())
{
dessert.icecream.AddTopping(dessert.topping);
dessert.Eat();
}
}
Ответ 5
Подобно ключевому слову var
, он предназначен для удобства - но так же легко злоупотребляется.
В моем самом скромном мнении не подвергайте Tuple
как возвращаемый класс. Используйте его конфиденциально, если требуется структура данных службы или компонента, но возвратите хорошо сформированные хорошо известные классы из общедоступных методов.
// one possible use of tuple within a private context. would never
// return an opaque non-descript instance as a result, but useful
// when scope is known [ie private] and implementation intimacy is
// expected
public class WorkflowHost
{
// a map of uri to a workflow service definition
// and workflow service instance. By convention, first
// element of tuple is definition, second element is
// instance
private Dictionary<Uri, Tuple<WorkflowService, WorkflowServiceHost>> _map =
new Dictionary<Uri, Tuple<WorkflowService, WorkflowServiceHost>> ();
}
Ответ 6
Использование класса типа ResultType
более ясное. Вы можете дать значимые имена полям в классе (тогда как с кортежем они будут называться Item1
и Item2
). Это еще более важно, если типы двух полей одинаковы: имя четко различает их.
Ответ 7
Как насчет использования Tuples в шаблоне decorate-sort-undecorate? (Шварццианское преобразование для людей Перла). Разумеется, это надуманный пример, но Tuples, похоже, являются хорошим способом справиться с такими вещами:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string[] files = Directory.GetFiles("C:\\Windows")
.Select(x => new Tuple<string, string>(x, FirstLine(x)))
.OrderBy(x => x.Item2)
.Select(x => x.Item1).ToArray();
}
static string FirstLine(string path)
{
using (TextReader tr = new StreamReader(
File.Open(path, FileMode.Open)))
{
return tr.ReadLine();
}
}
}
}
Теперь я мог бы использовать Object [] из двух элементов или в этом конкретном примере строку [] из двух элементов. Дело в том, что я мог бы использовать что-либо в качестве второго элемента в Tuple, который использовался внутри и довольно легко читался.
Ответ 8
IMO эти "кортежи" в основном представляют собой анонимные типы struct
открытого доступа с неназванными членами.
Единственное место, где я буду использовать кортеж, - это когда вам нужно быстро скомпоновать некоторые данные в очень ограниченном объеме. Семантика данных должна быть очевидной, поэтому код читать не сложно. Поэтому использование кортежа (int
, int
) для (row, col) представляется разумным. Но мне трудно найти преимущество над struct
с именованными членами (поэтому ошибок не происходит, а строка/столбец не меняются случайно)
Если вы отправляете данные обратно вызывающему абоненту или принимаете данные от вызывающего, вы действительно должны использовать struct
с именованными членами.
Возьмем простой пример:
struct Color{ float r,g,b,a ; }
public void setColor( Color color )
{
}
Версия кортежа
public void setColor( Tuple<float,float,float,float> color )
{
// why?
}
Я не вижу никакого преимущества в использовании кортежа вместо структуры с именованными членами. Использование неназванных участников - шаг назад для читаемости и понимания вашего кода.
Tuple поражает меня как ленивый способ избежать создания struct
с фактическими именованными членами. Чрезмерное использование кортежа, где вы действительно чувствуете, что вы/или кто-то другой сталкивается с вашим кодом, нуждаются в именованных членах - это Bad Thing ™, если я когда-либо видел его.
Ответ 9
Это зависит, конечно! Как вы сказали, кортеж может сэкономить вам код и время, когда вы хотите группировать некоторые элементы вместе для локального потребления. Вы также можете использовать их для создания более общих алгоритмов обработки, чем вы можете, если вы передадите конкретный класс. Я не могу вспомнить, сколько раз я бы хотел, чтобы у меня было что-то помимо KeyValuePair или DataRow, чтобы быстро передать некоторую дату из одного метода в другой.
С другой стороны, вполне возможно переусердствовать и перебирать кортежи, где вы можете только гадать, что они содержат. Если вы собираетесь использовать кортеж между классами, возможно, было бы лучше создать один конкретный класс.
Используется в модерации, конечно, кортежи могут привести к более сжатому и понятному коду. Вы можете посмотреть на С++, STL и Boost для примеров того, как Tuples используются на других языках, но в конце концов, всем нам придется поэкспериментировать, чтобы найти, как они лучше всего подходят в среде .NET.
Ответ 10
Кортежи - бесполезная функция фреймворка в .NET 4. Я думаю, что отличная возможность была упущена с С# 4.0. Я бы любил иметь кортежи с именованными членами, поэтому вы могли получить доступ к различным полям кортежа по имени вместо Value1, Value2 и т.д.
Это потребовало бы изменения языка (синтаксиса), но это было бы очень полезно.
Ответ 11
Я бы никогда не использовал Tuple в качестве возвращаемого типа, потому что нет никаких указаний на то, что представляют значения. Кортежи имеют ценное использование, потому что в отличие от объектов они являются типами значений и, таким образом, понимают равенство. Из-за этого я буду использовать их в качестве ключей словаря, если мне нужен многочастный ключ или ключ для предложения GroupBy, если я хочу группировать по нескольким переменным и не хочу вложенных группировок (кто когда-либо хочет вложенные группы?). Чтобы преодолеть эту проблему с помощью экстренной многословия, вы можете создать их с помощью вспомогательного метода. Имейте в виду, если вы часто обращаетесь к членам (через Item1, Item2 и т.д.), То вам, вероятно, следует использовать другую конструкцию, такую как структура или анонимный класс.