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 Automatycznie zaimplementowane właściwości.
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 };
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 następujące inicjatory 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ą się różnić od oryginalnego. Możesz przechowywać tylko część informacji z każdego obiektu w 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 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 należy wziąć pod uwagę implikacje właściwości typizowanej 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 to: 100003
(nowa wartość, która przypisujemy tutaj), (zachowana z inicjowania EmbeddedClassTypeA), true
BBBabc
(niezmieniona wartość domyślna 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ć 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. Inicjatory poszczególnych obiektów są ujęte w nawiasy klamrowe i oddzielone 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, podobnie jak Cats
właściwość CatOwner
w następującym przypadku:
public class CatOwner
{
public IList<Cat> Cats { get; } = new List<Cat>();
}
Nie 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 otoczony nawiasami klamrowymi. Powyższy kod jest identyczny do pisania:
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 });
Przykłady
Poniższy przykład łączy pojęcia inicjatorów obiektów i kolekcji.
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.
}
Poniższy przykład przedstawia 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. Począwszy od języka C# 13, params
parametr nie jest ograniczony do tablicy. Może to być typ kolekcji lub interfejs.
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
*/
}