Bagaimana cara mudah menginisialisasi daftar Tuples?

286

Saya suka tuple . Mereka memungkinkan Anda untuk dengan cepat mengelompokkan informasi yang relevan bersama tanpa harus menulis struct atau kelas untuk itu. Ini sangat berguna saat refactoring kode yang sangat lokal.

Menginisialisasi daftar mereka tampaknya agak berlebihan.

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

Apakah tidak ada cara yang lebih baik? Saya ingin solusi di sepanjang baris penginisialisasi Kamus .

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

Tidak bisakah kita menggunakan sintaksis yang sama?

Steven Jeuris
sumber
2
Dalam hal ini mengapa Anda tidak menggunakan kamus daripada daftar Tuples?
Ed S.
17
@ Ed S .: A Dictionarytidak mengizinkan kunci duplikat.
Steven Jeuris
2
@ Edit: Setiap kali itu bukan dua-tuple di mana satu item hashable / dapat dipesan dan unik.
Poin baiknya, tidak melihat kunci duplikat.
Ed S.

Jawaban:

279

c # 7.0 memungkinkan Anda melakukan ini:

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

Jika Anda tidak membutuhkan List, tetapi hanya sebuah array, Anda dapat melakukannya:

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

Dan jika Anda tidak menyukai "Item1" dan "Item2", Anda dapat melakukannya:

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

atau untuk sebuah array:

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

yang memungkinkan Anda melakukannya: tupleList[0].IndexdantupleList[0].Name

Kerangka 4.6.2 dan di bawah

Anda harus menginstal System.ValueTupledari Package Manager Nuget.

Kerangka 4.7 dan di atas

Itu dibangun ke dalam kerangka kerja. Jangan tidak menginstal System.ValueTuple. Bahkan, hapus dan hapus dari direktori bin.

Catatan: Dalam kehidupan nyata, saya tidak akan bisa memilih antara sapi, ayam atau pesawat. Saya akan benar-benar terkoyak.

toddmo
sumber
2
Bisakah ini digunakan pada .net core 2.0?
Алекса Јевтић
2
@ АлексаЈевтић, System.ValueTuplemendukung core 2.0. Tapi, cobalah tanpa Nuget terlebih dahulu, karena paket yang tidak perlu dapat menyebabkan masalah. Jadi satu atau lain cara, ya. Gunakan c # v7 atau lebih besar jika memungkinkan.
toddmo
1
Jawaban yang sangat sempurna! Terima kasih!
Vitox
224

Iya! Ini mungkin .

The {} sintaks dari koleksi initializer bekerja pada setiap IEnumerable jenis yang memiliki Tambah metode dengan jumlah yang benar argumen. Tanpa mempedulikan cara kerjanya di balik sampul, itu berarti Anda cukup memperluas dari Daftar <T> , menambahkan metode Tambahkan kustom untuk menginisialisasi T Anda , dan Anda selesai!

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

Ini memungkinkan Anda melakukan hal berikut:

var groceryList = new TupleList<int, string>
{
    { 1, "kiwi" },
    { 5, "apples" },
    { 3, "potatoes" },
    { 1, "tomato" }
};
Steven Jeuris
sumber
39
Kelemahan adalah menulis kelas. Seperti Anda, saya suka tupel karena saya tidak harus menulis kelas atau struct.
selamat tinggal
2
@ BillW Tidak terlalu yakin ini adalah pos yang tepat, ... tapi saya tahu Jon Skeet telah menulis tentang itu sebelumnya .
Steven Jeuris
1
apakah ini berfungsi groceryList[0] == groceryList[1]atau apakah perbandingan harus dilaksanakan?
Byyo
Saya membuat Paket Nuget dengan metode Tambahkan pada ICollection untuk menghemat waktu orang lain karena harus menjaga kode ini. Lihat di sini untuk paket: nuget.org/packages/Naos.Recipes.TupleInitializers Lihat di sini untuk kode: github.com/NaosProject/Naos.Recipes/blob/master/... Ini akan menyertakan file cs dalam solusi Anda di bawah ".Naos Folder .Recipes ", jadi Anda tidak perlu menyeret ketergantungan perakitan
SFun28
83

C # 6 menambahkan fitur baru hanya untuk ini: ekstensi Tambahkan metode. Ini selalu dimungkinkan untuk VB.net tetapi sekarang tersedia dalam C #.

Sekarang Anda tidak perlu menambahkan Add()metode ke kelas secara langsung, Anda dapat menerapkannya sebagai metode ekstensi. Saat memperluas jenis enumerable apa pun dengan Add()metode, Anda akan dapat menggunakannya dalam ekspresi initializer koleksi. Jadi, Anda tidak harus menurunkan daftar lagi secara eksplisit ( seperti yang disebutkan dalam jawaban lain ), Anda dapat memperpanjangnya.

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

Ini akan memungkinkan Anda untuk melakukan ini pada kelas apa pun yang mengimplementasikan 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 },
};

Tentu saja Anda tidak dibatasi untuk memperluas koleksi tupel, itu bisa untuk koleksi jenis tertentu yang Anda inginkan untuk sintaks khusus.

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 akan menambahkan dukungan untuk tuple yang dibangun ke dalam bahasa, meskipun mereka akan dari jenis yang berbeda ( System.ValueTuplesebagai gantinya). Jadi, sebaiknya tambahkan kelebihan untuk tuple nilai sehingga Anda memiliki opsi untuk menggunakannya juga. Sayangnya, tidak ada konversi implisit yang ditentukan di antara keduanya.

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

Dengan cara ini, inisialisasi daftar akan terlihat lebih bagus.

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

