Инициализаторы объектов и коллекций (Руководство по программированию в C#)
C# позволяет создать экземпляр объекта или коллекции и выполнять присваивания его членов в одной инструкции.
Инициализаторы объектов
Инициализаторы объектов позволяют присваивать значения всем доступным полям и свойствам объекта во время создания без вызова конструктора, за которым следуют строки операторов присваивания. Синтаксис инициализатора объекта позволяет задавать аргументы конструктора или опускать их (и синтаксис в скобках). В приведенном ниже примере демонстрируется использование инициализатора объекта с именованным типом Cat
и вызов конструктора без параметров. Обратите внимание на использование свойств автоимплементов в Cat
классе. Дополнительные сведения см. в разделе "Автоматически реализованные свойства".
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 };
Синтаксис инициализаторов объектов позволяет создать экземпляр, а затем присваивает созданный объект, включая назначенные ему свойства, переменной в назначении.
Инициализаторы объектов могут задавать индексаторы, а также назначать поля и свойства. Рассмотрим следующий базовый класс 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; }
}
}
Можно инициализировать единичную матрицу следующим кодом:
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,
};
Любой доступный индексатор, который содержит доступный метод задания, можно использовать как одно из выражений в инициализаторе объекта независимо от количества или типов аргументов. Аргументы индекса формируют левую часть присваивания, а значение стоит в его правой части. Например, следующие инициализаторы действительны, если IndexersExample
у них есть соответствующие индексаторы:
var thing = new IndexersExample
{
name = "object one",
[1] = '1',
[2] = '4',
[3] = '9',
Size = Math.PI,
['C',4] = "Middle C"
}
Для компиляции приведенного выше кода тип IndexersExample
должен иметь следующие члены:
public string name;
public double Size { set { ... }; }
public char this[int i] { set { ... }; }
public string this[char c, int i] { set { ... }; }
Инициализаторы объектов с анонимными типами
Хотя инициализаторы объектов могут использоваться в любом контексте, они особенно полезны в выражениях запросов LINQ. В выражениях запросов часто используются анонимные типы, которые можно инициализировать только с помощью инициализаторов объектов, как показано в приведенном ниже объявлении.
var pet = new { Age = 10, Name = "Fluffy" };
Анонимные типы позволяют select
предложению в выражении запроса LINQ преобразовывать объекты исходной последовательности в объекты, значения и фигуры которых могут отличаться от исходного. Может потребоваться сохранить только часть информации из каждого объекта в последовательности. В следующем примере предположим, что объект продукта (p
) содержит множество полей и методов, и вы заинтересованы только в создании последовательности объектов, содержащих имя продукта и цену единицы.
var productInfos =
from p in products
select new { p.ProductName, p.UnitPrice };
При выполнении productInfos
этого запроса переменная содержит последовательность объектов, к которым можно получить доступ в инструкции, как показано в foreach
этом примере:
foreach(var p in productInfos){...}
Каждый объект нового анонимного типа содержит два общедоступных свойства, имеющих те же имена, что и у свойств или полей исходного объекта. Вы также можете переименовать поле при создании анонимного типа; В следующем примере поле переименовывается UnitPrice
Price
в .
select new {p.ProductName, Price = p.UnitPrice};
Инициализаторы объектов с модификатором required
Ключевое required
слово используется для принудительного задания значения свойства или поля с помощью инициализатора объектов. Обязательные свойства не нужно задавать в качестве параметров конструктора. Компилятор обеспечивает инициализацию всех вызывающих значений.
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();
Обычно рекомендуется гарантировать правильность инициализации объекта, особенно если у вас есть несколько полей или свойств для управления и не хотят включать их в конструктор.
Инициализаторы объектов с методом init
доступа
Убедившись, что никто не изменяет разработанный init
объект, не может быть ограничен с помощью метода доступа. Это помогает ограничить параметр значения свойства.
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";
Обязательные свойства только для инициализации поддерживают неизменяемые структуры, позволяя естественному синтаксису для пользователей типа.
Инициализаторы объектов со свойствами с типизированными классами
Важно учитывать последствия для свойств типа класса при инициализации объекта:
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
}
В следующем примере показано, как для ClassB процесс инициализации включает обновление определенных значений при сохранении других из исходного экземпляра. Инициализатор повторно использует текущий экземпляр: значения ClassB: 100003
(новое значение, которое мы назначаем здесь), (сохранено из инициализации EmbeddedClassTypeA), true
BBBabc
(без изменений по умолчанию из EmbeddedClassTypeB).
Инициализаторы коллекций
Инициализаторы коллекций позволяют задавать один или несколько инициализаторов элементов при инициализации типа коллекции, который реализует интерфейс IEnumerable и включает Add
с соответствующей сигнатурой как метод экземпляра или метод расширения. Инициализаторы элементов могут быть значением, выражением или инициализатором объектов. С помощью инициализатора коллекции вам не нужно указывать несколько вызовов; компилятор автоматически добавляет вызовы.
Ниже приведен пример двух простых инициализаторов коллекций.
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() };
В следующем инициализаторе коллекции используются инициализаторы объектов класса Cat
, определенного в предыдущем примере. Инициализаторы отдельных объектов заключены в фигурные скобки и разделены запятыми.
List<Cat> cats = new List<Cat>
{
new Cat{ Name = "Sylvester", Age=8 },
new Cat{ Name = "Whiskers", Age=2 },
new Cat{ Name = "Sasha", Age=14 }
};
В качестве элемента инициализатора коллекции можно указать значение null, если метод Add
коллекции допускает это.
List<Cat?> moreCats = new List<Cat?>
{
new Cat{ Name = "Furrytail", Age=5 },
new Cat{ Name = "Peaches", Age=4 },
null
};
Можно указать индексированные элементы, если коллекция поддерживает индексирование чтения и записи.
var numbers = new Dictionary<int, string>
{
[7] = "seven",
[9] = "nine",
[13] = "thirteen"
};
В предыдущем примере создается код, который вызывает Item[TKey] для задания значений. Вы также можете инициализировать словари и другие ассоциативные контейнеры с помощью следующего синтаксиса. Обратите внимание, что вместо синтаксиса индексатора с круглыми скобками и присваиванием он использует объект с несколькими значениями:
var moreNumbers = new Dictionary<int, string>
{
{19, "nineteen" },
{23, "twenty-three" },
{42, "forty-two" }
};
В этом примере инициализатор вызывает Add(TKey, TValue) для добавления трех элементов в словарь. Эти два разных способа инициализации ассоциативных коллекций немного отличаются из-за вызовов методов, которые создает компилятор. Оба варианта могут работать с классом Dictionary
. Другие типы могут поддерживать только одну или другую на основе общедоступного API.
Инициализаторы объектов с инициализацией свойств коллекции только для чтения
Некоторые классы могут иметь свойства коллекции, в которых свойство доступно только для чтения, например Cats
свойство CatOwner
в следующем случае:
public class CatOwner
{
public IList<Cat> Cats { get; } = new List<Cat>();
}
Вы не можете использовать синтаксис инициализатора коллекции, рассмотренный до сих пор, так как свойство не может быть назначено новому списку:
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 }
}
};
Тем не менее новые записи можно добавить к Cats
, используя синтаксис инициализации без создания списка (new List<Cat>
), как показано далее:
CatOwner owner = new CatOwner
{
Cats =
{
new Cat{ Name = "Sylvester", Age=8 },
new Cat{ Name = "Whiskers", Age=2 },
new Cat{ Name = "Sasha", Age=14 }
}
};
Добавляемый набор записей отображается в виде фигурных скобок. Предыдущий код идентичен написанию:
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 });
Примеры
В следующем примере объединяются понятия инициализаторов коллекций и объектов.
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.
}
В следующем примере показан объект, реализующий IEnumerable и содержащий Add
метод с несколькими параметрами. Он использует инициализатор коллекции с несколькими элементами в списке, соответствующим сигнатуре Add
метода.
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
можно использовать ключевое слово params
, чтобы принимать переменное число аргументов, как показано в приведенном ниже примере. Здесь также демонстрируется пользовательская реализация индексатора, которая также применяется для инициализации коллекции с помощью индексов. Начиная с C# 13 params
параметр не ограничен массивом. Это может быть тип коллекции или интерфейс.
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
*/
}