Dlaczego mogę zainicjować listę, taką jak tablica w C #?


131

Dziś zdziwiło mnie, że w C # potrafię:

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

Dlaczego mogę to zrobić? Jaki konstruktor się nazywa? Jak mogę to zrobić z własnymi zajęciami? Wiem, że to jest sposób na inicjalizację tablic, ale tablice to elementy języka, a listy to proste obiekty ...


1
To pytanie może być pomocne: stackoverflow.com/questions/1744967/…
Bob Kaufman

10
Całkiem niesamowite, co? Możesz zrobić bardzo podobny kod, aby zainicjować słowniki:{ { "key1", "value1"}, { "key2", "value2"} }
danludwig

Z innego punktu widzenia ta tablica, podobnie jak składnia inicjalizacji, przypomina nam, że podstawowy typ danych a List<int>jest w rzeczywistości tylko tablicą. Nienawidzę zespołu C # za to, że nie nazywają go, ArrayList<T>co brzmi tak oczywiste i naturalne.
RBT

Odpowiedzi:


183

Jest to część składni inicjatora kolekcji w .NET. Tej składni możesz używać w każdej tworzonej kolekcji, o ile:

  • Wdraża IEnumerable(najlepiej IEnumerable<T>)

  • Ma metodę o nazwie Add(...)

Dzieje się tak, gdy wywoływany jest domyślny konstruktor, a następnie Add(...)jest wywoływany dla każdego elementu członkowskiego inicjatora.

Zatem te dwa bloki są z grubsza identyczne:

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

I

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

Ty można nazwać alternatywną konstruktora, jeśli chcesz, na przykład, aby zapobiec nadmiernej doborze List<T>podczas uprawy, itp:

// 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, }

Zwróć uwagę, że Add()metoda nie musi uwzględniać jednego elementu, na przykład Add()metoda dla Dictionary<TKey, TValue>wymaga dwóch elementów:

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

Jest mniej więcej identyczny z:

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

Tak więc, aby dodać to do swojej własnej klasy, wszystko, co musisz zrobić, jak wspomniano, to zaimplementować IEnumerable(ponownie, najlepiej IEnumerable<T>) i utworzyć jedną lub więcej Add()metod:

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();
    }
}

Następnie możesz go używać tak, jak robią to kolekcje BCL:

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

    // ...
}

(Aby uzyskać więcej informacji, zobacz MSDN )


29
Dobra odpowiedź, chociaż zauważam, że stwierdzenie „dokładnie identyczne” w pierwszym przykładzie nie jest całkiem dokładne. To jest dokładnie identyczny List<int> temp = new List<int>(); temp.Add(1); ... List<int> a = temp; Oznacza to, że azmienna nie jest zainicjowana, aż po cały dodaje nazywa. W przeciwnym razie legalne byłoby zrobienie czegoś takiego, List<int> a = new List<int>() { a.Count, a.Count, a.Count };co jest szalone.
Eric Lippert

1
@ user606723: Nie ma żadnej różnicy między List<int> a; a = new List<int>() { a.Count };iList<int> a = new List<int>() { a.Count };
Joren

4
@Joren: Masz rację; w rzeczywistości specyfikacja C # stwierdza, że T x = y;jest to to samo co T x; x = y;, Ten fakt może prowadzić do dziwnych sytuacji. Na przykład int x = M(out x) + x;jest całkowicie legalny, ponieważ int x; x = M(out x) + x;jest legalny.
Eric Lippert

1
@JamesMichaelHare nie trzeba implementować IEnumerable<T>; nieogólna IEnumerablewystarczy, aby umożliwić użycie składni inicjatora kolekcji.
phoog

2
Uwaga Erica jest ważna, jeśli używasz jakiejś kolekcji, która implementuje IDisposable. using (var x = new Something{ 1, 2 })nie usunie obiektu, jeśli jedno z Addwywołań się nie powiedzie.
porges


8

Zgodnie ze specyfikacją C # w wersji 3.0 „Obiekt kolekcji, do którego zastosowano inicjator kolekcji, musi być typu implementującego System.Collections.Generic.ICollection dla dokładnie jednego T.”

Jednak te informacje wydają się być niedokładne w chwili pisania tego tekstu; zobacz wyjaśnienie Erica Lipperta w komentarzach poniżej.


10
Ta strona nie jest dokładna. Dziękuję za zwrócenie mi na to uwagi. To musi być jakaś wstępna dokumentacja przedpremierowa, która w jakiś sposób nigdy nie została usunięta. Oryginalny projekt miał wymagać ICollection, ale nie jest to ostateczny projekt, na który się zdecydowaliśmy.
Eric Lippert

1
Dziękuję za wyjaśnienie.
Olivier Jacot-Descombes


6

Kolejną fajną rzeczą dotyczącą inicjatorów kolekcji jest to, że możesz mieć wiele przeciążeń Addmetody i możesz je wywoływać w tym samym inicjatorze! Na przykład to działa:

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

Wywołuje prawidłowe przeciążenia. Ponadto szuka tylko metody o nazwie Add, typem zwracanym może być wszystko.


0

Składnia przypominająca tablicę jest zmieniana w serii Add()wywołań.

Aby zobaczyć to w dużo bardziej interesującym przykładzie, rozważmy następujący kod, w którym robię dwie interesujące rzeczy, które najpierw brzmią nielegalnie w C #, 1) ustawiając właściwość readonly, 2) ustawiając listę z tablicą taką jak inicjator.

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"}
};

Ten kod będzie działał idealnie, chociaż 1) MyList jest tylko do odczytu i 2) ustawiam listę za pomocą inicjatora tablicy.

Powodem, dla którego to działa, jest to, że w kodzie, który jest częścią inicjalizatora obiektów, kompilator zawsze zmienia dowolną {}podobną składnię na serię Add()wywołań, które są całkowicie legalne nawet w polu tylko do odczytu.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.