Inicjatory obiektów i kolekcji (Przewodnik programowania w języku C#)

Język C# umożliwia utworzenie wystąpienia obiektu lub kolekcji i wykonanie przypisań składowych w jednej instrukcji.

Inicjatory obiektów

Inicjatory obiektów umożliwiają przypisywanie wartości do dowolnych dostępnych pól lub właściwości obiektu w czasie jego tworzenia, bez konieczności wywoływania konstruktora, po którym występują wiersze instrukcji przypisania. Składnia inicjatora obiektów umożliwia określenie argumentów dla konstruktora lub pominięcie argumentów (i składni z nawiasami). W poniższym przykładzie pokazano, jak używać inicjatora obiektów z nazwanym typem Cat i jak wywołać konstruktor bez parametrów. Zwróć uwagę na użycie automatycznie zaimplementowanych właściwości w Cat klasie . Aby uzyskać więcej informacji, zobacz Właściwości zaimplementowane automatycznie.

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

Składnia inicjatorów obiektów umożliwia utworzenie wystąpienia, a następnie przypisanie nowo utworzonego obiektu z przypisanymi właściwościami do zmiennej w przypisaniu.

Inicjatory obiektów mogą ustawiać indeksatory oprócz przypisywania pól i właściwości. Rozważmy tę podstawową Matrix klasę:

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

Macierz tożsamości można zainicjować przy użyciu następującego kodu:

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

Każdy dostępny indeksator, który zawiera dostępny zestaw, może służyć jako jedno z wyrażeń w inicjatorze obiektu, niezależnie od liczby lub typów argumentów. Argumenty indeksu tworzą lewą stronę przypisania, a wartość jest prawą stroną wyrażenia. Na przykład wszystkie te elementy są prawidłowe, jeśli IndexersExample mają odpowiednie indeksatory:

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

Aby poprzedni kod został skompilowany, IndexersExample typ musi mieć następujące elementy członkowskie:

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

Inicjatory obiektów z typami anonimowymi

Chociaż inicjatory obiektów mogą być używane w dowolnym kontekście, są one szczególnie przydatne w wyrażeniach zapytań LINQ. Wyrażenia zapytań często używają typów anonimowych, które można zainicjować tylko za pomocą inicjatora obiektów, jak pokazano w poniższej deklaracji.

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

Typy anonimowe umożliwiają klauzulę w wyrażeniu select zapytania LINQ, aby przekształcić obiekty oryginalnej sekwencji w obiekty, których wartość i kształt mogą różnić się od oryginalnego. Jest to użyteczne, gdy chce się przechowywać tylko część informacji z każdego obiektu sekwencji. W poniższym przykładzie przyjęto założenie, że obiekt produktu (p) zawiera wiele pól i metod oraz że interesuje Cię tylko utworzenie sekwencji obiektów zawierających nazwę produktu i cenę jednostkową.

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

Po wykonaniu productInfos tego zapytania zmienna będzie zawierać sekwencję obiektów, do których można uzyskać dostęp w foreach instrukcji, jak pokazano w tym przykładzie:

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

Każdy obiekt w nowym typie anonimowym ma dwie właściwości publiczne, które otrzymują takie same nazwy jak właściwości lub pola w oryginalnym obiekcie. Możesz również zmienić nazwę pola podczas tworzenia typu anonimowego; Poniższy przykład zmienia nazwę UnitPrice pola na Price.

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

Inicjatory obiektów z modyfikatorem required

Słowo kluczowe służy required do wymuszania ustawiania wartości właściwości lub pola za pomocą inicjatora obiektu. Wymagane właściwości nie muszą być ustawiane jako parametry konstruktora. Kompilator gwarantuje, że wszystkie wywołujące zainicjują te wartości.

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

Typowym rozwiązaniem jest zagwarantowanie, że obiekt jest poprawnie zainicjowany, zwłaszcza jeśli masz wiele pól lub właściwości do zarządzania i nie chcesz dołączać ich wszystkich do konstruktora.

Inicjatory obiektów z akcesorem init

Upewnij się, że nikt nie zmienia zaprojektowanego obiektu może być ograniczony przy użyciu init metody dostępu. Pomaga ograniczyć ustawienie wartości właściwości.

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

Wymagane właściwości tylko do inicjowania obsługują niezmienne struktury, zezwalając na składnię naturalną dla użytkowników typu.

Inicjatory obiektów z właściwościami wpisanymi przez klasę

Podczas inicjowania obiektu, szczególnie podczas ponownego użytku bieżącego wystąpienia, należy wziąć pod uwagę konsekwencje dla właściwości typowych klasy.

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
}

