Практический пример использования Tuple в .Net 4.0?

97

Я видел кортеж, представленный в .Net 4, но не могу представить, где его можно использовать. Мы всегда можем создать собственный класс или структуру.

Амитабх
источник
13
Возможно, здесь стоит отметить, что эта тема очень старая. Посмотрите, что произошло на C # 7 !
maf-soft

Ответы:

83

В том-то и дело - удобнее все время не создавать собственный класс или структуру. Это улучшение вроде Actionили Func... вы можете сделать эти типы самостоятельно, но удобно, что они существуют во фреймворке.

Tanascius
источник
5
Вероятно, стоит указать на MSDN : «Любые общедоступные статические члены этого типа являются потокобезопасными. Любые члены экземпляра не гарантируют безопасность потоков ».
Мэтт Борха
Что ж, может, тогда разработчикам языков следовало упростить создание пользовательских типов на лету? Значит, мы могли бы сохранить тот же синтаксис вместо того, чтобы вводить еще один?
Thomas
@ThomasEyde, чем они и занимались последние 15 лет. Вот как были добавлены члены, содержащие выражение, и теперь значения кортежей. Классы Запись в стиле едва не обрезанный в августе (9 месяцев до этого комментария) для этой версии C # 7, и, вероятно , выйдет в C # 8. Кроме того, обратите внимание , что значение кортежи предлагают равенство значений , где набившие оскомину классы не . Представление всего этого в 2002 году потребовало бы предвидения
Панайотис Канавос
что вы имеете в виду под convenient not to make a custom class. Вы имеете в виду создание собственного экземпляра класса с помощью =new..()?
Alex
75

С помощью кортежей вы можете легко реализовать двумерный словарь (или n-мерный в этом отношении). Например, вы можете использовать такой словарь для реализации сопоставления обмена валют:

var forex = new Dictionary<Tuple<string, string>, decimal>();
forex.Add(Tuple.Create("USD", "EUR"), 0.74850m); // 1 USD = 0.74850 EUR
forex.Add(Tuple.Create("USD", "GBP"), 0.64128m);
forex.Add(Tuple.Create("EUR", "USD"), 1.33635m);
forex.Add(Tuple.Create("EUR", "GBP"), 0.85677m);
forex.Add(Tuple.Create("GBP", "USD"), 1.55938m);
forex.Add(Tuple.Create("GBP", "EUR"), 1.16717m);
forex.Add(Tuple.Create("USD", "USD"), 1.00000m);
forex.Add(Tuple.Create("EUR", "EUR"), 1.00000m);
forex.Add(Tuple.Create("GBP", "GBP"), 1.00000m);

decimal result;
result = 35.0m * forex[Tuple.Create("USD", "EUR")]; // USD 35.00 = EUR 26.20
result = 35.0m * forex[Tuple.Create("EUR", "GBP")]; // EUR 35.00 = GBP 29.99
result = 35.0m * forex[Tuple.Create("GBP", "USD")]; // GBP 35.00 = USD 54.58
MarioVW
источник
Я бы предпочел использовать Enum для сокращений стран. Это возможно?
Zack
1
Конечно, это должно работать без проблем, поскольку перечисления являются типами значений.
MarioVW
@MarioVW Это также может быть выполнено с использованием многомерного массива. Как кортеж имеет значение?
Alex
26

В журнале MSDN есть отличная статья, в которой рассказывается о проблемах с дизайном и проблемах, связанных с добавлением Tuple в BCL. Особенно интересен выбор между типом значения и ссылочным типом.

Как ясно из статьи, движущей силой Tuple было то, что многие группы внутри Microsoft использовали его, команда F # была впереди. Хотя это не упоминается, я считаю, что новое ключевое слово «dynamic» в C # (и VB.NET) тоже имеет к этому какое-то отношение, кортежи очень распространены в динамических языках.

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


ОБНОВЛЕНИЕ: из-за большой доработки C # версии 7, теперь синтаксис стал намного популярнее. Предварительное объявление в этом блоге .

Ганс Пассан
источник
23

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

public static void Main(string[] args)
{
    int userId = 0;
    Tuple<string, string> userData = GetUserData(userId);
}