Tetapi alih-alih melalui semua masalah ini, mungkin lebih baik beralih ke menggunakan ValueTuplesecara eksklusif.

var points = new List<(int, int, int)>
{
    (0, 0, 0),
    (1, 2, 3),
    (-1, 12, -73),
};
Jeff Mercado
sumber
Saya akhirnya melihat ini sambil memperbarui perpustakaan saya ke C # 6.0. Meskipun sekilas terlihat bagus, saya lebih suka solusi saya diposting sebelumnya karena saya menemukan TupleList<int, string>.lebih mudah dibaca daripada List<Tuple<int, string>>.
Steven Jeuris
1
Itu sesuatu yang bisa diperbaiki oleh tipe alias. Memang alias itu sendiri tidak boleh generik yang mungkin lebih disukai. using TupleList = System.Collections.Generic.List<System.Tuple<int, string>>;
Jeff Mercado
Saya membuat Paket Nuget dengan metode Tambahkan pada ICollection untuk menghemat waktu orang lain karena harus menjaga kode ini. Lihat di sini untuk paket: nuget.org/packages/Naos.Recipes.TupleInitializers Lihat di sini untuk kode: github.com/NaosProject/Naos.Recipes/blob/master/... Ini akan menyertakan file cs dalam solusi Anda di bawah ".Naos Folder .Recipes ", jadi Anda tidak perlu menyeret ketergantungan perakitan
SFun28
42

Anda dapat melakukan ini dengan memanggil konstruktor setiap kali dengan sedikit lebih baik

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" )
};
Kevin Holditch
sumber
6
Saya tidak bisa mendapatkan kode asli untuk bekerja, jadi saya telah mengubahnya menjadi apa yang saya pikir seharusnya ... yang dapat membalikkan pendapat Anda tentang apakah sintaks "sedikit lebih baik" setelah semua :)
onedaywhen
5
setidaknya gunakan Tuple.Createsebagai gantinya dan Anda dapat menyimpulkan argumen tipe
Dave Cousineau
28

Pertanyaan lama, tapi inilah yang biasanya saya lakukan untuk membuat hal-hal sedikit lebih mudah dibaca:

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" )
};
Asad Saeeduddin
sumber
Bekerja pada versi lama .NET, juga!
Ed Bayiates
1

Super Duper Tua Saya tahu tetapi saya akan menambahkan bagian saya tentang menggunakan Linq dan kelanjutan lambdas tentang metode dengan menggunakan C # 7. Saya mencoba menggunakan tuple bernama sebagai pengganti untuk DTO dan proyeksi anonim ketika digunakan kembali di kelas. Ya untuk mengejek dan menguji Anda masih perlu kelas tetapi melakukan hal-hal inline dan berkeliling di kelas bagus untuk memiliki opsi IMHO ini lebih baru. Anda dapat membuat instantiate dari

  1. Instansiasi langsung
var items = new List<(int Id, string Name)> { (1, "Me"), (2, "You")};
  1. Di luar koleksi yang ada, dan sekarang Anda dapat mengembalikan tupel yang diketik dengan baik, mirip dengan bagaimana proyeksi anonim dulu dilakukan.
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. Gunakan kembali tupel nanti dengan metode pengelompokan atau gunakan metode untuk membangunnya sesuai logika lain
//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
sumber
Anda membuat saya merasa tua. :) Saya mungkin kehilangan sesuatu di sini, tetapi menginisialisasi 'memegang' tidak ringkas sama sekali. Ini adalah titik yang tepat dari pertanyaan itu.
Steven Jeuris
@ Seven Jeuris Tidak, Anda benar saya menyalin dan menempelkan sedikit kode yang salah untuk Poin 1. Perlu sedikit lebih lambat sebelum saya menekan 'kirim'.
djangojazz
0

Mengapa suka tupel? Ini seperti jenis anonim: tidak ada nama. Tidak dapat memahami struktur data.

Saya suka kelas klasik

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" }
};
Alexey Obukhov
sumber
3
Seperti yang saya katakan: "Mereka memungkinkan Anda untuk dengan cepat mengelompokkan informasi yang relevan bersama tanpa harus menulis struct atau kelas untuk itu." Jika kelas ini hanya akan digunakan dalam metode tunggal sebagai struktur data pembantu, saya lebih suka menggunakan Tuples, jadi jangan mengisi sebuah namespace dengan kelas yang tidak perlu.
Steven Jeuris
1
Anda memikirkan kelas Tuple yang lebih lama (bukan struct). C # 7 menambahkan tuple berbasis struct (didukung oleh tipe ValueTuple baru) yang BISA memiliki nama, sehingga Anda dapat memahami struktur data
Steve Niles
0

Satu teknik yang saya pikir sedikit lebih mudah dan itu belum disebutkan sebelumnya di sini:

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

Saya pikir itu sedikit lebih bersih daripada:

var asdf = new List<Tuple<int, string>> { 
    (Age: 1, Name: "cow"), 
    (Age: 2, Name: "bird")
};
Alex Dresko
sumber
Yang lebih terasa adalah apakah Anda lebih suka menyebutkan jenis secara eksplisit atau tidak, sia-sia untuk digunakan varatau tidak. Saya pribadi lebih suka mengetik secara eksplisit (ketika tidak diduplikasi dalam mis. Konstruktor).
Steven Jeuris
-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>
Egi Messito
sumber
Bagaimana Anda mendapatkan sintaks nilai / nama dengan Tuple?
Andreas Reiff
Compiler mengatakan bahwa tipe anonim tidak secara implisit dapat dikonversi menjadi Tuple
cthepenier