Практический пример, где Tuple можно использовать в .Net 4.0? - PullRequest
96 голосов
/ 30 апреля 2010

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

Ответы [ 19 ]

82 голосов
/ 30 апреля 2010

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

74 голосов
/ 23 августа 2013

С помощью кортежей вы можете легко реализовать двумерный словарь (или 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
26 голосов
/ 30 апреля 2010

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

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

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


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

23 голосов
/ 30 апреля 2010

Я использовал кортеж для решения Задачи 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 .

22 голосов
/ 30 апреля 2010

Вот небольшой пример - скажем, у вас есть метод, который должен искать дескриптор пользователя и адрес электронной почты, учитывая идентификатор пользователя. Вы всегда можете создать пользовательский класс, который содержит эти данные, или использовать параметр 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");
}
16 голосов
/ 30 апреля 2010

Синтаксис кортежей в 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) соответственно.

7 голосов
/ 06 июня 2011

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

И они комбинируют все хеш-коды своих предметов внутри; например, вот код 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 голосов
/ 30 января 2013

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

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

5 голосов
/ 09 марта 2016

Я склонен избегать 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.

5 голосов
/ 30 апреля 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...