Mengapa saya bisa menginisialisasi Daftar seperti array di C #?

131

Hari ini saya terkejut menemukan itu di C # yang bisa saya lakukan:

List<int> a = new List<int> { 1, 2, 3 };

Mengapa saya bisa melakukan ini? Konstruktor apa yang disebut? Bagaimana saya bisa melakukan ini dengan kelas saya sendiri? Saya tahu bahwa ini adalah cara untuk menginisialisasi array tetapi array adalah item bahasa dan Daftar adalah objek sederhana ...

Ignacio Soler Garcia
sumber
1
Pertanyaan ini mungkin bisa membantu: stackoverflow.com/questions/1744967/…
Bob Kaufman
10
Cukup keren ya? Anda dapat melakukan kode yang sangat mirip untuk menginisialisasi kamus juga:{ { "key1", "value1"}, { "key2", "value2"} }
danludwig
Dari tampilan lain array ini seperti sintaks inisialisasi memberi kita pengingat bahwa tipe data yang mendasari a List<int>sebenarnya array saja. Saya benci tim C # karena mereka tidak menyebutkan nama ArrayList<T>yang terdengar begitu jelas dan alami.
RBT

Jawaban:

183

Ini adalah bagian dari sintaks initializer koleksi di .NET. Anda dapat menggunakan sintaks ini pada koleksi yang Anda buat selama:

  • Ini mengimplementasikan IEnumerable(lebih disukai IEnumerable<T>)

  • Itu memiliki metode bernama Add(...)

Apa yang terjadi adalah konstruktor default dipanggil, dan kemudian Add(...)dipanggil untuk setiap anggota penginisialisasi.

Dengan demikian, kedua blok ini kira-kira identik:

List<int> a = new List<int> { 1, 2, 3 };

Dan

List<int> temp = new List<int>();
temp.Add(1);
temp.Add(2);
temp.Add(3);
List<int> a = temp;

Anda dapat memanggil konstruktor alternatif jika Anda mau, misalnya untuk mencegah ukuran yang terlalu besar List<T>selama pertumbuhan, dll:

// Notice, calls the List constructor that takes an int arg
// for initial capacity, then Add()'s three items.
List<int> a = new List<int>(3) { 1, 2, 3, }

Perhatikan bahwa Add()metode ini tidak perlu mengambil satu item, misalnya Add()metode untuk Dictionary<TKey, TValue>mengambil dua item:

var grades = new Dictionary<string, int>
    {
        { "Suzy", 100 },
        { "David", 98 },
        { "Karen", 73 }
    };

Secara kasar identik dengan:

var temp = new Dictionary<string, int>();
temp.Add("Suzy", 100);
temp.Add("David", 98);
temp.Add("Karen", 73);
var grades = temp;

Jadi, untuk menambahkan ini ke kelas Anda sendiri, yang perlu Anda lakukan, seperti yang disebutkan, adalah mengimplementasikan IEnumerable(sekali lagi, lebih disukai IEnumerable<T>) dan membuat satu atau lebih Add()metode:

public class SomeCollection<T> : IEnumerable<T>
{
    // implement Add() methods appropriate for your collection
    public void Add(T item)
    {
        // your add logic    
    }

