Как легко инициализировать список кортежей?

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
В таком случае, почему бы вам не использовать словарь вместо списка кортежей?
Эд С.
17
@Ed S .: A Dictionaryне позволяет дублировать ключи.
Стивен Джеурис
2
@EdS .: Каждый раз, когда не один кортеж, один элемент может быть хэшируемым / упорядоченным и уникальным.
Хороший вопрос, не заметил дубликат ключа.
Эд С.

Ответы:

282

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

Framework 4.6.2 и ниже

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

Framework 4.7 и выше

Он встроен в рамки. Вы не устанавливать System.ValueTuple. Фактически удалите его и удалите из каталога bin.

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

toddmo
источник
2
Это можно использовать на ядре .net 2.0?
Алекса Јевтић
2
@ АлексаЈевтић, System.ValueTupleподдерживает ядро ​​2.0. Но попробуйте сначала без Nuget, так как ненужные пакеты могут вызвать проблемы. Так или иначе, да. Используйте c # v7 или выше, если это возможно.
Toddmo
1
Абсолютно идеальный ответ! Спасибо!
Витокс
224

Да! Это возможно .

{} Синтаксис коллекции инициализатор работает на любом IEnumerable типа , который имеет Add метод с правильным количеством аргументов. Не заботясь о том, как это работает под прикрытием, это означает, что вы можете просто выйти из List <T> , добавить пользовательский метод Add для инициализации вашего 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]или сравнение должно быть реализовано?
Byyo
Я создал пакет Nuget с методами Add в ICollection, чтобы сэкономить другим время на поддержание этого кода. Смотрите здесь для пакета: nuget.org/packages/Naos.Recipes.TupleInitializers Смотрите здесь для кода: github.com/NaosProject/Naos.Recipes/blob/master/… Это будет включать файл CS в вашем решении под ".Naos .Recipes ", так что вам не нужно перетаскивать зависимость сборки
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 с методами Add в ICollection, чтобы сэкономить другим время на поддержание этого кода. Смотрите здесь для пакета: nuget.org/packages/Naos.Recipes.TupleInitializers Смотрите здесь для кода: github.com/NaosProject/Naos.Recipes/blob/master/… Это будет включать файл CS в вашем решении под ".Naos .Recipes ", так что вам не нужно перетаскивать зависимость сборки
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
Я не смог заставить работать оригинальный код, поэтому я изменил его так, как мне кажется… это может изменить ваше мнение о том, что синтаксис «немного лучше» в конце концов :)
день, когда
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!
Эд Байятес
1

Super Duper Old Я знаю, но я бы добавил свою статью об использовании Linq и продолжении лямбда-выражений для методов с использованием C # 7. Я пытаюсь использовать именованные кортежи в качестве замены для DTO и анонимных проекций при повторном использовании в классе. Да, для насмешек и тестирования вам все еще нужны классы, но делать что-то встроенное и проходить в классе хорошо, если у вас есть этот новый вариант ИМХО. Вы можете создать их экземпляр из

  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());
djangojazz
источник
Ты заставляешь меня чувствовать себя старым. :) Я мог бы что-то здесь упустить, но инициализация 'hold' совсем не лаконична. Это была точная точка вопроса.
Стивен Джеурис
@ 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
Как я сказал: «Они позволяют вам быстро сгруппировать соответствующую информацию без необходимости писать структуру или класс для нее». В случае, если этот класс будет использоваться исключительно в рамках одного метода в качестве вспомогательной структуры данных, я предпочитаю использовать кортежи, чтобы не заполнять пространство имен ненужным классом.
Стивен Джеурис
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
cthepenier