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


С добавлением кортежа!--7--> класс в .net 4, я пытался решить, если использование их в моем дизайне является плохим выбором или нет. Как я это вижу, a кортежа!--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;
}

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

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

13 163

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, где проекции будут создавать типы, к которым мы обычно не хочется назначать имена. (Да, я знаю, что анонимные типы с одинаковыми типами и именованными свойствами объединяются компилятором).

мое эмпирическое правило:если вы вернете его из своего публичного интерфейса-сделайте его именованным типом.

мое другое эмпирическое правило для использования кортежей:имя аргументов метода и локальных переменных типа 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 и т. д.

анонимные классы в 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();
    }
}

похоже на ключевое слово 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 типы с неназванными членами.

The только место, где я бы использовал Кортеж, - это когда вам нужно быстро собрать вместе некоторые данные в очень ограниченном объеме. семантика данных должна быть очевидна, так что код не трудно читать. Поэтому с помощью кортежа (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.

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

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

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

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

return (string Name1, int Name2)

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

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

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

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

мое мнение, что кортежи-это ущерб, а не особенность C#.

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