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

91

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

  • 11
    Может быть, это хорошее место, чтобы отметить, что эта тема очень старая. Посмотрите, что случилось в C # 7 !
Теги:
c#-4.0
.net-4.0

19 ответов

78
Лучший ответ

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

  • 5
    Вероятно, стоит указать в MSDN : «Любые открытые статические члены этого типа являются поточно-ориентированными. Любые члены экземпляров не гарантируют поточно-ориентированные ».
  • 0
    Ну, может быть, разработчики языка должны были упростить создание пользовательских типов на лету, тогда? Таким образом, мы могли бы сохранить тот же синтаксис вместо введения еще одного?
Показать ещё 2 комментария
70

С кортежами вы можете легко реализовать двумерный словарь (или 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
  • 0
    Я бы предпочел использовать Enum для аббревиатур страны. Это возможно?
  • 1
    Конечно, это должно работать без проблем, поскольку перечисления являются типами значений.
Показать ещё 2 комментария
23

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

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

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


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

23

Вот небольшой пример - скажем, у вас есть метод, который должен искать пользовательский дескриптор и адрес электронной почты, учитывая идентификатор пользователя. Вы всегда можете создать собственный класс, который содержит эти данные, или использовать параметр ref/out для этих данных, или вы можете просто вернуть кортеж и иметь красивую подпись метода, не создавая новый 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");
}
  • 9
    Однако это хороший пример, не оправдывающий использование Tuple.
  • 7
    Кортеж здесь подходит, так как вы возвращаете разные значения; но кортеж светит больше, когда вы возвращаете несколько значений разных типов .
Показать ещё 3 комментария
22

Я использовал кортеж для решения Проблема 11 Project Euler:

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.

15

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

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

// 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,...>
  • 0
    @Juliet Что использовать ключевое слово State
7

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

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

    int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
    {
        return Tuple.CombineHashCodes(comparer.GetHashCode(this.m_Item1), comparer.GetHashCode(this.m_Item2), comparer.GetHashCode(this.m_Item3));
    }
6

Кортежи отлично подходят для выполнения нескольких асинхронных операций ввода-вывода за один раз и возвращения всех значений вместе. Вот примеры того, как это делается с 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);
                                            }
                                        });
                            }
                        });
            }
        });

С Tuple

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. Возврат кортежа из метода? Используйте экономно, когда ясность кода является ключевым, по моему скромному мнению. Я знаю, что функциональное программирование на С# трудно сопротивляться, но мы должны рассмотреть все эти старые неуклюжие "объектно-ориентированные" программисты на С#.

  • 0
    ! пропустил восклицательный знак в первом примере
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. У вас есть несколько вариантов:

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

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

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

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

4

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

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

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

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

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

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

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

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

1

Ну, в моем случае, я должен был использовать Tuple, когда узнал, что мы не можем использовать параметр в асинхронном методе. Читайте об этом здесь. Мне также нужен другой тип возврата. Поэтому я использовал 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 here. Надеюсь, это поможет.

  • 0
    Я предполагаю, что вы не хотите возвращать анонимный тип или массив (или динамическое содержимое)
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);

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

1

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

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

private string MyMethod(out object)

или создание словаря

Dictionary<objectType1, objectType2>

Использование Tuple, тем не менее, сохраняет либо создание объекта 'out', либо, по существу, поиск записи в словаре;

0

Ну, я попробовал 3 способа решить ту же проблему в С# 7, и я нашел пример использования для Tuples.

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

Мне нравится, как 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 };
            }
        }
    }
}
0

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

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

0

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

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

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

Второй вариант, используя Tuple, является еще более элегантным решением, чем использование пользовательского объект. Кортеж может быть создан для хранения любого количества значений различных типов. Кроме того, данные, хранящиеся в Tuple, неизменяемы; после добавления данных в кортеж через конструктор или статический метод Create, данные не могут быть изменилось. Кортежи могут принимать до восьми отдельных значений. Если вам нужно вернуться более восьми значений, вам нужно будет использовать специальный класс Tuple: Класс Tuple При создании кортежа с более чем восьми значениями вы не можете использовать статический Create method - вместо этого вы должны использовать конструктор класса. Вот как вы создайте Кортеж из 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));

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

0

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

Пример:

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

метод расширения:

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню