Як легко ініціалізувати список кортежів?


287

Я люблю кортежі . Вони дозволяють швидко групувати релевантну інформацію разом без необхідності писати структуру чи клас для неї. Це дуже корисно під час рефакторингу дуже локалізованого коду.

Однак ініціалізація їх списку здається трохи зайвою.

var tupleList = new List<Tuple<int, string>>
{
    Tuple.Create( 1, "cow" ),
    Tuple.Create( 5, "chickens" ),
    Tuple.Create( 1, "airplane" )
};

Хіба немає кращого способу? Я б хотів, щоб рішення було узгоджено з ініціалізатором словника .

Dictionary<int, string> students = new Dictionary<int, string>()
{
    { 111, "bleh" },
    { 112, "bloeh" },
    { 113, "blah" }
};

Чи не можемо ми використовувати подібний синтаксис?


2
У цьому випадку чому б ви не використовували словник замість списку Tuples?
Ред С.

17
@Ed S .: A Dictionaryне дозволяє повторювати ключі.
Стівен Євріс

2
@EdS.: Кожен раз, коли це не два подвійних елемента, де один предмет є придатним для впорядкування / замовленням та унікальним.

Добре, що не помітили повторюваного ключа.
Ред С.

Відповіді:


280

c # 7.0 дозволяє зробити це:

  var tupleList = new List<(int, string)>
  {
      (1, "cow"),
      (5, "chickens"),
      (1, "airplane")
  };

Якщо вам не потрібен Listмасив, а просто масив, ви можете:

  var tupleList = new(int, string)[]
  {
      (1, "cow"),
      (5, "chickens"),
      (1, "airplane")
  };

І якщо вам не подобаються "Item1" та "Item2", ви можете:

  var tupleList = new List<(int Index, string Name)>
  {
      (1, "cow"),
      (5, "chickens"),
      (1, "airplane")
  };

або для масиву:

  var tupleList = new (int Index, string Name)[]
  {
      (1, "cow"),
      (5, "chickens"),
      (1, "airplane")
  };

що дозволяє вам: tupleList[0].IndexіtupleList[0].Name

Рамка 4.6.2 і нижче

Ви повинні встановити System.ValueTupleз диспетчера пакунків Nuget.

Рамка 4.7 і вище

Він вбудований в рамки. Ви НЕ встановлювати System.ValueTuple. Фактично, видаліть його та видаліть із каталогу бін.

Примітка: У реальному житті я не міг би вибирати між коровою, курами чи літаком. Я був би справді розірваний.


2
Чи можна це використовувати на .net core 2.0?
Алекса Јевтић

2
@ АлексаЈевтић, System.ValueTupleпідтримує core 2.0. Але спробуйте спочатку без Nuget, оскільки непотрібні пакунки можуть спричинити проблеми. Так чи інакше, так. Використовуйте c # v7 або більше, якщо можливо.
toddmo

1
Абсолютно ідеальна відповідь! Дякую!
Вітокс

224

Так! Це можливо .

{} Синтаксис колекції ініціалізатор працює на будь-якому IEnumerable типу , який має Add метод з правильним кількістю аргументів. Не турбуючись про те, як це працює під кришками, це означає, що ви можете просто продовжити зі списку <T> , додати спеціальний метод Додати для ініціалізації вашого T , і ви закінчили!

public class TupleList<T1, T2> : List<Tuple<T1, T2>>
{
    public void Add( T1 item, T2 item2 )
    {
        Add( new Tuple<T1, T2>( item, item2 ) );
    }
}

Це дозволяє зробити наступне:

var groceryList = new TupleList<int, string>
{
    { 1, "kiwi" },
    { 5, "apples" },
    { 3, "potatoes" },
    { 1, "tomato" }
};

39
Недоліком є ​​написання класу. Як ви, я люблю кортежі, тому що мені не потрібно писати клас чи структуру.
goodeye

2
@BillW Не надто впевнений, це точний пост, ... але я знаю, що Джон Скіт писав про це раніше .
Стівен Євріс

1
це працює groceryList[0] == groceryList[1]чи потрібно проводити порівняння?
Байо

Я створив Nuget Package з методами Add на ICollection, щоб заощадити іншим час необхідності підтримувати цей код. Ознайомтесь з пакунком: nuget.org/packages/Naos.Recipes.TupleInitializer Дивіться тут код: github.com/NaosProject/Naos.Recipes/blob/master/…. Це додасть файл cs у ваше рішення під ".Naos .Рецепти ", тому вам не доведеться перетягувати залежність від складання
SFun28

83

C # 6 додає нову функцію саме для цього: розширення Додати методи. Це завжди було можливим для VB.net, але тепер він доступний на C #.

Тепер вам не потрібно додавати Add()методи до своїх класів безпосередньо, ви можете реалізувати їх як методи розширення. Розширюючи будь-який перелічений тип Add()методом, ви зможете використовувати його у виразах ініціалізатора колекції. Таким чином, вам більше не доведеться виводити зі списків прямо ( як це було сказано в іншій відповіді ), ви можете просто розширити його.