public static Tuple<string, string> GetUserData(int userId)
{
    return new Tuple<string, string>("Hello", "World");
}
Tejs
источник
10
Однако это хороший пример, который не оправдывает использование Tuple.
Амитабх
7
Кортеж здесь подходит, так как вы возвращаете разные значения; но кортеж становится ярче, когда вы возвращаете несколько значений разных типов .
Марк Рушаков
21
Другой хороший пример - int.TryParse, так как вы можете исключить выходной параметр и вместо этого использовать Tuple. Таким образом, у вас может быть Tuple<bool, T> TryParse<T>(string input)и вместо использования выходного параметра вы получите оба значения обратно в кортеж.
Tejs
3
Фактически, именно это и происходит, когда вы вызываете любой метод TryParse из F #.
Джоэл Мюллер
23

Я использовал кортеж для решения проблемы 11 проекта Эйлер :

class Grid
{
    public static int[,] Cells = { { 08, 02, 22, // whole grid omitted

    public static IEnumerable<Tuple<int, int, int, int>> ToList()
    {
        // code converts grid to enumeration every possible set of 4 per rules
        // code omitted
    }
}

Теперь я могу решить всю проблему с помощью:

class Program
{
    static void Main(string[] args)
    {
        int product = Grid.ToList().Max(t => t.Item1 * t.Item2 * t.Item3 * t.Item4);
        Console.WriteLine("Maximum product is {0}", product);
    }
}

Я мог бы использовать для этого собственный тип, но он выглядел бы точно так же, как Tuple .

Крэйг Стунц
источник
16

Синтаксис кортежей C # смехотворно громоздок, поэтому объявлять кортежи сложно. И у него нет сопоставления с образцом, поэтому их также сложно использовать.

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

// sum and sum of squares at the same time
var x =
    Enumerable.Range(1, 100)
    .Aggregate((acc, x) => Tuple.Create(acc.Item1 + x, acc.Item2 + x * x));

Вместо того, чтобы объединять коллекцию значений в единый результат, давайте расширим единичный результат в коллекцию значений. Самый простой способ написать эту функцию:

static IEnumerable<T> Unfold<T, State>(State seed, Func<State, Tuple<T, State>> f)
{
    Tuple<T, State> res;
    while ((res = f(seed)) != null)
    {
        yield return res.Item1;
        seed = res.Item2;
    }
}

fпреобразует некоторое состояние в кортеж. Мы возвращаем первое значение из кортежа и устанавливаем наше новое состояние на второе значение. Это позволяет нам сохранять состояние на протяжении всего вычисления.

Вы используете это как таковое:

// return 0, 2, 3, 6, 8
var evens =
    Unfold(0, state => state < 10 ? Tuple.Create(state, state + 2) : null)
    .ToList();

// returns 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
var fibs =
    Unfold(Tuple.Create(0, 1), state => Tuple.Create(state.Item1, Tuple.Create(state.Item2, state.Item1 + state.Item2)))
    .Take(10).ToList();

evensдовольно прост, но fibsнемного умнее. На stateсамом деле это кортеж, содержащий соответственно fib (n-2) и fib (n-1).

Джульетта
источник
4
+1 Tuple.Create - удобное сокращение дляnew Tuple<Guid,string,...>
AaronLS
@Juliet Какая польза от ключевого слова State?
irfandar
7

Мне не нравится злоупотребление ими, поскольку они создают код, который не объясняет себя, но они великолепны для реализации составных ключей на лету, поскольку они реализуют IStructuralEquatable и IStructuralComparable (чтобы использовать как для поиска, так и для упорядочивания целей).

И они внутренне объединяют хэш-коды всех своих элементов; например, вот GetHashCode Tuple (взято из ILSpy):

    int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
    {
        return Tuple.CombineHashCodes(comparer.GetHashCode(this.m_Item1), comparer.GetHashCode(this.m_Item2), comparer.GetHashCode(this.m_Item3));
    }
Notoriousxl
источник
7

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

Без (неприятного вложения!):

Task.Factory.StartNew(() => data.RetrieveServerNames())
    .ContinueWith(antecedent1 =>
        {
            if (!antecedent1.IsFaulted)
            {
                ServerNames = KeepExistingFilter(ServerNames, antecedent1.Result);
                Task.Factory.StartNew(() => data.RetrieveLogNames())
                    .ContinueWith(antecedent2 =>
                        {
                            if (antecedent2.IsFaulted)
                            {
                                LogNames = KeepExistingFilter(LogNames, antecedent2.Result);
                                Task.Factory.StartNew(() => data.RetrieveEntryTypes())
                                    .ContinueWith(antecedent3 =>
                                        {
                                            if (!antecedent3.IsFaulted)
                                            {
                                                EntryTypes = KeepExistingFilter(EntryTypes, antecedent3.Result);
                                            }
                                        });
                            }
                        });
            }
        });

С кортежем

Task.Factory.StartNew(() =>
    {
        List<string> serverNames = data.RetrieveServerNames();
        List<string> logNames = data.RetrieveLogNames();
        List<string> entryTypes = data.RetrieveEntryTypes();
        return Tuple.Create(serverNames, logNames, entryTypes);
    }).ContinueWith(antecedent =>
        {
            if (!antecedent.IsFaulted)
            {
                ServerNames = KeepExistingFilter(ServerNames, antecedent.Result.Item1);
                LogNames = KeepExistingFilter(LogNames, antecedent.Result.Item2);
                EntryTypes = KeepExistingFilter(EntryTypes, antecedent.Result.Item3);
            }
        });

Если вы все равно использовали анонимную функцию с подразумеваемым типом, то вы не делаете код менее понятным, используя Tuple. Перенастроить кортеж из метода? По моему скромному мнению, используйте экономно, когда ясность кода является ключевым фактором. Я знаю, что против функционального программирования на C # трудно устоять, но мы должны учитывать всех этих старых неуклюжих "объектно-ориентированных" программистов на C #.

ЭндиКло
источник
5

Кортежи широко используются в функциональных языках, которые могут делать с ними больше вещей, теперь F # - это «официальный» язык .net, вы можете захотеть взаимодействовать с ним из C # и передавать их между кодом, написанным на двух языках.

Mant101
источник
Кортежи также встроены в типы для некоторых популярных языков сценариев, таких как Python и Ruby (которые также имеют реализации .Net для взаимодействия ... IronPython / Ruby).
MutantNinjaCodeMonkey
5

Я стараюсь избегать Tupleбольшинства сценариев, поскольку это ухудшает читабельность. Однако Tupleэто полезно, когда вам нужно сгруппировать несвязанные данные.

Например, предположим, что у вас есть список автомобилей и городов, в которых они были приобретены:

Mercedes, Seattle
Mustang, Denver
Mercedes, Seattle
Porsche, Seattle
Tesla, Seattle
Mercedes, Seattle

Вы хотите суммировать количество автомобилей для каждого города:

Mercedes, Seattle [3]
Mustang, Denver [1]
Porsche, Seattle [1]
Tesla, Seattle [1]

Для этого вы создаете файл Dictionary. У вас есть несколько вариантов:

  1. Создайте Dictionary<string, Dictionary<string, int>>.
  2. Создайте Dictionary<CarAndCity, int>.
  3. Создайте Dictionary<Tuple<string, string>, int>.

При первом варианте теряется читаемость. Вам потребуется написать намного больше кода.

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

Третий вариант - лаконичный и чистый. Это хорошее применение Tuple.

Джон Курлак
источник
4

Несколько примеров из моей головы:

  • Расположение X и Y (и Z, если хотите)
  • Ширина и высота
  • Все, что измеряется с течением времени

Например, вы не хотели бы включать System.Drawing в веб-приложение только для использования Point / PointF и Size / SizeF.

Джеймс Вестгейт
источник
2
Tupleтак же удобен в критических ситуациях, как Pointи SomeVectorможет быть полезен при выполнении графических операций. Он подчеркивает две ценности, которые очень связаны друг с другом. Я часто вижу свойства с именами StartTime и EndTime при чтении кода, но даже лучше, чем дата, время и продолжительность, Tuple заставляет вас учитывать оба значения каждый раз, когда вы работаете в этой области своей бизнес-логики. Или возврат «эй, данные изменились (булево), вот данные (другой тип)» в тяжелой обработке, а не через интенсивные привязки.
Леон Пеллетье
3

Вы должны быть очень осторожны с использованием Tupleи, вероятно, дважды подумать, прежде чем делать это. Из своего предыдущего опыта я узнал, что использование Tupleделает код очень трудным для чтения и поддержки в будущем. Некоторое время назад мне пришлось поправить код, в котором кортежи использовались почти везде. Вместо того чтобы думать о правильных объектных моделях, они просто использовали кортежи. Это был кошмар ... иногда мне хотелось убить парня, который написал код ...

Не хочу сказать, что вам не следует использовать, Tupleи это зло или что-то в этом роде, и я на сто процентов уверен, что есть некоторые задачи, в Tupleкоторых лучше всего использовать этот вариант, но, вероятно, вам стоит подумать еще раз, действительно ли он вам нужен ?

Мистер Тыква
источник
1

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

Другие простые альтернативы могут использовать параметр out

private string MyMethod(out object)

или сделать словарь

Dictionary<objectType1, objectType2>

Однако использование кортежа избавляет либо от создания объекта out, либо от необходимости искать запись в словаре;

Сиджамес
источник
1

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

Задача - написать ответ JSON от LINQ без дополнительных классов:

 //I select some roles from my ORM my with subrequest and save results to Tuple list
 var rolesWithUsers = (from role in roles
                       select new Tuple<string, int, int>(
                         role.RoleName, 
                         role.RoleId, 
                         usersInRoles.Where(ur => ur.RoleId == role.RoleId).Count()
                      ));

 //Then I add some new element required element to this collection
 var tempResult = rolesWithUsers.ToList();
 tempResult.Add(new Tuple<string, int, int>(
                        "Empty", 
                         -1,
                         emptyRoleUsers.Count()
                      ));

 //And create a new anonimous class collection, based on my Tuple list
 tempResult.Select(item => new
            {
                GroupName = item.Item1,
                GroupId = item.Item2,
                Count = item.Item3
            });


 //And return it in JSON
 return new JavaScriptSerializer().Serialize(rolesWithUsers);

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

Alex
источник
1

В моем случае мне пришлось использовать Tuple, когда я обнаружил, что мы не можем использовать параметр out в асинхронном методе. Об этом читайте здесь . Мне также нужен был другой тип возврата. Поэтому я использовал Tuple в качестве возвращаемого типа и пометил этот метод как асинхронный.

Пример кода ниже.

...
...
// calling code.
var userDetails = await GetUserDetails(userId);
Console.WriteLine("Username : {0}", userDetails.Item1);
Console.WriteLine("User Region Id : {0}", userDetails.Item2);
...
...

private async Tuple<string,int> GetUserDetails(int userId)
{
    return new Tuple<string,int>("Amogh",105);
    // Note that I can also use the existing helper method (Tuple.Create).
}

Подробнее о Tuple здесь . Надеюсь это поможет.

Амог Нату
источник
Я предполагаю, что вы не хотели возвращать анонимный тип или массив (или динамическое содержимое)
ozzy432836,
0

Изменение формы объектов, когда вам нужно отправить их по сети или передать на другой уровень приложения, и несколько объектов объединяются в один:

Пример:

var customerDetails = new Tuple<Customer, List<Address>>(mainCustomer, new List<Address> {mainCustomerAddress}).ToCustomerDetails();

ExtensionMethod:

public static CustomerDetails ToCustomerDetails(this Tuple<Website.Customer, List<Website.Address>> customerAndAddress)
    {
        var mainAddress = customerAndAddress.Item2 != null ? customerAndAddress.Item2.SingleOrDefault(o => o.Type == "Main") : null;
        var customerDetails = new CustomerDetails
        {
            FirstName = customerAndAddress.Item1.Name,
            LastName = customerAndAddress.Item1.Surname,
            Title = customerAndAddress.Item1.Title,
            Dob = customerAndAddress.Item1.Dob,
            EmailAddress = customerAndAddress.Item1.Email,
            Gender = customerAndAddress.Item1.Gender,
            PrimaryPhoneNo = string.Format("{0}", customerAndAddress.Item1.Phone)
        };

        if (mainAddress != null)
        {
            customerDetails.AddressLine1 =
                !string.IsNullOrWhiteSpace(mainAddress.HouseName)
                    ? mainAddress.HouseName
                    : mainAddress.HouseNumber;
            customerDetails.AddressLine2 =
                !string.IsNullOrWhiteSpace(mainAddress.Street)
                    ? mainAddress.Street
                    : null;
            customerDetails.AddressLine3 =
                !string.IsNullOrWhiteSpace(mainAddress.Town) ? mainAddress.Town : null;
            customerDetails.AddressLine4 =
                !string.IsNullOrWhiteSpace(mainAddress.County)
                    ? mainAddress.County
                    : null;
            customerDetails.PostCode = mainAddress.PostCode;
        }
...
        return customerDetails;
    }
Матас Вайткявичюс
источник
0

Параметр out отлично подходит, когда нужно вернуть только несколько значений, но когда вы начинаете встречать 4, 5, 6 или более значений, которые нужно вернуть, он может стать громоздким. Другой вариант для возврата нескольких значений - создать и вернуть определенный пользователем класс / структуру или использовать Tuple для упаковки всех значений, которые должны быть возвращены методом.

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

public struct Dimensions
{
public int Height;
public int Width;
public int Depth;
}

Второй вариант, использующий кортеж, является даже более элегантным решением, чем использование определяемого пользователем объекта. Можно создать кортеж для хранения любого количества значений разных типов. Кроме того, данные, которые вы храните в кортеже, неизменны; после добавления данных в кортеж через конструктор или статический метод Create эти данные не могут быть изменены. Кортежи могут принимать до восьми отдельных значений включительно. Если вам нужно вернуть более восьми значений, вам нужно будет использовать специальный класс Tuple: Класс Tuple При создании Tuple с более чем восемью значениями вы не можете использовать статический метод Create - вместо этого вы должны использовать конструктор класса. Вот как вы создадите кортеж из 10 целочисленных значений:

var values = new Tuple<int, int, int, int, int, int, int, Tuple<int, int, int>> (
1, 2, 3, 4, 5, 6, 7, new Tuple<int, int, int> (8, 9, 10));

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

Яшвант Чоудари Ката
источник
0

Только для прототипирования - кортежи бессмысленны. Пользоваться ими удобно, но это всего лишь ярлык! Для прототипов - нормально. Только не забудьте удалить этот код позже.

Легко писать, трудно читать. Он не имеет видимых преимуществ перед классами, внутренними классами, анонимными классами и т. Д.

кролик1985
источник
0

Я попробовал 3 способа решить ту же проблему в C # 7 и нашел вариант использования кортежей.

Работа с динамическими данными в веб-проектах иногда может быть проблемой при отображении и т. Д.

Мне нравится, как Tuple просто автоматически сопоставляется с item1, item2, itemN, что мне кажется более надежным, чем использование индексов массива, где вы можете попасть в элемент вне индекса или использовать анонимный тип, где вы можете неправильно написать имя свойства.

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

using System;

namespace Playground
{
    class Program
    {
        static void Main(string[] args)
        {
            var tuple = GetTuple();
            Console.WriteLine(tuple.Item1);
            Console.WriteLine(tuple.Item2);
            Console.WriteLine(tuple.Item3);
            Console.WriteLine(tuple);

            Console.WriteLine("---");

            var dyn = GetDynamic();
            Console.WriteLine(dyn.First);
            Console.WriteLine(dyn.Last);
            Console.WriteLine(dyn.Age);
            Console.WriteLine(dyn);

            Console.WriteLine("---");

            var arr = GetArray();
            Console.WriteLine(arr[0]);
            Console.WriteLine(arr[1]);
            Console.WriteLine(arr[2]);
            Console.WriteLine(arr);

            Console.Read();

            (string, string, int) GetTuple()
            {
                return ("John", "Connor", 1);
            }

            dynamic GetDynamic()
            {
                return new { First = "John", Last = "Connor", Age = 1 };
            }

            dynamic[] GetArray()
            {
                return new dynamic[] { "John", "Connor", 1 };
            }
        }
    }
}
ozzy432836
источник