Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
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 };
Синтаксис инициализаторов объектов позволяет создать экземпляр, а затем присваивает созданный объект, включая назначенные ему свойства, переменной в назначении.
Начиная с свойств вложенных объектов, можно использовать синтаксис инициализатора объектов без ключевого new
слова. Этот синтаксис Property = { ... }
позволяет инициализировать элементы существующих вложенных объектов, что особенно полезно с свойствами только для чтения. Дополнительные сведения см. в разделе "Инициализаторы объектов" с типизированными свойствами класса.
Инициализаторы объектов могут задавать индексаторы, а также назначать поля и свойства. Рассмотрим следующий базовый класс 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";
Обязательные свойства только для инициализации поддерживают неизменяемые структуры, позволяя естественному синтаксису для пользователей типа.
Инициализаторы объектов со свойствами с типизированными классами
При инициализации объектов с типизированными свойствами можно использовать два разных синтаксиса:
-
Инициализатор объектов без
new
ключевого слова:Property = { ... }
-
Инициализатор объектов с
new
ключевым словом:Property = new() { ... }
Эти синтаксисы ведут себя по-разному. В следующем примере демонстрируется оба подхода:
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
}
Основные отличия
Без
new
ключевого слова (ClassB = { BI = 100003 }
): этот синтаксис изменяет существующий экземпляр свойства, созданного во время создания объекта. Он вызывает инициализаторы членов в существующем объекте.С
new
ключевым словом (ClassB = new() { BI = 100003 }
): этот синтаксис создает совершенно новый экземпляр и назначает его свойству, заменив любой существующий экземпляр.
Инициализатор без new
повторно использует текущий экземпляр. В приведенном выше примере значения ClassB: 100003
(новое назначенное значение), true
(сохранено из инициализации EmbeddedClassTypeA) BBBabc
(без изменений по умолчанию из EmbeddedClassTypeB).
Инициализаторы объектов без new
, применимые к свойствам только для чтения
Синтаксис без new
особенно полезен для свойств только для чтения, где нельзя назначить новый экземпляр, но по-прежнему можно инициализировать члены существующего экземпляра.
public class ReadOnlyPropertyExample
{
public class Settings
{
public string Theme { get; set; } = "Light";
public int FontSize { get; set; } = 12;
}
public class Application
{
public string Name { get; set; } = "";
// This property is read-only - it can only be set during construction
public Settings AppSettings { get; } = new();
}
public static void Example()
{
// You can still initialize the nested object's properties
// even though AppSettings property has no setter
var app = new Application
{
Name = "MyApp",
AppSettings = { Theme = "Dark", FontSize = 14 }
};
// This would cause a compile error because AppSettings has no setter:
// app.AppSettings = new Settings { Theme = "Dark", FontSize = 14 };
Console.WriteLine($"App: {app.Name}, Theme: {app.AppSettings.Theme}, Font Size: {app.AppSettings.FontSize}");
}
}
Этот подход позволяет инициализировать вложенные объекты, даже если у содержащегося свойства нет метода задания.
Инициализаторы коллекций
Инициализаторы коллекций позволяют задавать один или несколько инициализаторов элементов при инициализации типа коллекции, который реализует интерфейс 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
};
С помощью элемента spread можно создать один список, копирующий другой список или списки.
List<Cat> allCats = [.. cats, .. moreCats];
И включите дополнительные элементы вместе с использованием оператора распространения.
List<Cat> additionalCats = [.. cats, new Cat { Name = "Furrytail", Age = 5 }, .. moreCats];
Можно указать индексированные элементы, если коллекция поддерживает индексирование чтения и записи.
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
};
List<Cat> allCats = [.. cats, new Cat { Name = "Łapka", Age = 5 }, cat, .. moreCats];
// Display results.
foreach (Cat? c in allCats)
{
if (c != null)
{
System.Console.WriteLine(c.Name);
}
else
{
System.Console.WriteLine("List element has null value.");
}
}
}
// Output:
// Sylvester
// Whiskers
// Sasha
// Łapka
// Fluffy
// 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
*/
}