Initializers voor objecten en verzamelingen (C#-programmeerhandleiding)

Met C# kunt u een object of verzameling instantiëren en lidtoewijzingen uitvoeren in één instructie.

Object-initialisatiefuncties

Met object-initialisatiefuncties kunt u waarden toewijzen aan toegankelijke velden of eigenschappen van een object tijdens het maken zonder dat u een constructor hoeft aan te roepen, gevolgd door regels toewijzingsinstructies. Met de syntaxis van de object-initialisatiefunctie kunt u argumenten opgeven voor een constructor of de argumenten weglaten (en de syntaxis van haakjes). In het volgende voorbeeld ziet u hoe u een object-initialisatiefunctie gebruikt met een benoemd type Cat en hoe u de parameterloze constructor aanroept. Let op het gebruik van automatisch geïmplementeerde eigenschappen in de Cat klasse. Zie Eigenschappen die automatisch zijn geïmplementeerd voor meer informatie.

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

Met de syntaxis van de object-initialisatiefunctie kunt u een exemplaar maken en daarna wordt het zojuist gemaakte object, met de toegewezen eigenschappen, toegewezen aan de variabele in de toewijzing toegewezen.

Object-initialisatiefuncties kunnen indexeerfuncties instellen, naast het toewijzen van velden en eigenschappen. Houd rekening met deze basisklasse Matrix :

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

U kunt de identiteitsmatrix initialiseren met de volgende code:

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

Elke toegankelijke indexeerfunctie die een toegankelijke setter bevat, kan worden gebruikt als een van de expressies in een object-initialisatiefunctie, ongeacht het aantal of de typen argumenten. De indexargumenten vormen de linkerkant van de toewijzing en de waarde is de rechterkant van de expressie. Deze zijn bijvoorbeeld allemaal geldig als IndexersExample de juiste indexeerfuncties zijn:

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

Voordat de voorgaande code moet worden gecompileerd, moet het IndexersExample type de volgende leden hebben:

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

Object initializers met anonieme typen

Hoewel object-initialisatiefuncties in elke context kunnen worden gebruikt, zijn ze vooral nuttig in LINQ-query-expressies. Query-expressies maken vaak gebruik van anonieme typen, die alleen kunnen worden geïnitialiseerd met behulp van een object initialisatiefunctie, zoals wordt weergegeven in de volgende declaratie.

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

Anonieme typen maken het mogelijk dat de select component in een LINQ-queryexpressie objecten van de oorspronkelijke reeks transformeert in objecten waarvan de waarde en vorm afwijken van het origineel. Dit is handig als u slechts een deel van de informatie van elk object in een reeks wilt opslaan. In het volgende voorbeeld wordt ervan uitgegaan dat een productobject (p) veel velden en methoden bevat en dat u alleen geïnteresseerd bent in het maken van een reeks objecten die de productnaam en de eenheidsprijs bevatten.

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

Wanneer deze query wordt uitgevoerd, bevat de productInfos variabele een reeks objecten die kunnen worden geopend in een foreach instructie, zoals wordt weergegeven in dit voorbeeld:

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

Elk object in het nieuwe anonieme type heeft twee openbare eigenschappen die dezelfde namen ontvangen als de eigenschappen of velden in het oorspronkelijke object. U kunt ook de naam van een veld wijzigen wanneer u een anoniem type maakt; in het volgende voorbeeld wordt de naam van het UnitPrice veld gewijzigd in Price.

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

Object initializers met de required modifier

U gebruikt het required trefwoord om aanroepers te dwingen de waarde van een eigenschap of veld in te stellen met behulp van een object-initialisatiefunctie. Vereiste eigenschappen hoeven niet als constructorparameters te worden ingesteld. De compiler zorgt ervoor dat alle bellers deze waarden initialiseren.

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

Het is een gebruikelijke procedure om te garanderen dat uw object correct is geïnitialiseerd, met name wanneer u meerdere velden of eigenschappen hebt die u wilt beheren en deze niet allemaal in de constructor wilt opnemen.

Object initialisatiefuncties met de init toegangsfunctie

Zorg ervoor dat niemand het ontworpen object wijzigt door een init accessor te gebruiken. Hiermee kunt u de instelling van de eigenschapswaarde beperken.

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

Vereiste init-only eigenschappen ondersteunen onveranderbare structuren en bieden natuurlijke syntaxis voor gebruikers van het type.

Object initializers met class-typed eigenschappen

Bij het initialiseren van een object, met name bij het hergebruik van het huidige exemplaar, is het van cruciaal belang om rekening te houden met de gevolgen voor eigenschappen met klassetypen.

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
}

