Sdílet prostřednictvím


Inicializátory objektů a kolekcí (průvodce programováním v C#)

Jazyk C# umožňuje vytvořit instanci objektu nebo kolekce a provádět přiřazení členů v jednom příkazu.

Inicializátory objektů

Inicializátory objektů umožňují při vytváření přiřazovat hodnoty všem přístupným polím nebo vlastnostem objektu. Nemusíte vyvolat konstruktor a pak použít příkazy přiřazení. Syntaxe inicializátoru objektů umožňuje zadat argumenty pro konstruktor nebo vynechat argumenty a závorky. Pokyny k používání inicializátorů objektů konzistentně najdete v tématu Použití inicializátorů objektů (pravidlo stylu IDE0017). Následující příklad ukazuje, jak použít inicializátor objektů s pojmenovaným typem Cata jak vyvolat konstruktor bez parametrů. Všimněte si použití automaticky implementovaných vlastností ve Cat třídě. Další informace naleznete v tématu Automaticky implementované vlastnosti.

public class Cat
{
    // Automatically implemented properties.
    public int Age { get; set; }
    public string? Name { get; set; }

    public Cat()
    {
    }

    public Cat(string name)
    {
        this.Name = name;
    }
}
Cat cat = new Cat { Age = 10, Name = "Fluffy" };
Cat sameCat = new Cat("Fluffy"){ Age = 10 };

Syntaxe inicializátoru objektů umožňuje vytvořit instanci a přiřadit nově vytvořený objekt s jeho přiřazenými vlastnostmi k proměnné v přiřazení.

Počínaje vlastnostmi vnořeného objektu můžete použít syntaxi inicializátoru objektů bez klíčového new slova. Tato syntaxe Property = { ... }umožňuje inicializovat členy existujících vnořených objektů, což je užitečné s vlastnostmi jen pro čtení. Další informace naleznete v tématu Inicializátory objektů s vlastnostmi typu třídy.

Inicializátory objektů mohou kromě přiřazování polí a vlastností nastavit indexery. Vezměte v úvahu tuto základní Matrix třídu:

public class Matrix
{
    private double[,] storage = new double[3, 3];

    public double this[int row, int column]
    {
        // The embedded array will throw out of range exceptions as appropriate.
        get { return storage[row, column]; }
        set { storage[row, column] = value; }
    }
}

Matici identit můžete inicializovat pomocí následujícího kódu:

var identity = new Matrix
{
    [0, 0] = 1.0,
    [0, 1] = 0.0,
    [0, 2] = 0.0,

    [1, 0] = 0.0,
    [1, 1] = 1.0,
    [1, 2] = 0.0,

    [2, 0] = 0.0,
    [2, 1] = 0.0,
    [2, 2] = 1.0,
};

Jakýkoli přístupný indexer, který obsahuje přístupnou setter, lze použít jako jeden z výrazů v inicializátoru objektů bez ohledu na počet nebo typy argumentů. Argumenty indexu tvoří levou stranu přiřazení a hodnota je pravou stranou výrazu. Například následující inicializátory jsou platné, pokud IndexersExample mají příslušné indexery:

var thing = new IndexersExample
{
    name = "object one",
    [1] = '1',
    [2] = '4',
    [3] = '9',
    Size = Math.PI,
    ['C',4] = "Middle C"
}

Aby se předchozí kód zkompiloval, IndexersExample musí mít typ následující členy:

public string name;
public double Size { set { ... }; }
public char this[int i] { set { ... }; }
public string this[char c, int i] { set { ... }; }

Inicializátory objektů s anonymními typy

I když můžete použít inicializátory objektů v libovolném kontextu, jsou zvláště užitečné ve výrazech Language-Integrated Query (LINQ). Výrazy dotazů často používají anonymní typy, které můžete inicializovat pouze pomocí inicializátoru objektů, jak je znázorněno v následující deklaraci.

var pet = new { Age = 10, Name = "Fluffy" };

Pomocí anonymních typů select může klauzule ve výrazu dotazu LINQ transformovat objekty původní sekvence na objekty, jejichž hodnota a tvar se liší od původního. Můžete chtít uložit pouze část informací z každého objektu v posloupnosti. V následujícím příkladu předpokládejme, že objekt produktu (p) obsahuje mnoho polí a metod a že vás zajímá pouze vytvoření posloupnosti objektů, které obsahují název produktu a jednotkovou cenu.

var productInfos =
    from p in products
    select new { p.ProductName, p.UnitPrice };

Při spuštění tohoto dotazu productInfos obsahuje proměnná posloupnost objektů, ke kterým můžete přistupovat v foreach příkazu, jak je znázorněno v tomto příkladu:

foreach(var p in productInfos){...}

Každý objekt v novém anonymním typu má dvě veřejné vlastnosti, které přijímají stejné názvy jako vlastnosti nebo pole v původním objektu. Pole můžete přejmenovat také při vytváření anonymního typu. Následující příklad přejmenuje UnitPrice pole na Price.

select new {p.ProductName, Price = p.UnitPrice};

Inicializátory objektů s modifikátorem required

Pomocí klíčového required slova vynucujte volajícím nastavit hodnotu vlastnosti nebo pole pomocí inicializátoru objektů. Jako parametry konstruktoru nemusíte nastavovat požadované vlastnosti. Kompilátor zajistí, že všichni volající tyto hodnoty inicializují.

public class Pet
{
    public required int Age;
    public string Name;
}

// `Age` field is necessary to be initialized.
// You don't need to initialize `Name` property
var pet = new Pet() { Age = 10};

// Compiler error:
// Error CS9035 Required member 'Pet.Age' must be set in the object initializer or attribute constructor.
// var pet = new Pet();

Je to typický postup, jak zaručit, že je objekt správně inicializován, zejména pokud máte více polí nebo vlastností ke správě a nechcete je zahrnout všechny do konstruktoru.

Inicializátory objektů s přístupovým prvkem init

Pomocí přístupového objektu init se můžete ujistit, že se objekt po inicializace nezmění. Pomáhá omezit nastavení hodnoty vlastnosti.

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; init; }
}