    // implement your enumerators for IEnumerable<T> (and IEnumerable)
    public IEnumerator<T> GetEnumerator()
    {
        // your implementation
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Kemudian Anda dapat menggunakannya seperti koleksi BCL lakukan:

public class MyProgram
{
    private SomeCollection<int> _myCollection = new SomeCollection<int> { 13, 5, 7 };    

    // ...
}

(Untuk informasi lebih lanjut, lihat MSDN )

James Michael Hare
sumber
29
Jawaban yang bagus, meskipun saya perhatikan bahwa tidak terlalu akurat untuk mengatakan "persis identik" dalam contoh pertama Anda. Itu persis identik dengan List<int> temp = new List<int>(); temp.Add(1); ... List<int> a = temp; Yaitu, avariabel tidak diinisialisasi sampai setelah semua add dipanggil. Kalau tidak, itu legal untuk melakukan sesuatu List<int> a = new List<int>() { a.Count, a.Count, a.Count };yang gila untuk dilakukan.
Eric Lippert
1
@ user606723: Tidak ada perbedaan nyata antara List<int> a; a = new List<int>() { a.Count };danList<int> a = new List<int>() { a.Count };
Joren
4
@Joren: Anda benar; sebenarnya spesifikasi C # menyatakan bahwa T x = y;sama dengan T x; x = y;, Fakta ini dapat menyebabkan beberapa situasi aneh. Misalnya, int x = M(out x) + x;sangat legal karena int x; x = M(out x) + x;legal.
Eric Lippert
1
@JamesMichaelHare tidak perlu diimplementasikan IEnumerable<T>; non-generik IEnumerablesudah cukup untuk memungkinkan penggunaan sintaks initializer koleksi.
phoog
2
Poin Eric penting jika Anda menggunakan semacam koleksi yang mengimplementasikan IDisposable. using (var x = new Something{ 1, 2 })tidak akan membuang objek jika salah satu Addpanggilan gagal.
porges
11

Ini disebut gula sintaksis .

List<T> adalah kelas "sederhana", tetapi kompiler memberikan perlakuan khusus untuknya agar hidup Anda lebih mudah.

Yang ini disebut initializer koleksi . Anda perlu menerapkan IEnumerable<T>dan Addmetode.

Krizz
sumber
8

Menurut Spesifikasi C # Versi 3.0 "Objek koleksi yang digunakan penginisialisasi koleksi harus dari tipe yang mengimplementasikan System.Collections.Generic.ICollection untuk tepat satu T."

Namun, informasi ini tampaknya tidak akurat pada tulisan ini; lihat klarifikasi Eric Lippert dalam komentar di bawah.

Olivier Jacot-Descombes
sumber
10
Halaman itu tidak akurat. Terima kasih sudah membawa ini padaku. Itu pasti beberapa dokumentasi pra-rilis awal yang entah bagaimana tidak pernah dihapus. Desain aslinya membutuhkan ICollection, tapi itu bukan desain akhir yang kami setujui.
Eric Lippert
1
Terimakasih atas klarifikasinya.
Olivier Jacot-Descombes
6

Ini berfungsi berkat inisialisasi koleksi yang pada dasarnya memerlukan koleksi untuk menerapkan metode Tambahkan dan itu akan bekerja untuk Anda.

vc 74
sumber
6

Hal keren lainnya tentang inisialisasi koleksi adalah Anda dapat memiliki banyak kelebihan Addmetode dan Anda dapat memanggil mereka semua di inisialisasi yang sama! Misalnya ini berfungsi:

public class MyCollection<T> : IEnumerable<T>
{
    public void Add(T item, int number)
    {

    }
    public void Add(T item, string text) 
    {

    }
    public bool Add(T item) //return type could be anything
    {

    }
}

var myCollection = new MyCollection<bool> 
{
    true,
    { false, 0 },
    { true, "" },
    false
};

Itu memanggil kelebihan yang benar. Juga, hanya mencari metode dengan nama Add, tipe pengembalian bisa apa saja.

nawfal
sumber
0

Array seperti sintaks sedang diubah dalam serangkaian Add()panggilan.

Untuk melihat ini dalam contoh yang jauh lebih menarik, pertimbangkan kode berikut di mana saya melakukan dua hal menarik yang terdengar ilegal pertama di C #, 1) mengatur properti readonly, 2) mengatur daftar dengan array seperti inisialisasi.

public class MyClass
{   
    public MyClass()
    {   
        _list = new List<string>();
    }
    private IList<string> _list;
    public IList<string> MyList 
    { 
        get
        { 
            return _list;
        }
    }
}
//In some other method
var sample = new MyClass
{
    MyList = {"a", "b"}
};

Kode ini akan bekerja dengan sempurna, walaupun 1) MyList hanya bisa dibaca dan 2) Saya menetapkan daftar dengan penginisialisasi array.

Alasan mengapa ini bekerja, adalah karena dalam kode yang merupakan bagian dari objek intializer kompiler selalu mengubah {}sintaksis seperti ke serangkaian Add()panggilan yang sangat legal bahkan pada bidang yang hanya dibaca.

halo yoel
sumber