W poniższym przykładzie pokazano, jak w przypadku klasy ClassB proces inicjowania obejmuje aktualizowanie określonych wartości przy zachowaniu innych z oryginalnego wystąpienia. Inicjator ponownie używa bieżącego wystąpienia: wartości klasy B będą następujące: 100003 (nowa wartość, która zostanie tutaj przypisana), (zachowana z inicjowania EmbeddedClassTypeA), trueBBBabc (bez zmian domyślnych z EmbeddedClassTypeB)

Inicjatory kolekcji

Inicjatory kolekcji umożliwiają określenie co najmniej jednego inicjatora elementu podczas inicjowania typu kolekcji, który implementuje IEnumerable i ma Add odpowiedni podpis jako metodę wystąpienia lub metodę rozszerzenia. Inicjatory elementów mogą być prostą wartością, wyrażeniem lub inicjatorem obiektu. Za pomocą inicjatora kolekcji nie trzeba określać wielu wywołań; kompilator dodaje wywołania automatycznie.

W poniższym przykładzie przedstawiono dwa proste inicjatory kolekcji:

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

Poniższy inicjator kolekcji używa inicjatorów obiektów do inicjowania obiektów Cat klasy zdefiniowanej w poprzednim przykładzie. Należy zauważyć, że poszczególne inicjatory obiektów są umieszczone w nawiasach klamrowych i rozdzielone przecinkami.

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

Można określić wartość null jako element w inicjatorze kolekcji, jeśli metoda kolekcji Add na to zezwala.

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

Można określić indeksowane elementy, jeśli kolekcja obsługuje indeksowanie odczytu/zapisu.

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

Powyższy przykład generuje kod, który wywołuje Item[TKey] metodę , aby ustawić wartości. Można również zainicjować słowniki i inne kontenery kojarzące przy użyciu następującej składni. Zwróć uwagę, że zamiast składni indeksatora z nawiasami i przypisaniem używa obiektu z wieloma wartościami:

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

Ten przykład inicjatora wywołuje polecenie Add(TKey, TValue) , aby dodać trzy elementy do słownika. Te dwa różne sposoby inicjowania kolekcji asocjacyjnych mają nieco inne zachowanie, ponieważ metoda wywołuje generowanie kompilatora. Oba warianty współpracują z klasą Dictionary . Inne typy mogą obsługiwać tylko jeden lub drugi na podstawie publicznego interfejsu API.

Inicjatory obiektów z inicjowaniem właściwości tylko do odczytu kolekcji

Niektóre klasy mogą mieć właściwości kolekcji, w których właściwość jest tylko do odczytu, na przykład Cats właściwość CatOwner w następującym przypadku:

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

Nie będzie można używać składni inicjatora kolekcji omówionej do tej pory, ponieważ nie można przypisać nowej listy właściwości:

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

Jednak nowe wpisy można dodać do Cats programu , używając składni inicjowania, pomijając tworzenie listy (new List<Cat>), jak pokazano poniżej:

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

Zestaw wpisów do dodania jest wyświetlany po prostu otoczony nawiasami klamrowymi. Powyższe informacje są identyczne z zapisem:

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

Przykłady

Poniższy przykład łączy pojęcia inicjatorów obiektów i kolekcji.

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

W poniższym przykładzie pokazano obiekt, który implementuje IEnumerable i zawiera metodę Add z wieloma parametrami. Używa inicjatora kolekcji z wieloma elementami na element na liście, który odpowiada podpisowi 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 mogą użyć params słowa kluczowego , aby użyć zmiennej liczby argumentów, jak pokazano w poniższym przykładzie. W tym przykładzie pokazano również niestandardową implementację indeksatora w celu zainicjowania kolekcji przy użyciu indeksów.

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

Zobacz też