C#: Inicializátory polí a kolekcí

Nedávno jsem dostal několik otázek ohledně inicializátorů polí a kolekcí v C#. Protože se to může hodit i někomu dalšímu, rozhodl jsem se sepsat odpověď touto formou.

Veškeré informace zmíněné dále jsou platné pro C# verze 3.0 a vyšší.

Pole

Jednorozměrné pole se standardně deklaruje a inicializuje takto:

string[] array = new string[3]; //Pole o třech řetezcích

Hodnoty se přiřazují prostým zápisem na patričný index:

array[0] = "alpha";
array[1] = "bravo";
array[2] = "charlie";

Pomocí inicializátoru pole (array initializer) lze celý předchozí kód zapsat jedním příkazem:

string[] array = new string[3] {"alpha", "bravo", "charlie"};
var array = new string[3] {"alpha", "bravo", "charlie"}; //Alternativa s implicitně typovanou proměnnou

Inicializátor vždy vytvoří pole o stejném počtu prvků, kolik jich je zadáno ve složených závorkách, takže v přechozím příkladu nelze zadat jiné číslo než tři. Počet prvků tedy vůbec není třeba uvádět.

var array = new string[] {"alpha", "bravo", "charlie"}; //Kompilátor ví, že má vytvořit pole o třech prvcích

C# umožňuje tento příkaz dále zjednodušit – kompilátor totiž dokáže odvodit typ pole z typů jsou použitých v inicializátoru, takže nemusíte uvádět ani typ pole:

var array = new[] {"alpha", "bravo", "charlie"}; //Pole řetězců
var array = new[] {"alpha", "bravo", new Object()}; //Pole objektů
var array = new[] {"alpha", "bravo", 'c'}; //Chyba - žádný "nejlepší" typ ("No best type found for implicitly-typed array")

V posledním případě by kompilátor musel zapouzdřit znak ‘c’ do objektu a vytvořit pole objektů, což raději nechce udělat automaticky. Snadno to ale vyřešíte uvedením typu pole.

Ve zjednodušování lze jít ještě dále a vynechat i operátor new[] – kompilátor ví, že výstupem inicializátoru bude pole. Nelze ale zároveň deklarovat proměnnou jako var a nechat kompilátor typ odvodit.

string[] array = {"alpha", "bravo", "charlie"}; //OK
var array = {"alpha", "bravo", "charlie"}; //Nelze

Tuto syntaxi je navíc možné použít pouze při deklaraci lokální proměnné nebo pole (field), nemůžete je použít například při definování argumentu metody.

String.Concat( {"a", "b"} ); //Nelze
String.Concat( new[] {"a", "b"} ); //OK

Vícerozměrná pole fungují velmi podobně jako jednorozměrná:

string[,] array =
{
    {"a", "b"},
    {"c", "d"}
}

Stejný inticializátor ale nemůžete použít pro “zubaté” pole (pole polí), vnitřní pole musíte vytvořit pomocí operátoru new.

string[][] array =
{
    new[] {"a", "b"},
    new[] {"c", "d"}
}

Kolekce

Podobná podpora pro inicializaci položek je k dispozici pro všechny kolekce – respektive pro všechny typy implementující rozhraní IEnumerable a poskytující veřejnou metodu Add. V případě kolekcí je vždy nutné volat konstruktor, ale místo volání Add stačí zadat hodnoty ve složených závorkách.

var list = new List<string>() { "alpha", "bravo", "charlie" };

Při volání konstruktoru bez parametrů lze kulaté závorky vynechat.

var list = new List<string> { "a", "b", "c" };

Nicméně je možné využít i parametrický konstruktor – následující příklad vytvoří novou instanci s prvky z existující kolekce a pak přidá další prvky v inicializátoru.

var list2 = new List<string>(list) { "d", "e" };
list2.ForEach( s => Console.Write(s)); //Zobrazí "abcde"

Pokud má metoda Add více parametrů, je třeba je uzavřít do dodatečných složených závorek. Následující kód přidává dvě položky do slovníku.

var dict = new Dictionary<int, string>
{
    { 1, "alpha" },
    { 2, "bravo" }
};

Každý prvek v inicializátoru může navíc používat jiné přetížení metody Add, pokud je k dispozici. Například NameValueCollection (z jmenného prostoru System.Collection.Specialized) nabízí metody Add(String, String) a Add(NameValueCollection). Inicializátor proměnné nvc2 využívá obě dvě:

NameValueCollection nvc1 = new NameValueCollection { {"a", "b"} };
NameValueCollection nvc2 = new NameValueCollection
{
    {"c", "d"},
    nvc1
};

Inicializátory sice nepřináší nic, co by se nedalo napsat jinak, ale díky nim lze psát kratší a přehlednější kód – a to není málo.