public static class TupleListExtensions
{
    public static void Add<T1, T2>(this IList<Tuple<T1, T2>> list,
            T1 item1, T2 item2)
    {
        list.Add(Tuple.Create(item1, item2));
    }

    public static void Add<T1, T2, T3>(this IList<Tuple<T1, T2, T3>> list,
            T1 item1, T2 item2, T3 item3)
    {
        list.Add(Tuple.Create(item1, item2, item3));
    }

    // and so on...
}

Це дозволить зробити це в будь-якому класі, який реалізує IList<>:

var numbers = new List<Tuple<int, string>>
{
    { 1, "one" },
    { 2, "two" },
    { 3, "three" },
    { 4, "four" },
    { 5, "five" },
};
var points = new ObservableCollection<Tuple<double, double, double>>
{
    { 0, 0, 0 },
    { 1, 2, 3 },
    { -4, -2, 42 },
};

Звичайно, ви не обмежуєтеся розширенням колекцій кортежів, це може бути колекція будь-якого конкретного типу, для якого потрібен спеціальний синтаксис.

public static class BigIntegerListExtensions
{
    public static void Add(this IList<BigInteger> list,
        params byte[] value)
    {
        list.Add(new BigInteger(value));
    }

    public static void Add(this IList<BigInteger> list,
        string value)
    {
        list.Add(BigInteger.Parse(value));
    }
}

var bigNumbers = new List<BigInteger>
{
    new BigInteger(1), // constructor BigInteger(int)
    2222222222L,       // implicit operator BigInteger(long)
    3333333333UL,      // implicit operator BigInteger(ulong)
    { 4, 4, 4, 4, 4, 4, 4, 4 },               // extension Add(byte[])
    "55555555555555555555555555555555555555", // extension Add(string)
};

C # 7 додасть до підтримки кортежі, вбудовані в мову, хоча вони будуть іншого типу ( System.ValueTupleзамість цього). Тож було б добре додати перевантаження для кортежів цінності, щоб у вас була можливість використовувати їх також. На жаль, між ними не визначено неявних перетворень.

public static class ValueTupleListExtensions
{
    public static void Add<T1, T2>(this IList<Tuple<T1, T2>> list,
        ValueTuple<T1, T2> item) => list.Add(item.ToTuple());
}

Таким чином, ініціалізація списку буде виглядати ще приємніше.

var points = new List<Tuple<int, int, int>>
{
    (0, 0, 0),
    (1, 2, 3),
    (-1, 12, -73),
};

Але замість того, щоб переживати всю цю неприємність, може бути просто краще перейти до використання ValueTupleвиключно.

var points = new List<(int, int, int)>
{
    (0, 0, 0),
    (1, 2, 3),
    (-1, 12, -73),
};

Нарешті я гідно поглянув на це під час оновлення своєї бібліотеки до C # 6.0. Хоча це з першого погляду виглядає добре, я віддаю перевагу своєму раніше розміщеному рішенню над цим, оскільки мені здається, TupleList<int, string>.що він читає більше, ніж List<Tuple<int, string>>.
Стівен Єуріс

1
Це те, що псевдонім типу може виправити. Зазначений псевдонім не може бути загальним, що може бути кращим. using TupleList = System.Collections.Generic.List<System.Tuple<int, string>>;
Джефф Меркадо

Я створив Nuget Package з методами Add на ICollection, щоб заощадити іншим час необхідності підтримувати цей код. Ознайомтесь з пакунком: nuget.org/packages/Naos.Recipes.TupleInitializer Дивіться тут код: github.com/NaosProject/Naos.Recipes/blob/master/…. Це додасть файл cs у ваше рішення під ".Naos .Рецепти ", тому вам не доведеться перетягувати залежність від складання
SFun28

42

Ви можете зробити це, зателефонувавши конструктору кожен раз із трохи краще

var tupleList = new List<Tuple<int, string>>
{
    new Tuple<int, string>(1, "cow" ),
    new Tuple<int, string>( 5, "chickens" ),
    new Tuple<int, string>( 1, "airplane" )
};

6
Я не міг змусити оригінальний код працювати, тому я доповнив його тим, що, на мою, він повинен бути ... що може перевернути вашу думку щодо того, чи синтаксис "трохи кращий" зрештою :)
onedaywhen

5
принаймні використовуйте Tuple.Createзамість цього, і ви можете зробити аргументи типу
Дейв Кузіно,

28

Старе питання, але це те, що я зазвичай роблю, щоб зробити речі легшими для читання:

Func<int, string, Tuple<int, string>> tc = Tuple.Create;

var tupleList = new List<Tuple<int, string>>
{
    tc( 1, "cow" ),
    tc( 5, "chickens" ),
    tc( 1, "airplane" )
};

Працює і в старих версіях .NET!
Ed Bayiates

1

Super Duper Old Я знаю, але я би додав свою частину щодо використання Linq та продовження лямбда на методах із використанням C # 7. Я намагаюся використовувати названі кортежі в якості заміни для DTO та анонімних прогнозів при повторному використанні в класі. Так, для глузування та тестування вам все-таки потрібні заняття, але робити вбудовані речі та проходити в класі приємно мати цей новий варіант IMHO. Ви можете створити ім'я для них

  1. Пряма миттєвість