// The `LastName` property can be set only during initialization. It CAN'T be modified afterwards.
// The `FirstName` property can be modified after initialization.
var pet = new Person() { FirstName = "Joe", LastName = "Doe"};

// You can assign the FirstName property to a different value.
pet.FirstName = "Jane";

// Compiler error:
// Error CS8852  Init - only property or indexer 'Person.LastName' can only be assigned in an object initializer,
//               or on 'this' or 'base' in an instance constructor or an 'init' accessor.
// pet.LastName = "Kowalski";

Požadované inicializační vlastnosti podporují neměnné struktury a umožňují uživatelům typu přirozenou syntaxi.

Inicializátory objektů s vlastnostmi typu třídy

Při inicializaci objektů s vlastnostmi typu třídy můžete použít dvě různé syntaxe:

  1. Inicializátor objektů bez new klíčového slova: Property = { ... }
  2. Inicializátor objektů s klíčovým slovemnew:Property = new() { ... }

Tyto syntaxe se chovají odlišně. Následující příklad ukazuje oba přístupy:

public class HowToClassTypedInitializer
{
    public class EmbeddedClassTypeA
    {
        public int I { get; set; }
        public bool B { get; set; }
        public string S { get; set; }
        public EmbeddedClassTypeB ClassB { get; set; }

        public override string ToString() => $"{I}|{B}|{S}|||{ClassB}";

        public EmbeddedClassTypeA()
        {
            Console.WriteLine($"Entering EmbeddedClassTypeA constructor. Values are: {this}");
            I = 3;
            B = true;
            S = "abc";
            ClassB = new() { BB = true, BI = 43 };
            Console.WriteLine($"Exiting EmbeddedClassTypeA constructor. Values are: {this})");
        }
    }

    public class EmbeddedClassTypeB
    {
        public int BI { get; set; }
        public bool BB { get; set; }
        public string BS { get; set; }

        public override string ToString() => $"{BI}|{BB}|{BS}";

        public EmbeddedClassTypeB()
        {
            Console.WriteLine($"Entering EmbeddedClassTypeB constructor. Values are: {this}");
            BI = 23;
            BB = false;
            BS = "BBBabc";
            Console.WriteLine($"Exiting EmbeddedClassTypeB constructor. Values are: {this})");
        }
    }

