Initiatorer för objekt och samling (programmeringsguide för C#)

Med C# kan du instansiera ett objekt eller en samling och utföra medlemstilldelningar i en enda instruktion.

Objektinitierare

Med objektinitierare kan du tilldela värden till tillgängliga fält eller egenskaper för ett objekt vid skapandetillfället utan att behöva anropa en konstruktor följt av rader med tilldelningsuttryck. Med syntaxen för initiering av objekt kan du ange argument för en konstruktor eller utelämna argumenten (och parentessyntax). I följande exempel visas hur du använder en objektinitierare med en namngiven typ Cat och hur du anropar den parameterlösa konstruktorn. Observera användningen av automatiskt implementerade egenskaper i Cat klassen. Mer information finns i Auto-Implemented Properties (Automatiskt implementerade egenskaper).

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

Med syntaxen för objektinitierare kan du skapa en instans och därefter tilldelar det nyligen skapade objektet, med dess tilldelade egenskaper, variabeln i tilldelningen.

Objektinitierare kan ange indexerare, förutom att tilldela fält och egenskaper. Tänk på den här grundläggande Matrix klassen:

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

Du kan initiera identitetsmatrisen med följande kod:

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

Alla tillgängliga indexerare som innehåller en tillgänglig setter kan användas som ett av uttrycken i en objektinitierare, oavsett antal eller typer av argument. Indexargumenten utgör den vänstra sidan av tilldelningen och värdet är höger sida av uttrycket. Dessa är till exempel giltiga om IndexersExample de har rätt indexerare:

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

För att koden ovan ska kompileras IndexersExample måste typen ha följande medlemmar:

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

Objektinitierare med anonyma typer

Även om objektinitierare kan användas i alla sammanhang är de särskilt användbara i LINQ-frågeuttryck. Frågeuttryck använder ofta anonyma typer, som bara kan initieras med hjälp av en objektinitierare, enligt följande deklaration.

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

Anonyma typer aktiverar select satsen i ett LINQ-frågeuttryck för att omvandla objekt i den ursprungliga sekvensen till objekt vars värde och form kan skilja sig från originalet. Detta är användbart om du bara vill lagra en del av informationen från varje objekt i en sekvens. Anta i följande exempel att ett produktobjekt (p) innehåller många fält och metoder och att du bara är intresserad av att skapa en sekvens med objekt som innehåller produktnamnet och enhetspriset.

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

När den här frågan körs innehåller variabeln productInfos en sekvens med objekt som kan nås i en foreach instruktion som visas i det här exemplet:

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

Varje objekt i den nya anonyma typen har två offentliga egenskaper som får samma namn som egenskaperna eller fälten i det ursprungliga objektet. Du kan också byta namn på ett fält när du skapar en anonym typ. i följande exempel byt namn på fältet UnitPrice till Price.

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

Objektinitierare med required modifieraren

Du använder nyckelordet required för att tvinga anropare att ange värdet för en egenskap eller ett fält med hjälp av en objektinitierare. Nödvändiga egenskaper behöver inte anges som konstruktorparametrar. Kompilatorn ser till att alla anropare initierar dessa värden.

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

Det är en vanlig metod att garantera att objektet initieras korrekt, särskilt när du har flera fält eller egenskaper att hantera och inte vill inkludera dem alla i konstruktorn.

Objektinitierare med init accessorn

Se till att ingen ändrar det designade objektet kan begränsas med hjälp av en init accessor. Det hjälper till att begränsa inställningen för egenskapsvärdet.

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

Obligatoriska egenskaper med enbart init stöder oföränderliga strukturer samtidigt som naturlig syntax tillåts för användare av typen.

Objektinitierare med klasstypade egenskaper

När du initierar ett objekt, särskilt när du återanvänder den aktuella instansen, är det viktigt att överväga konsekvenserna för klasstypade egenskaper.

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
}

I följande exempel visas hur initieringsprocessen för ClassB innebär att vissa värden uppdateras samtidigt som andra bevaras från den ursprungliga instansen. Initieraren återanvänder den aktuella instansen: ClassB:s värden är: 100003 (nytt värde som vi tilldelar här), true (sparas från EmbeddedClassTypeA:s initiering) BBBabc (oförändrat standardvärde från EmbeddedClassTypeB)

Insamlingsinitierare

Med insamlingsinitieringar kan du ange en eller flera elementinitierare när du initierar en samlingstyp som implementerar IEnumerable och har Add med lämplig signatur som en instansmetod eller en tilläggsmetod. Elementinitierarna kan vara ett enkelt värde, ett uttryck eller en objektinitierare. Genom att använda en insamlingsinitierare behöver du inte ange flera anrop. kompilatorn lägger till anropen automatiskt.

I följande exempel visas två enkla insamlingsinitierare:

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

Följande insamlingsinitierare använder objektinitierare för att initiera objekt i klassen Cat som definierats i ett tidigare exempel. Observera att de enskilda objektinitierarna omges av klammerparenteser och avgränsas med kommatecken.

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

Du kan ange null som ett element i en insamlingsinitierare om samlingens Add metod tillåter det.

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

Du kan ange indexerade element om samlingen stöder läs-/skrivindexering.

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

Föregående exempel genererar kod som anropar Item[TKey] för att ange värdena. Du kan också initiera ordlistor och andra associativa containrar med hjälp av följande syntax. Observera att i stället för indexerarens syntax, med parenteser och en tilldelning, använder det ett objekt med flera värden:

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

Det här initieringsexemplet anropar Add(TKey, TValue) för att lägga till de tre objekten i ordlistan. Dessa två olika sätt att initiera associativa samlingar har något olika beteende på grund av metoden som anropet kompilatorn genererar. Båda varianterna fungerar med Dictionary klassen. Andra typer kan bara stödja det ena eller det andra baserat på deras offentliga API.

Objektinitierare med initiering av skrivskyddad samlingsegenskap

Vissa klasser kan ha samlingsegenskaper där egenskapen är skrivskyddad, till exempel Cats egenskapen CatOwner för i följande fall:

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

Du kommer inte att kunna använda syntaxen för insamlingsinitiering som diskuterats hittills eftersom egenskapen inte kan tilldelas en ny lista:

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

Nya poster kan dock läggas till Cats med hjälp av initieringssyntaxen genom att utelämna listskapandet (new List<Cat>), som du ser härnäst:

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

Den uppsättning poster som ska läggas till visas bara omgiven av klammerparenteser. Ovanstående är identiskt med att skriva:

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

Exempel

I följande exempel kombineras begreppen objekt- och samlingsinitierare.

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

I följande exempel visas ett objekt som implementerar IEnumerable och innehåller en Add metod med flera parametrar. Den använder en insamlingsinitierare med flera element per objekt i listan som motsvarar metodens Add signatur.

    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 metoder kan använda nyckelordet params för att ta ett variabelt antal argument, enligt följande exempel. Det här exemplet visar också den anpassade implementeringen av en indexerare för att initiera en samling med hjälp av 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
     */
}

Se även