var items = new List<(int Id, string Name)> { (1, "Me"), (2, "You")};
  1. Вимкнено існуючу колекцію, і тепер ви можете повернути добре набрані кортежі, подібні до того, як раніше робили анонімні прогнози.
public class Hold
{
    public int Id { get; set; }
    public string Name { get; set; }
}

//In some method or main console app:
var holds = new List<Hold> { new Hold { Id = 1, Name = "Me" }, new Hold { Id = 2, Name = "You" } };
var anonymousProjections = holds.Select(x => new { SomeNewId = x.Id, SomeNewName = x.Name });
var namedTuples = holds.Select(x => (TupleId: x.Id, TupleName: x.Name));
  1. Пізніше використовуйте кортежі методами групування або використовуйте метод, щоб побудувати їх вбудованими в іншій логіці:
//Assuming holder class above making 'holds' object
public (int Id, string Name) ReturnNamedTuple(int id, string name) => (id, name);
public static List<(int Id, string Name)> ReturnNamedTuplesFromHolder(List<Hold> holds) => holds.Select(x => (x.Id, x.Name)).ToList();
public static void DoSomethingWithNamedTuplesInput(List<(int id, string name)> inputs) => inputs.ForEach(x => Console.WriteLine($"Doing work with {x.id} for {x.name}"));

var namedTuples2 = holds.Select(x => ReturnNamedTuple(x.Id, x.Name));
var namedTuples3 = ReturnNamedTuplesFromHolder(holds);
DoSomethingWithNamedTuplesInput(namedTuples.ToList());

Ти змушуєш мене відчувати себе старим. :) Можливо, мені щось тут не вистачає, але ініціалізація «затримок» зовсім не є короткою. Це був точний момент питання.
Стівен Євріс

@Steven Jeuris Ні, ти мав рацію, я скопіював і вставив неправильний біт коду для пункту 1. Потрібно пройти трохи повільніше, перш ніж натиснути «подати».
djangojazz

0

Чому подобаються кортежі? Це як анонімні типи: імен немає. Неможливо зрозуміти структуру даних.

Мені подобаються класичні заняття

class FoodItem
{
     public int Position { get; set; }
     public string Name { get; set; }
}

List<FoodItem> list = new List<FoodItem>
{
     new FoodItem { Position = 1, Name = "apple" },
     new FoodItem { Position = 2, Name = "kiwi" }
};

3
Як я вже зазначив: "Вони дозволяють швидко групувати релевантну інформацію разом без необхідності писати структуру чи клас для неї". У випадку, якщо цей клас буде використовуватися виключно в рамках одного методу як допоміжна структура даних, я вважаю за краще використовувати Tuples, щоб не заповнювати простір імен непотрібним класом.
Стівен Євріс

1
Ви думаєте про старший клас Tuple (не структура). C # 7 додав кортежі на основі структури (підкріплені новим типом ValueTuple), які МОЖУТЬ мати імена, щоб ви могли зрозуміти структуру даних
Стів Нілз

0

Один метод, на який я думаю, трохи легший, про який тут раніше не говорилося:

var asdf = new [] { 
    (Age: 1, Name: "cow"), 
    (Age: 2, Name: "bird")
}.ToList();

Я думаю, що це трохи чистіше, ніж:

var asdf = new List<Tuple<int, string>> { 
    (Age: 1, Name: "cow"), 
    (Age: 2, Name: "bird")
};

Це зводиться більше до смаку, чи віддаєте перевагу виразно згадувати типи, чи ні, так само марно varчи ні. Я особисто віддаю перевагу явному введенню тексту (коли він не дублюється, наприклад, у конструкторі).
Стівен Євріс

-4
    var colors = new[]
    {
        new { value = Color.White, name = "White" },
        new { value = Color.Silver, name = "Silver" },
        new { value = Color.Gray, name = "Gray" },
        new { value = Color.Black, name = "Black" },
        new { value = Color.Red, name = "Red" },
        new { value = Color.Maroon, name = "Maroon" },
        new { value = Color.Yellow, name = "Yellow" },
        new { value = Color.Olive, name = "Olive" },
        new { value = Color.Lime, name = "Lime" },
        new { value = Color.Green, name = "Green" },
        new { value = Color.Aqua, name = "Aqua" },
        new { value = Color.Teal, name = "Teal" },
        new { value = Color.Blue, name = "Blue" },
        new { value = Color.Navy, name = "Navy" },
        new { value = Color.Pink, name = "Pink" },
        new { value = Color.Fuchsia, name = "Fuchsia" },
        new { value = Color.Purple, name = "Purple" }
    };
    foreach (var color in colors)
    {
        stackLayout.Children.Add(
            new Label
            {
                Text = color.name,
                TextColor = color.value,
            });
        FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label))
    }

this is a Tuple<Color, string>

Як ви отримуєте синтаксис значення / імені з кортежем?
Андреас Рейфф

Компілятор розповідає, що анонімний тип не можна неявно перетворити на
Tuple
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.