    public static void Main()
    {
        var a = new EmbeddedClassTypeA
        {
            I = 103,
            B = false,
            ClassB = { BI = 100003 }
        };
        Console.WriteLine($"After initializing EmbeddedClassTypeA: {a}");

        var a2 = new EmbeddedClassTypeA
        {
            I = 103,
            B = false,
            ClassB = new() { BI = 100003 } //New instance
        };
        Console.WriteLine($"After initializing EmbeddedClassTypeA a2: {a2}");
    }

    // Output:
    //Entering EmbeddedClassTypeA constructor Values are: 0|False||||
    //Entering EmbeddedClassTypeB constructor Values are: 0|False|
    //Exiting EmbeddedClassTypeB constructor Values are: 23|False|BBBabc)
    //Exiting EmbeddedClassTypeA constructor Values are: 3|True|abc|||43|True|BBBabc)
    //After initializing EmbeddedClassTypeA: 103|False|abc|||100003|True|BBBabc
    //Entering EmbeddedClassTypeA constructor Values are: 0|False||||
    //Entering EmbeddedClassTypeB constructor Values are: 0|False|
    //Exiting EmbeddedClassTypeB constructor Values are: 23|False|BBBabc)
    //Exiting EmbeddedClassTypeA constructor Values are: 3|True|abc|||43|True|BBBabc)
    //Entering EmbeddedClassTypeB constructor Values are: 0|False|
    //Exiting EmbeddedClassTypeB constructor Values are: 23|False|BBBabc)
    //After initializing EmbeddedClassTypeA a2: 103|False|abc|||100003|False|BBBabc
}

Hlavní rozdíly

  • Bez new klíčového slova (ClassB = { BI = 100003 }): Tato syntaxe upravuje existující instanci vlastnosti, kterou vytvořil konstruktor objektu. Volá inicializátory členů u existujícího objektu.

  • S klíčovým slovem new (ClassB = new() { BI = 100003 }): Tato syntaxe vytvoří novou instanci a přiřadí ji k vlastnosti a nahradí všechny existující instance.

Inicializátor bez new znovu používá aktuální instanci. V předchozím příkladu jsou hodnoty TřídyB: 100003 (nová hodnota přiřazena), true (uchováno z inicializace EmbeddedClassTypeA), BBBabc (beze změny výchozí z EmbeddedClassTypeB).

Inicializátory objektů bez new pro vlastnosti jen pro čtení

Syntaxe bez new je užitečná s vlastnostmi jen pro čtení. Nemůžete přiřadit novou instanci, ale stále můžete inicializovat členy existující instance:

public class ReadOnlyPropertyExample
{
    public class Settings
    {
        public string Theme { get; set; } = "Light";
        public int FontSize { get; set; } = 12;
    }

    public class Application
    {
        public string Name { get; set; } = "";
        // This property is read-only - it can only be set during construction
        public Settings AppSettings { get; } = new();
    }

    public static void Example()
    {
        // You can still initialize the nested object's properties
        // even though AppSettings property has no setter
        var app = new Application
        {
            Name = "MyApp",
            AppSettings = { Theme = "Dark", FontSize = 14 }
        };

        // This would cause a compile error because AppSettings has no setter:
        // app.AppSettings = new Settings { Theme = "Dark", FontSize = 14 };

        Console.WriteLine($"App: {app.Name}, Theme: {app.AppSettings.Theme}, Font Size: {app.AppSettings.FontSize}");
    }
}

Tento přístup umožňuje inicializovat vnořené objekty i v případě, že vlastnost obsahující neobsahuje setter.

Inicializátory kolekce

Inicializátory kolekcí umožňují určit jeden nebo více inicializátorů elementů při inicializaci typu kolekce, která implementuje IEnumerable a má metodu Add s odpovídajícím podpisem jako metodu instance nebo rozšiřující metodu. Inicializátory elementů mohou být hodnota, výraz nebo inicializátor objektů. Pomocí inicializátoru kolekce nemusíte zadávat více volání; kompilátor přidá volání automaticky. Pokyny k používání inicializátorů kolekcí konzistentně najdete v tématu Použití inicializátorů kolekcí (pravidla stylu IDE0028). Inicializátory kolekcí jsou také užitečné v dotazech LINQ.

