Является ли использование кортежей .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 и т.д.), То вам, вероятно, следует использовать другую конструкцию, такую ​​как структура или анонимный класс.