Это Using.NET 4.0 кортежи в моем коде C# плохое дизайнерское решение?

С добавлением кортежа!--7--> класс в .net 4, я пытался решить, является ли их использование в моем дизайне плохим выбором или нет. По-моему, а кортежа!--7--> может быть ярлыком для написания класса результатов (я уверен, что есть и другие виды использования).

значит так:

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;
}

таким образом, оставляя в стороне возможность того, что мне не хватает точки кортежей, является примером с кортежа!--7--> плохой выбор дизайна? Мне кажется, что меньше беспорядка, но не как самостоятельное документирование и очистка. Это означает, что с типом ResultType, очень ясно позже, что означает каждая часть класса, но у вас есть дополнительный код для поддержания. С Tuple<string, int> вам нужно будет посмотреть вверх и выяснить, что каждый Item представляет, но вы писать меньше кода.

любой опыт вы имели с этим выбором будут с благодарностью.

13 ответов


кортежи великолепны, если вы контролируете их создание и использование - вы можете поддерживать контекст, который необходим для их понимания.

на общедоступном API, однако, они менее эффективны. Потребитель (а не Вы) должен либо угадать, либо посмотреть документацию, особенно для таких вещей, как Tuple<int, int>.

Я бы использовал их для частных / внутренних членов, но использую классы результатов для открытых / защищенных членов.

ответ также имеет некоторые информация.


как я вижу, Кортеж-это ярлык для написания класса результатов (I уверен, что есть и другие применения).

есть действительно другие ценные использования для Tuple<> - большинство из них включают в себя абстрагирование семантики определенной группы типов, которые имеют схожую структуру, и рассматривают их просто как упорядоченный набор значений. Во всех случаях преимущество кортежей заключается в том, что они избегают загромождения пространства имен классами только для данных это предоставляет свойства, но не методы.

вот пример разумного использования Tuple<>:

var opponents = new Tuple<Player,Player>( playerBob, playerSam );

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

var pokerHand = Tuple.Create( card1, card2, card3, card4, card5 );

покер можно рассматривать как просто набор карт - и кортеж (может быть) разумный способ выражения этой концепции.

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

возврат строго набранных 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 );
        }
    }
}

П. С. существует еще один (более простой) способ обойти проблемы, возникающие из анонимных типов в словарях, и это использовать var ключевое слово, чтобы компилятор "выводил" тип для вас. Вот эта версия:

var foundItem = peopleDict.FirstOrDefault().Value;
if( peopleDict.TryGetValue( "Smith", out foundItem ) )
{
   // use foundItem...
}

кортежи могут быть полезны... но они также могут быть болью позже. Если у вас есть метод, который возвращает Tuple<int,string,string,int> Как вы знаете, что эти значения позже. Были они ID, FirstName, LastName, Age или UnitNumber, Street, City, ZipCode.


кортежи довольно неутешительное дополнение к CLR с точки зрения программиста C#. Если у вас есть коллекция элементов различной длины, вам не нужно, чтобы они имели уникальные статические имена во время компиляции.

но если у вас есть коллекция постоянной длины, это означает, что фиксированные местоположения в коллекции имеют определенное предопределенное значение. И это всегда лучше дать им соответствующие статические имена в этом случае, а не нужно помнить значение Item1, Item2, etc.

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

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();
    }
}

похожие на keyword 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's 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>> ();
}

используя класс ResultType понятнее. Вы можете дать значимые имена полям в классе (тогда как с кортежем они будут называться Item1 и Item2). Это еще более важно, если типы двух полей одинаковы: имя четко различает их между собой.


Как насчет использования кортежей в шаблоне decorate-sort-undecorate? (Преобразование Шварца для людей Perl). Вот надуманный пример, чтобы быть уверенным, но кортежи, похоже, хороший способ справиться с такими вещами:

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();
            }
        }
    }
}

теперь я мог бы использовать объект[] из двух элементов или в этом конкретном примере строку [] из двух элементов. Дело в том, что я мог бы использовать что угодно в качестве второго элемента кортежа, который используется внутри и довольно легко читается.


IMO эти "кортежи" в основном все анонимный доступ общественности struct типы с неназванными членами.

на только место я бы использовал кортеж, когда вам нужно быстро blob вместе некоторые данные,в очень ограниченной области. семантика данных должна быть очевидна, поэтому код не трудно читать. Итак, используя кортеж (int,int) for (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?
}

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

Кортеж поражает меня как ленивый способ избежать создания struct с фактическими имени членов. Чрезмерное использование кортежа, где вы действительно чувствуете, что вам / или кому-то другому, столкнувшемуся с вашим кодом, понадобится члены это плохо™, если я когда-либо видел.


Это зависит, конечно! Как вы сказали, Кортеж может сэкономить код и время, когда вы хотите сгруппировать некоторые элементы для местного потребления. Вы также можете использовать их для создания более общих алгоритмов обработки, чем при передаче конкретного класса. Я не помню, сколько раз я хотел, чтобы у меня было что-то за пределами KeyValuePair или DataRow, чтобы быстро передать какую-то дату от одного метода к другому.

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

использованный в умеренности конечно, кортежи могут привести к более сжатому и читаемому коду. Вы можете посмотреть на C++, STL и Boost для примеров того, как кортежи используются на других языках, но в конце концов, нам всем придется экспериментировать, чтобы найти, как они лучше всего подходят в среде .NET.


кортежи бесполезная функция в рамках .Сеть 4. Я думаю, отличная возможность была упущена с C# 4.0. Я бы хотел иметь кортежи с именованными членами, чтобы вы могли получить доступ к различным полям кортежа по имени вместо Value1, Value2, etc...

Это потребовало бы изменения языка (синтаксиса), но это было бы очень полезно.


Я бы лично никогда не использовал Кортеж в качестве возвращаемого типа, потому что нет указания на то, что представляют значения. Кортежи имеют некоторые ценные применения, потому что в отличие от объектов они являются типами значений и поэтому понимают равенство. Из-за этого я буду использовать их как ключи словаря, если мне нужен составной ключ или как ключ для предложения GroupBy, если я хочу группировать по нескольким переменным и не хочу вложенных группировок (кто когда-либо хочет вложенных группировок?). Чтобы преодолеть проблему с чрезвычайной многословностью вы можно создать их с помощью вспомогательного метода. Имейте в виду, что если вы часто обращаетесь к членам (через Item1, Item2 и т. д.), то вам, вероятно, следует использовать другую конструкцию, такую как структура или анонимный класс.


Не судите меня, я не эксперт, но с новыми кортежами в C# 7.x теперь вы можете вернуть что-то вроде:

return (string Name1, int Name2)

по крайней мере, теперь вы можете назвать его, и разработчики могут увидеть некоторую информацию.


я использовал кортежи, оба Tuple и новая ValueTuple в ряде различных сценариев и пришли к следующему выводу: не используйте.

каждый раз, я сталкивался со следующими проблемами:

  • код стал нечитаемым из-за отсутствия сильных именования;
  • невозможно использовать функции иерархии классов, такие как базовый класс DTO и дочерний класс DTOs и т. д.;
  • если они используются в более чем одном месте, вы копирование и вставка этих уродливых определений вместо чистого имени класса.

мое мнение, что кортежи являются ущербом, а не особенностью C#.

у меня есть несколько похожая, но гораздо менее жесткая критика Func<> и Action<>. Они полезны во многих ситуациях, особенно простые Action и Func<type> варианты, но что-то помимо этого, я обнаружил, что создание типа делегата более элегантное, читаемое, поддерживаемое и дает вам больше возможностей, таких как ref/out параметры.