Následující příklad ukazuje dva jednoduché inicializátory kolekce:

List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
List<int> digits2 = new List<int> { 0 + 1, 12 % 3, MakeInt() };

Následující inicializátor kolekce používá inicializátory objektů k inicializaci objektů Cat třídy definované v předchozím příkladu. Jednotlivé inicializátory objektů jsou uzavřeny ve složených závorkách a odděleny čárkami.

List<Cat> cats =
[
    new Cat { Name = "Sylvester", Age = 8 },
    new Cat { Name = "Whiskers", Age = 2 },
    new Cat { Name = "Sasha", Age = 14 }
];

Pokud to metoda kolekce Add umožňuje, můžete jako prvek inicializátoru kolekce zadat null.

List<Cat?> moreCats = new List<Cat?>
{
    new Cat{ Name = "Furrytail", Age=5 },
    new Cat{ Name = "Peaches", Age=4 },
    null
};

Prvek rozložení můžete použít k vytvoření jednoho seznamu, který kopíruje jiný seznam nebo seznamy.

List<Cat> allCats = [.. cats, .. moreCats];

A zahrnout další prvky spolu s použitím rozprostřeného elementu.

List<Cat> additionalCats = [.. cats, new Cat { Name = "Furrytail", Age = 5 }, .. moreCats];

Indexované prvky můžete zadat, pokud kolekce podporuje indexování pro čtení a zápis.

var numbers = new Dictionary<int, string>
{
    [7] = "seven",
    [9] = "nine",
    [13] = "thirteen"
};

Předchozí ukázka vygeneruje kód, který volá Item[TKey] k nastavení hodnot. Slovníky a další asociativní kontejnery můžete inicializovat také pomocí následující syntaxe. Místo syntaxe indexeru se závorky a přiřazením používá objekt s více hodnotami:

var moreNumbers = new Dictionary<int, string>
{
    {19, "nineteen" },
    {23, "twenty-three" },
    {42, "forty-two" }
};

Tento příklad inicializátoru volá Add(TKey, TValue) k přidání tří položek do slovníku. Tyto dva různé způsoby inicializace asociativních kolekcí mají mírně odlišné chování kvůli voláním metod, která generuje kompilátor. Obě varianty pracují s Dictionary třídou. Jiné typy můžou podporovat pouze jeden nebo druhý na základě svého veřejného rozhraní API.

Argumenty výrazů kolekce

Počínaje jazykem C# 15 použijte with(...) element jako první prvek ve výrazu kolekce k předání argumentů konstruktoru kolekce. Pomocí této funkce můžete zadat kapacitu, porovnávače nebo jiné parametry konstruktoru přímo v syntaxi výrazu kolekce:

public static void CollectionExpressionWithArgumentsExample()
{
    string[] values = ["one", "two", "three"];

    // Use with() to pass capacity to the List<T> constructor
    List<string> names = [with(capacity: values.Length * 2), .. values];

    Console.WriteLine($"Created List<string> with capacity: {names.Capacity}");
    Console.WriteLine($"List contains {names.Count} elements: [{string.Join(", ", names)}]");

    // Use with() to pass a comparer to the HashSet<T> constructor
    HashSet<string> caseInsensitiveSet = [with(StringComparer.OrdinalIgnoreCase), "Hello", "HELLO"];
    // caseInsensitiveSet contains only one element because "Hello" and "HELLO" are equal

    Console.WriteLine($"HashSet with case-insensitive comparer contains {caseInsensitiveSet.Count} elements: [{string.Join(", ", caseInsensitiveSet)}]");
    Console.WriteLine("Note: 'Hello' and 'HELLO' are treated as the same element due to OrdinalIgnoreCase comparer");
}

Další informace o argumentech výrazů kolekce, včetně podporovaných cílových typů a omezení, naleznete v tématu Argumenty výrazů kolekce.

Inicializátory objektů s inicializací pouze pro čtení vlastností kolekce