In het volgende voorbeeld ziet u hoe voor classB het initialisatieproces specifieke waarden bijwerkt terwijl anderen van het oorspronkelijke exemplaar behouden blijven. De Initializer gebruikt het huidige exemplaar opnieuw: de waarden van ClassB zijn: 100003 (nieuwe waarde die we hier toewijzen), true (behouden van de initialisatie van EmbeddedClassTypeA), BBBabc (ongewijzigde standaardwaarde van EmbeddedClassTypeB)

Initialisatie van verzamelingen

Met initialisatiefunctie voor verzamelingen kunt u een of meer element-initialisaties opgeven wanneer u een verzamelingstype initialiseert dat wordt geïmplementeerd IEnumerable en met de juiste handtekening als instantiemethode of een extensiemethode wordt gebruikt Add . De element-initialisatiefuncties kunnen een eenvoudige waarde, een expressie of een object-initialisatiefunctie zijn. Met behulp van een initialisatiefunctie voor verzamelingen hoeft u niet meerdere aanroepen op te geven; de compiler voegt de aanroepen automatisch toe.

In het volgende voorbeeld ziet u twee eenvoudige initialisatieprogramma's voor verzamelingen:

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

De volgende initialisatiefunctie voor verzamelingen maakt gebruik van objectinitialiseerde objecten van de Cat klasse die in een eerder voorbeeld is gedefinieerd. Houd er rekening mee dat de afzonderlijke object initializers tussen accolades staan en gescheiden door komma's.

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

U kunt null opgeven als een element in een initialisatiefunctie voor verzamelingen als de methode van Add de verzameling dit toestaat.

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

U kunt geïndexeerde elementen opgeven als de verzameling indexering voor lezen/schrijven ondersteunt.

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

In het voorgaande voorbeeld wordt code gegenereerd die de Item[TKey] waarden aanroept. U kunt ook woordenlijsten en andere associatieve containers initialiseren met behulp van de volgende syntaxis. U ziet dat in plaats van de syntaxis van de indexeerfunctie, met haakjes en een toewijzing, een object met meerdere waarden wordt gebruikt:

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

In dit initialisatievoorbeeld wordt aanroepen Add(TKey, TValue) om de drie items toe te voegen aan de woordenlijst. Deze twee verschillende manieren om associatieverzamelingen te initialiseren, hebben iets ander gedrag vanwege de methode die de compiler genereert. Beide varianten werken met de Dictionary klasse. Andere typen ondersteunen mogelijk slechts één of de andere op basis van hun openbare API.

Initialisatie van objecten met alleen-lezen eigenschap initialisatie van verzamelingen

Sommige klassen hebben mogelijk verzamelingseigenschappen waarbij de eigenschap alleen-lezen is, zoals de Cats eigenschap van CatOwner in het volgende geval:

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

U kunt de syntaxis van de initialisatiefunctie voor verzamelingen die tot nu toe is besproken, niet gebruiken omdat de eigenschap geen nieuwe lijst kan worden toegewezen:

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

Nieuwe vermeldingen kunnen echter worden toegevoegd aan Cats de hand van de initialisatiesyntaxis door het maken van de lijst weg te laten(new List<Cat>), zoals hierna wordt weergegeven:

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

De set vermeldingen die moeten worden toegevoegd, wordt eenvoudig tussen accolades weergegeven. Het bovenstaande is identiek aan het schrijven:

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

Voorbeelden

In het volgende voorbeeld worden de concepten van object- en verzamelings initialisatiefuncties gecombineerd.

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

In het volgende voorbeeld ziet u een object dat een Add methode met meerdere parameters implementeert IEnumerable en bevat. Het maakt gebruik van een initialisatiefunctie voor verzamelingen met meerdere elementen per item in de lijst die overeenkomen met de handtekening van de Add methode.

    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 methoden kunnen het params trefwoord gebruiken om een variabel aantal argumenten te gebruiken, zoals wordt weergegeven in het volgende voorbeeld. In dit voorbeeld ziet u ook de aangepaste implementatie van een indexeerfunctie om een verzameling te initialiseren met behulp van indexen.

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

Zie ook