Inicializátory objektu a kolekce (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řadit hodnoty k jakýmkoli přístupným polím nebo vlastnostem objektu při vytváření, bez nutnosti vyvolání konstruktoru následovaného řádky příkazů přiřazení. Syntaxe inicializátoru objektu umožňuje zadat argumenty pro konstruktor, nebo tyto argumenty (a syntaxi se závorkami) vynechat. Následující příklad ukazuje, jak použít inicializátor objektů s pojmenovaným typem Cat a 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
{
    // Auto-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átorů objektů umožňuje vytvořit instanci a potom přiřadí nově vytvořený objekt s přiřazenými vlastnostmi proměnné v přiřazení.

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 identity můžete inicializovat následujícím kódem:

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. Všechna jsou například platná, pokud IndexersExample má 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ž inicializátory objektů lze použít v jakémkoli kontextu, jsou zvlášť užitečné ve výrazech dotazu LINQ. Výrazy dotazů často používají anonymní typy, které lze inicializovat pouze pomocí inicializátoru objektů, jak je znázorněno v následující deklaraci.

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

Anonymní typy umožňují klauzuli select ve výrazu dotazu LINQ transformovat objekty původní sekvence na objekty, jejichž hodnota a tvar se mohou lišit od původního. Tato možnost je užitečná, pokud chcete uložit pouze část informace z jednotlivých objektů v sekvenci. 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í productInfos tohoto dotazu bude proměnná obsahovat posloupnost objektů, ke kterým lze získat přístup 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 také přejmenovat 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 vynutíte volajícím nastavit hodnotu vlastnosti nebo pole pomocí inicializátoru objektů. Požadované vlastnosti není nutné nastavit jako parametry konstruktoru. 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říslušenstvím init

Ujistěte se, že nikdo nezmění navržený objekt pomocí přístupového objektu init . 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 objektu, zejména při opakovaném použití aktuální instance, je důležité zvážit důsledky pro vlastnosti typu třídy.

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
}

Následující příklad ukazuje, jak proces inicializace třídyB zahrnuje aktualizaci konkrétních hodnot a zachování ostatních z původní instance. Inicializátor znovu použije aktuální instanci: hodnoty TřídyB budou: 100003 (nová hodnota, kterou zde přiřadíme), true (uchována z inicializace EmbeddedClassTypeA), BBBabc (beze změny výchozí z EmbeddedClassTypeB)

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á Add s příslušným podpisem jako metodu instance nebo rozšiřující metodu. Inicializátory elementů mohou být jednoduchá 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.

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. Pamatujte, že jednotlivé incializátory objektů jsou uzavřeny ve složených závorkách a odděleny čárkami.

List<Cat> cats = new List<Cat>
{
    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 hodnotu null.

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

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] nastavení hodnot. Pomocí následující syntaxe můžete také inicializovat slovníky a další asociativní kontejnery. Všimněte si, že 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" }
};

Toto inicializátor příklad volání Add(TKey, TValue) přidat tři položky do slovníku. Tyto dva různé způsoby inicializace asociativních kolekcí mají mírně odlišné chování, protože metoda volá kompilátor generuje. 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.

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

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

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

Dosud probíranou syntaxi inicializátoru kolekce nebudete moct použít, protože vlastnost nelze přiřadit nový seznam:

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 je však možné přidat do Cats syntaxe inicializace vynecháním vytvoření seznamu (new List<Cat>), jak je znázorněno dále:

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é se mají přidat, se jednoduše zobrazí obklopená složenými závorkami. Výše uvedené informace jsou shodné s psaním:

CatOwner owner = new CatOwner();
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
    {
        // Auto-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 List<Cat>
        {
            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
        };

        // Display results.
        System.Console.WriteLine(cat.Name);

        foreach (Cat c in cats)
            System.Console.WriteLine(c.Name);

        foreach (Cat? c in moreCats)
            if (c != null)
                System.Console.WriteLine(c.Name);
            else
                System.Console.WriteLine("List element has null value.");
    }
    // Output:
    //Fluffy
    //Sylvester
    //Whiskers
    //Sasha
    //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 v seznamu, které odpovídají podpisu Add metody.

    public class FullExample
    {
        class FormattedAddresses : IEnumerable<string>
        {
            private List<string> internalList = new List<string>();
            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 params klíčové slovo k převzetí proměnné 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ů.

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 List<TValue>());

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

Viz také