Některé třídy mají vlastnosti kolekce, kde je vlastnost pouze pro čtení, podobně jako vlastnost Cats v CatOwner v následujícím případě:

public class CatOwner
{
    public IList<Cat> Cats { get; } = new List<Cat>();
}

Syntaxi inicializátoru kolekce, kterou jsme probírali dříve, nemůžete použít, protože vlastnost nemůže být přiřazena k novému seznamu:

CatOwner owner = new CatOwner
{
    Cats = new List<Cat>
    {
        new Cat{ Name = "Sylvester", Age=8 },
        new Cat{ Name = "Whiskers", Age=2 },
        new Cat{ Name = "Sasha", Age=14 }
    }
};

Nové položky Cats však můžete přidat pomocí syntaxe inicializace a vynechat vytvoření seznamu (new List<Cat>), jak je znázorněno v následujícím příkladu:

CatOwner owner = new CatOwner
{
    Cats =
    {
        new Cat{ Name = "Sylvester", Age=8 },
        new Cat{ Name = "Whiskers", Age=2 },
        new Cat{ Name = "Sasha", Age=14 }
    }
};

Sada položek, které chcete přidat, se zobrazí obklopená složenými závorkami. Předchozí kód je identický s psaním:

CatOwner owner = new ();
owner.Cats.Add(new Cat{ Name = "Sylvester", Age=8 });
owner.Cats.Add(new Cat{ Name = "Whiskers", Age=2 });
owner.Cats.Add(new Cat{ Name = "Sasha", Age=14 });

Příklady

Následující příklad kombinuje koncepty inicializátorů objektů a kolekcí.

public class InitializationSample
{
    public class Cat
    {
        // Automatically implemented properties.
        public int Age { get; set; }
        public string? Name { get; set; }

        public Cat() { }

        public Cat(string name)
        {
            Name = name;
        }
    }

    public static void Main()
    {
        Cat cat = new Cat { Age = 10, Name = "Fluffy" };
        Cat sameCat = new Cat("Fluffy"){ Age = 10 };

        List<Cat> cats =
        [
            new Cat { Name = "Sylvester", Age = 8 },
            new Cat { Name = "Whiskers", Age = 2 },
            new Cat { Name = "Sasha", Age = 14 }
        ];

        List<Cat?> moreCats = new List<Cat?>
        {
            new Cat { Name = "Furrytail", Age = 5 },
            new Cat { Name = "Peaches", Age = 4 },
            null
        };

        List<Cat> allCats = [.. cats, new Cat { Name = "Łapka", Age = 5 }, cat, .. moreCats];

        // Display results.
        foreach (Cat? c in allCats)
        {
            if (c != null)
            {
                System.Console.WriteLine(c.Name);
            }
            else
            {
                System.Console.WriteLine("List element has null value.");
            }
        }
    }
    // Output:
    // Sylvester
    // Whiskers
    // Sasha
    // Łapka
    // Fluffy
    // Furrytail
    // Peaches
    // List element has null value.
}

Následující příklad ukazuje objekt, který implementuje IEnumerable a obsahuje metodu Add s více parametry. Používá inicializátor kolekce s více prvky na každou položku v seznamu, které odpovídají signatuře Add metody.

public class FullExample
{
    class FormattedAddresses : IEnumerable<string>
    {
        private List<string> internalList = new();
        public IEnumerator<string> GetEnumerator() => internalList.GetEnumerator();

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => internalList.GetEnumerator();

        public void Add(string firstname, string lastname,
            string street, string city,
            string state, string zipcode) => internalList.Add($"""
            {firstname} {lastname}
            {street}
            {city}, {state} {zipcode}
            """
            );
    }

    public static void Main()
    {
        FormattedAddresses addresses = new FormattedAddresses()
        {
            {"John", "Doe", "123 Street", "Topeka", "KS", "00000" },
            {"Jane", "Smith", "456 Street", "Topeka", "KS", "00000" }
        };

        Console.WriteLine("Address Entries:");

        foreach (string addressEntry in addresses)
        {
            Console.WriteLine("\r\n" + addressEntry);
        }
    }

    /*
        * Prints:

        Address Entries:

        John Doe
        123 Street
        Topeka, KS 00000

        Jane Smith
        456 Street
        Topeka, KS 00000
        */
}

