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 autoimplementerade egenskaper i Cat
klassen. Mer information finns i Automatiskt implementerade egenskaper.
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 };
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. Följande initiatorer ä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. Du kanske bara vill lagra en del av informationen från varje objekt i en sekvens. I följande exempel antar du 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
Det är viktigt att överväga konsekvenserna för klasstypade egenskaper när du initierar ett objekt:
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 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. 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 kanske bara stöder 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 kan inte 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 omgiven av klammerparenteser. Föregående kod är identisk med att skriva:
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 });
Exempel
I följande exempel kombineras begreppen objekt- och samlingsinitierare.
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 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. Från och med C# 13 är parametern params
inte begränsad till en matris. Det kan vara en samlingstyp eller ett gränssnitt.
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
*/
}