Постфиксные операторы инкремента и декремента
post-increment-expression:
primary-expression ++
post-decrement-expression:
primary-expression --
Операнд постфиксного оператора инкремента или декремента должен быть выражением, которое классифицируется как переменная, доступ к свойству или доступ к индексатору. Результатом операции является значение того же типа, что и операнд.
Если первичное выражение имеет тип времени компиляции dynamic, то оператор динамически привязан (§7.2.2), выражение после инкремента или выражение после декремента имеет тип времени компиляции dynamic, а также во время выполнения применяются следующие правила, используя тип времени выполнения первичного выражения.
Если операндом постфиксного оператора инкремента или декремента является свойство или доступ к индексатору, то у свойства и индексатора должны быть оба метода доступа get и set. В противном случае возникает ошибка времени привязки.
Для выбора конкретной реализации оператора используется разрешение перегрузки унарных операторов (§7.3.3). Предопределенные операторы ++ и -- существуют для следующих типов: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal и любых перечисляемых типов. Стандартный оператор ++ возвращает значение, полученное добавлением 1 к операнду, а стандартный оператор -- возвращает значение, полученное вычитанием 1 из операнда. В контексте checked если результат такого сложения или вычитания выходит за пределы допустимого диапазона для типа результата и результат имеет целый тип или тип перечисления, то возникает исключение System.OverflowException.
Во время выполнения обработка постфиксных операций инкремента или декремента в виде x++ или x-- включает следующие этапы.
· Если x классифицируется как переменная, то:
o x вычисляется для создания переменной.
o Значение x сохраняется.
o Вызывается выбранный оператор с сохраненным значением x в качестве аргумента.
o Значение, возвращенное оператором, сохраняется в расположении, предоставленном при вычислении x.
o Сохраненное значение x становится результатом операции.
· Если x классифицируется как свойство или доступ к индексатору, то:
o Вычисляются выражение экземпляра (если x не имеет тип static) и список аргументов (если x является доступом к индексатору), связанные с x, и полученные результаты используются при последующих вызовах методов доступа get и set.
o Вызывается метод доступа get для x, а возвращенное значение сохраняется.
o Вызывается выбранный оператор с сохраненным значением x в качестве аргумента.
o Вызывается метод доступа set для x со значением, возвращенным оператором в качестве своего аргумента value.
o Сохраненное значение x становится результатом операции.
Операторы ++ и -- также могут использоваться в препозиции (§7.7.5). Обычно результатом операторов x++ и x-- является значение x до операции, тогда как результатом ++x и --x является значение x после операции. В обоих случаях сама переменная x имеет одинаковое значение после операции.
Реализацию operator ++ или operator -- можно вызывать в префиксной и постфиксной форме. Для двух этих форм нельзя создать разные реализации операторов.
Оператор new
Оператор new используется для создания новых экземпляров типов.
Существует три формы выражений new:
· Выражения создания объектов используются для создания новых экземпляров типов класса и типов значения.
· Выражения создания массивов используются для создания новых экземпляров типов массива.
· Выражения создания делегатов используются для создания новых экземпляров типа делегата.
Оператор new подразумевает создание экземпляра типа, но необязательно подразумевает динамическое выделение памяти. В частности, для экземпляров с типом значения не требуется дополнительная память помимо переменных, в которых они находятся, и при использовании new для создания экземпляров с типом значения динамическое выделение памяти не происходит.
Выражения создания объектов
Выражение создания объекта используется для создания нового экземпляра типа класса или типа значения.
object-creation-expression:
new type ( argument-listopt ) object-or-collection-initializeropt
new type object-or-collection-initializer
object-or-collection-initializer:
object-initializer
collection-initializer
Тип выражения создания объекта должен быть равен типу класса, типу значения или параметру типа. Тип не может быть типом класса abstract.
Необязательный список аргументов (§7.5.1) допускается, только если тип равен типу класса или типу структуры.
Выражение создания объекта может не содержать списка аргументов конструктора в скобках, если оно включает инициализатор объекта или коллекции. Отсутствие списка аргументов конструктора и скобок эквивалентно заданию пустого списка аргументов.
При обработке выражения создания объекта, которое включает инициализатор объекта или коллекции, сначала выполняется конструктор экземпляра, а затем выполняется инициализация члена или элемента, указанного в инициализаторе объекта (§7.6.10.2) или коллекции (§7.6.10.3).
Если какой-либо из аргументов в необязательном списке аргументов имеет тип времени компиляции dynamic, то выражение создания объекта динамически привязано (§7.2.2), и во время выполнения применяются следующие правила, используя тип времени выполнения тех аргументов из списка аргументов, которые имеют тип времени компиляции dynamic. Однако, для создания объекта выполняется ограниченная проверка времени компиляции, как описано в разделе §7.5.4.
Во время привязки обработка выражения создания объекта в виде new T(A), где T является типом класса или типом значения, а A является необязательным списком аргументов, включает следующие этапы.
· Если T является типом значения и список A не указан.
o Выражение создания объекта является вызовом конструктора по умолчанию. Результатом выражения создания объекта является значение типа T, а именно значение T по умолчанию, определенное в §4.1.1.
· Иначе, если T является параметром типа и A не указан.
o Если для T не были указаны ограничения типа значения или конструктора (§10.1.5), то возникает ошибка времени привязки.
o Результатом выражения создания объекта является значение типа времени выполнения, к которому привязан параметр типа, а именно результат вызов конструктора по умолчанию для этого типа. Тип времени выполнения может быть типом значения или ссылочным типом.
· Иначе, если T является типом класса или типом структуры:
o Если T является типом класса с модификатором abstract, возникает ошибка времени компиляции.
o Вызываемый конструктор экземпляра определяется с помощью правил разрешения перегрузки из раздела §7.5.3. Набор кандидатов конструкторов экземпляров включает все доступные конструкторы экземпляров, объявленные в T, применимые в соответствии со списком A (§7.5.3.1). Если набор кандидатов конструкторов экземпляров пуст или невозможно определить один лучший конструктор экземпляра, то возникает ошибка времени привязки.
o Результатом выражения создания объекта является значение типа T, а именно значение, получаемое при вызове конструктора экземпляра, определенного на предыдущем этапе.
· Иначе выражение создания объекта является недопустимым и возникает ошибка времени привязки.
Даже если выражение создания объекта динамически привязано, тип времени компиляции все равно будет T.
Во время выполнения обработка выражения создания объекта в виде new T(A), где T является типом класса или типом структуры, а A является необязательным списком аргументов, включает следующие этапы.
· Если T является типом класса:
o Создается новый экземпляр класса T. Если для создания нового экземпляра недостаточно памяти, то возникает исключение System.OutOfMemoryException и дальнейшие этапы не выполняются
o Все поля нового экземпляра инициализируются с помощью значений по умолчанию (§5.2).
o В соответствии с правилами вызова функции-члена (§7.5.4) вызывается конструктор экземпляра. Ссылка на созданный экземпляр автоматически передается конструктору экземпляра, и к этому экземпляру можно обращаться из этого конструктора с помощью this.
· Если T является типом структуры:
o С помощью выделения временной локальной переменной создается экземпляр типа T. Поскольку для явного присвоения значений каждому полю создаваемого экземпляра требуется конструктор экземпляра типа структуры, инициализация временной переменной не требуется.
o В соответствии с правилами вызова функции-члена (§7.5.4) вызывается конструктор экземпляра. Ссылка на созданный экземпляр автоматически передается конструктору экземпляра, и к этому экземпляру можно обращаться из этого конструктора с помощью this.
Инициализаторы объектов
Инициализатор объекта задает значения для нуля или нескольких полей или свойств объекта.
object-initializer:
{ member-initializer-listopt }
{ member-initializer-list , }
member-initializer-list:
member-initializer
member-initializer-list , member-initializer
member-initializer:
identifier = initializer-value
initializer-value:
expression
object-or-collection-initializer
Инициализатор объекта состоит из последовательности инициализаторов членов, заключенных между лексемами { и } и разделенных запятыми. В каждом инициализаторе члена должно указываться имя доступного поля или свойства инициализируемого объекта, за которым следует знак равенства и выражение или инициализатор объекта или коллекции. Инициализатор объекта не может содержать более одного инициализатора члена для одного поля или свойства. Инициализатор объекта не может ссылаться на инициализируемый им объект.
Инициализатор члена, в котором после знака равенства указывается выражение, обрабатывается так же, как присваиванием (§7.17.1) полю или свойству.
Инициализатор члена, в котором после знака равенства указывается инициализатор объекта, является инициализатором вложенного объекта, то есть выполняет инициализацию внедренного объекта. Вместо присваивания нового значения полю или свойству присваивания в инициализаторе вложенного объекта рассматриваются как присваивания членам поля или свойства. Инициализаторы вложенного объекта не могут применяться к свойствам типа значения или к полям с типом значения, доступным только для чтения.
Инициализатор члена, в котором после знака равенства указывается инициализатор коллекции, выполняет инициализацию внедренной коллекции. Вместо назначения полю или свойству новой коллекции элементы, указанные в инициализаторе, добавляются в коллекцию, на которую ссылается поле или свойство. Поле или свойство должно иметь тип коллекции, который удовлетворяет требованиям раздела §7.6.10.3.
Следующий класс представляет собой точку с двумя координатами.
public class Point
{
int x, y;
public int X { get { return x; } set { x = value; } }
public int Y { get { return y; } set { y = value; } }
}
Экземпляр Point можно создать и инициализировать следующим образом:
Point a = new Point { X = 0, Y = 1 };
что равносильно
Point __a = new Point();
__a.X = 0;
__a.Y = 1;
Point a = __a;
где __a является невидимой и недоступной другим образом временной переменной. Следующий класс представляет собой прямоугольник, создаваемый на основании двух точек:
public class Rectangle
{
Point p1, p2;
public Point P1 { get { return p1; } set { p1 = value; } }
public Point P2 { get { return p2; } set { p2 = value; } }
}
Экземпляр Rectangle можно создать и инициализировать следующим образом:
Rectangle r = new Rectangle {
P1 = new Point { X = 0, Y = 1 },
P2 = new Point { X = 2, Y = 3 }
};
что равносильно
Rectangle __r = new Rectangle();
Point __p1 = new Point();
__p1.X = 0;
__p1.Y = 1;
__r.P1 = __p1;
Point __p2 = new Point();
__p2.X = 2;
__p2.Y = 3;
__r.P2 = __p2;
Rectangle r = __r;
где __r, __p1 и __p2 являются временными переменными, не видимыми и недоступными другим образом.
Если конструктор Rectangle’s выделяет два внедренных экземпляра Point
public class Rectangle
{
Point p1 = new Point();
Point p2 = new Point();
public Point P1 { get { return p1; } }
public Point P2 { get { return p2; } }
}
то вместо назначения новых экземпляров для инициализации внедренных экземпляров Point можно использовать следующую конструкцию:
Rectangle r = new Rectangle {
P1 = { X = 0, Y = 1 },
P2 = { X = 2, Y = 3 }
};
что равносильно
Rectangle __r = new Rectangle();
__r.P1.X = 0;
__r.P1.Y = 1;
__r.P2.X = 2;
__r.P2.Y = 3;
Rectangle r = __r;
Инициализаторы коллекции
Инициализатор коллекции задает элементы коллекции.
collection-initializer:
{ element-initializer-list }
{ element-initializer-list , }
element-initializer-list:
element-initializer
element-initializer-list , element-initializer
element-initializer:
non-assignment-expression
{ expression-list }
expression-list:
expression
expression-list , expression
Инициализатор коллекции состоит из последовательности инициализаторов элементов, заключенных между лексемами { и } и разделенных запятыми. Каждый инициализатор элемента задает элемент, добавляемый в инициализируемый объект коллекции, и состоит из списка выражений, заключенных между лексемами { и } и разделенных запятыми. Инициализатор элемента с одним выражением можно записывать без скобок, но в таком случае оно не может быть выражением присваивания, чтобы избежать неоднозначности с инициализаторами членов. Создание выражения не присваивания определяется в разделе §7.18.
Ниже приводится пример выражения создания объекта, в которое входит инициализатор коллекции:
List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Объект коллекции, к которому применяется инициализатор коллекции, должен иметь тип, в котором реализуется интерфейс System.Collections.IEnumerable, иначе будет возникать ошибка времени компиляции. Для каждого указанного элемента по порядку инициализатор вызывает метод Add целевого объекта со списком выражений инициализатора элемента в качестве списка аргументов, применяя обычное разрешение перегрузки для каждого вызова. Таким образом, объект коллекции должен содержать применимый метод Add для каждого инициализатора элемента.
Следующий класс представляет собой контакт с именем и списком телефонных номеров:
public class Contact
{
string name;
List<string> phoneNumbers = new List<string>();
public string Name { get { return name; } set { name = value; } }
public List<string> PhoneNumbers { get { return phoneNumbers; } }
}
Экземпляр List<Contact> можно создать и инициализировать следующим образом:
var contacts = new List<Contact> {
new Contact {
Name = "Chris Smith",
PhoneNumbers = { "206-555-0101", "425-882-8080" }
},
new Contact {
Name = "Bob Harris",
PhoneNumbers = { "650-555-0199" }
}
};
что равносильно
var __clist = new List<Contact>();
Contact __c1 = new Contact();
__c1.Name = "Chris Smith";
__c1.PhoneNumbers.Add("206-555-0101");
__c1.PhoneNumbers.Add("425-882-8080");
__clist.Add(__c1);
Contact __c2 = new Contact();
__c2.Name = "Bob Harris";
__c2.PhoneNumbers.Add("650-555-0199");
__clist.Add(__c2);
var contacts = __clist;
где __clist, __c1 и __c2 являются временными переменными, не видимыми и недоступными другим образом.
Выражения создания массива
Выражение создания массива используется для создания нового экземпляра типа массива.
array-creation-expression:
new non-array-type [ expression-list ] rank-specifiersopt array-initializeropt
new array-type array-initializer
new rank-specifier array-initializer
Выражение создания массива первого типа создает экземпляр массива с типом, который получается после удаления всех отдельных выражений из списка выражений. Например, выражение создания массива new int[10, 20] создает экземпляр массива с типом int[,], а выражение new int[10][,] — экземпляр массива с типом int[][,]. Каждое выражение в списке выражений должно иметь тип int, uint, long, ulong или тип, который может быть явно преобразован в один или несколько этих типов. Значение каждого выражения определяет размер соответствующего измерения в новом созданном экземпляре массива. Поскольку размер измерения массива должен быть неотрицательным, при наличии константного выражения с отрицательным значением в списке выражений будет возникать ошибка времени компиляции.
За исключением небезопасных контекстов (§18.1) формат массива не указывается.
Если выражение создания массива первого типа содержит инициализатор массива, то каждое выражение в списке выражений должно быть константным, а ранг и длина измерения, указанные в списке выражений, должны совпадать с соответствующими значениями инициализатора массива.
В выражении создания массива второго или третьего типа ранг указанного типа массива или спецификация ранга должны быть равны соответствующим значениям инициализатора массива. Длины отдельных измерений выводятся из числа элементов в каждом соответствующем вложенном уровне инициализатора массива. Таким образом, выражение
new int[,] {{0, 1}, {2, 3}, {4, 5}}
в точности соответствует
new int[3, 2] {{0, 1}, {2, 3}, {4, 5}}
Выражение создания массива третьего типа называется выражением создания массива с неявным указанием типа. Оно похоже на второй тип за исключением того, что тип элемента массива не задается явно, но определяется как наиболее общий тип (§7.5.2.14) в наборе выражений в инициализаторе массива. Для многомерного массива (в спецификации ранга, у которого есть, по крайней мере, одна запятая) этот набор включает все выражения, находящиеся во вложенных инициализаторах массива.
Инициализаторы массива подробнее описываются далее в разделе §12.6.
Результат вычисления выражения создания массива классифицируется как значение, а именно как ссылка на новый созданный экземпляр массива. Во время выполнения обработка выражения создания массива включает следующие этапы.
· Вычисляются выражения длины измерений в списке выражений по порядку слева направо. После вычисления всех выражений выполняется неявное преобразование (§6.1) в один из следующих типов:int, uint, long, ulong. Выбирается первый тип из этого списка, для которого существует неявное преобразование. Если при вычислении выражения или последующем неявном преобразовании возникает исключение, то следующие выражения не вычисляются и дальнейшие этапы не выполняются.
· Вычисленные значения для длин измерений проверяются следующим образом. Если одно или несколько значений оказываются меньше нуля, то вызывается исключение System.OverflowException и дальнейшие этапы не выполняются.
· Создается экземпляр массива с полученными длинами измерений. Если для создания нового экземпляра недостаточно памяти, то возникает исключение System.OutOfMemoryException и дальнейшие этапы не выполняются
· Все элементы нового экземпляра массива инициализируются с помощью значений по умолчанию (§5.2).
· Если выражение создания массива содержит инициализатор массива, то вычисляется каждое выражение в инициализаторе массива и полученное значение назначается соответствующему элементу массива. Вычисления и присваивания выполняются в порядке записи выражений в инициализаторе массива, другими словами, инициализация элементов происходит по возрастанию индекса, причем первым обрабатывается самое правое измерение. Если при вычислении данного выражения или последующем присваивании соответствующему элементу массива возникает исключение, то другие элементы не инициализируются (следовательно, оставшиеся элементы будут иметь значения по умолчанию).
Выражение создания массива позволяет проводить инициализацию массива с помощью элементов типа массива, однако элементы такого массива необходимо инициализировать вручную. Например, в выражении
int[][] a = new int[100][];
создается одномерный массив со 100 элементами типа int[]. Исходным значением каждого элемента является null. В этом же выражении создания массива нельзя инициализировать подмассивы, и выражение
int[][] a = new int[100][5]; // Error
возникает ошибка времени компиляции. Вместо этого инициализация подмассивов должна выполняться вручную, ср.:
int[][] a = new int[100][];
for (int i = 0; i < 100; i++) a[i] = new int[5];
Когда массив массивов имеет «прямоугольную» форму, то есть когда все подмассивы имеют одинаковую длину, более эффективно использовать многомерный массив. В примере выше при инициализации массива массивов создается 101 объект — один внешний массив и 100 вложенных массивов. Напротив, в выражении
int[,] = new int[100, 5];
создается только один объект, двумерный массив, и это создание выполняется в одном выражении.
Ниже приведены примеры выражений создания массивов с неявным заданием типа.
var a = new[] { 1, 10, 100, 1000 }; // int[]
var b = new[] { 1, 1.5, 2, 2.5 }; // double[]
var c = new[,] { { "hello", null }, { "world", "!" } }; // string[,]
var d = new[] { 1, "one", 2, "two" }; // Error
Последнее выражение вызывает ошибку времени выполнения, поскольку типы int и string не могут быть неявно преобразованы один в другой, поэтому в данном случае наиболее общий тип отсутствует. В этом случае необходимо использовать выражение создания массива с явным заданием типа, например, указав тип object[]. Иначе один из элементов можно привести к общему базовому типу, который затем станет выведенным типом элемента.
Выражения создания массива с неявным заданием типа можно комбинировать с инициализаторами анонимных объектов (§7.6.10.6) для создания структуры данных с анонимным типом Пример:
var contacts = new[] {
new {
Name = "Chris Smith",
PhoneNumbers = new[] { "206-555-0101", "425-882-8080" }
},
new {
Name = "Bob Harris",
PhoneNumbers = new[] { "650-555-0199" }
}
};
Выражения создания делегата
Выражение создания делегата используется для создания нового экземпляра типа делегата.
delegate-creation-expression:
new delegate-type ( expression )
Аргументом выражения создания делегата должна быть группа методов, анонимная функция, или значение типа времени компиляции dynamic или типа делегата. Если аргумент является группой методов, он определяет метод, а для метода экземпляра — объект, для которого создается делегат. Если аргументом является анонимная функция, он напрямую определяет параметры и тело метода целевого делегата. Если аргумент является значением, он определяет экземпляр делегата, для которого создается копия.
Если выражение имеет тип dynamic во время компиляции, то выражение создания делегата динамически связано (§7.2.2), а правила внизу применяются во время выполнения с помощью типа времени выполнения выражения. Иначе правила применяются во время компиляции.
Во время привязки обработка выражения создания делегата в виде new D(E), где D имеет тип делегата, а E является выражением, включает следующие этапы.
· Если E является группой методов, выражение создания делегата обрабатывается так же, как и преобразование группы методов (§6.6) из E в D.
· Если E является анонимной функцией, выражение создания делегата обрабатывается так же, как и преобразование анонимной функции (§6.5) из E в D.
· Если E является значением, то E должно быть совместимо (§15.1) с D, а результатом является новый созданный делегат типа D, который ссылается на тот же список вызова, что и E. Если E не совместимо с D, возникает ошибка времени компиляции.
Во время выполнения обработка выражения создания делегата в виде new D(E), где D имеет тип делегата, а E является выражением, включает следующие этапы.
· Если E является группой методов, выражение создания делегата обрабатывается как преобразование группы методов (§6.6) из E в D.
· Если E является анонимной функцией, выражение создания делегата обрабатывается как преобразование анонимной функции из E в D (§6.5).
· Если E является значением типа делегата:
o Вычисляется E. Если при этом вычислении возникает исключение, дальнейшие этапы не выполняются.
o Если E имеет значение null, вызывается исключение System.NullReferenceException и дальнейшие действия не выполняются.
o Создается новый экземпляр типа делегата D. Если для создания нового экземпляра недостаточно памяти, то возникает исключение System.OutOfMemoryException и дальнейшие этапы не выполняются
o Новый экземпляр делегата инициализируется с помощью того же списка вызова, что и экземпляр делегата, предоставленный E.
Список вызова делегата определяется при инициализации делегата и остается неизменным в течение всего срока жизни делегата. Другими словами после создания делегата изменить его целевые вызываемые сущности невозможно. При объединении двух делегатов или удалении одного из другого (§15.1) создается новый делегат. Содержимое существующих делегатов не меняется.
Нельзя создать делегат, который бы ссылался на свойство, индексатор, пользовательский оператор, конструктор экземпляра, деструктор или статический конструктор.
Как говорилось выше, когда делегат создается из группы методов, список формальных параметров и тип возвращаемого значения делегата определяют выбираемый перегруженный метод. В этом примере
delegate double DoubleFunc(double x);
class A
{
DoubleFunc f = new DoubleFunc(Square);
static float Square(float x) {
return x * x;
}
static double Square(double x) {
return x * x;
}
}
поле A.f инициализируется с помощью делегата, который ссылается на второй метод Square, потому что этот метод в точности совпадает по списку формальных параметров и типу возвращаемого значения с DoubleFunc. Если бы второй метод Square отсутствовал, то возникла бы ошибка времени компиляции.