Add metody mohou použít klíčové slovo params k přijímání proměnlivého počtu argumentů, jak je znázorněno v následujícím příkladu. Tento příklad také ukazuje vlastní implementaci indexeru pro inicializaci kolekce pomocí indexů. Počínaje verzí jazyka C# 13 není parametr params omezen pouze na pole. Může to být typ kolekce nebo rozhraní.

public class DictionaryExample
{
    class RudimentaryMultiValuedDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, List<TValue>>> where TKey : notnull
    {
        private Dictionary<TKey, List<TValue>> internalDictionary = new Dictionary<TKey, List<TValue>>();

        public IEnumerator<KeyValuePair<TKey, List<TValue>>> GetEnumerator() => internalDictionary.GetEnumerator();

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => internalDictionary.GetEnumerator();

        public List<TValue> this[TKey key]
        {
            get => internalDictionary[key];
            set => Add(key, value);
        }

        public void Add(TKey key, params TValue[] values) => Add(key, (IEnumerable<TValue>)values);

        public void Add(TKey key, IEnumerable<TValue> values)
        {
            if (!internalDictionary.TryGetValue(key, out List<TValue>? storedValues))
            {
                internalDictionary.Add(key, storedValues = new());
            }
            storedValues.AddRange(values);
        }
    }

    public static void Main()
    {
        RudimentaryMultiValuedDictionary<string, string> rudimentaryMultiValuedDictionary1
            = new RudimentaryMultiValuedDictionary<string, string>()
            {
                {"Group1", "Bob", "John", "Mary" },
                {"Group2", "Eric", "Emily", "Debbie", "Jesse" }
            };
        RudimentaryMultiValuedDictionary<string, string> rudimentaryMultiValuedDictionary2
            = new RudimentaryMultiValuedDictionary<string, string>()
            {
                ["Group1"] = new List<string>() { "Bob", "John", "Mary" },
                ["Group2"] = new List<string>() { "Eric", "Emily", "Debbie", "Jesse" }
            };
        RudimentaryMultiValuedDictionary<string, string> rudimentaryMultiValuedDictionary3
            = new RudimentaryMultiValuedDictionary<string, string>()
            {
                {"Group1", new string []{ "Bob", "John", "Mary" } },
                { "Group2", new string[]{ "Eric", "Emily", "Debbie", "Jesse" } }
            };

        Console.WriteLine("Using first multi-valued dictionary created with a collection initializer:");

        foreach (KeyValuePair<string, List<string>> group in rudimentaryMultiValuedDictionary1)
        {
            Console.WriteLine($"\r\nMembers of group {group.Key}: ");

            foreach (string member in group.Value)
            {
                Console.WriteLine(member);
            }
        }

        Console.WriteLine("\r\nUsing second multi-valued dictionary created with a collection initializer using indexing:");

        foreach (KeyValuePair<string, List<string>> group in rudimentaryMultiValuedDictionary2)
        {
            Console.WriteLine($"\r\nMembers of group {group.Key}: ");

            foreach (string member in group.Value)
            {
                Console.WriteLine(member);
            }
        }
        Console.WriteLine("\r\nUsing third multi-valued dictionary created with a collection initializer using indexing:");

        foreach (KeyValuePair<string, List<string>> group in rudimentaryMultiValuedDictionary3)
        {
            Console.WriteLine($"\r\nMembers of group {group.Key}: ");

            foreach (string member in group.Value)
            {
                Console.WriteLine(member);
            }
        }
    }

    /*
        * Prints:

        Using first multi-valued dictionary created with a collection initializer:

        Members of group Group1:
        Bob
        John
        Mary

        Members of group Group2:
        Eric
        Emily
        Debbie
        Jesse

        Using second multi-valued dictionary created with a collection initializer using indexing:

        Members of group Group1:
        Bob
        John
        Mary

        Members of group Group2:
        Eric
        Emily
        Debbie
        Jesse

        Using third multi-valued dictionary created with a collection initializer using indexing:

        Members of group Group1:
        Bob
        John
        Mary

        Members of group Group2:
        Eric
        Emily
        Debbie
        Jesse
        */
}