Поделиться через


12 выражений

12.1 Общие

Выражение — это последовательность операторов и операндов. Это предложение определяет синтаксис, порядок оценки операндов и операторов и значение выражений.

Классификации выражений 12.2

12.2.1 Общие

Результат выражения классифицируется как один из следующих:

  • Значение. Каждое значение имеет связанный тип.
  • Переменная. Если иное не указано, переменная явно типируется и имеет связанный тип, а именно объявленный тип переменной. Неявно типизированная переменная не имеет связанного типа.
  • Литерал NULL. Выражение с этой классификацией может быть неявно преобразовано в ссылочный тип или тип значения, допускающий значение NULL.
  • Анонимная функция. Выражение с этой классификацией может быть неявно преобразовано в совместимый тип делегата или тип дерева выражений.
  • Кортеж. Каждый кортеж имеет фиксированное количество элементов, каждое из которых содержит выражение и необязательное имя элемента кортежа.
  • Доступ к свойству. Каждый доступ к свойству имеет связанный тип, а именно тип свойства. Кроме того, доступ к свойствам может иметь соответствующее выражение экземпляра. При вызове аксессора доступа к свойству экземпляра результат вычисления выражения экземпляра становится экземпляром, представленным this (§12.8.14).
  • Доступ к индексатору. Каждый доступ индексатора имеет связанный тип, а именно тип элемента индексатора. Кроме того, обращение к индексатору имеет соответствующее выражение экземпляра и соответствующий список аргументов. Когда вызывается аксессор индексатора, результат вычисления выражения экземпляра становится экземпляром, представленным this (§12.8.14), а результат вычисления списка аргументов становится списком параметров вызова.
  • Ничто. Это происходит при вызове метода с типом возвращаемого значения void. Выражение, классифицирующееся как ничего, является допустимым только в контексте statement_expression (§13.7) или как текст lambda_expression (§12.19).

Для выражений, которые встречаются в виде подвыражений в составе больших выражений с указанными ограничениями, результат также можно классифицировать как один из следующих:

  • Пространство имен. Выражение с этой классификацией может появляться только слева от member_access (§12.8.7). В любом другом контексте выражение, классифицирующееся как пространство имен, приводит к ошибке во время компиляции.
  • Тип. Выражение с этой классификацией может отображаться только в левой части member_access (§12.8.7). В любом другом контексте выражение, классифицирующееся как тип, приводит к ошибке во время компиляции.
  • Группа методов, которая представляет собой набор перегруженных методов, полученных от поиска элементов (§12.5). Группа методов может иметь ассоциированное выражение экземпляра и список ассоциированных аргументов типа. При вызове метода экземпляра результат вычисления этого выражения становится экземпляром, представленным как this (§12.8.14). Группа методов разрешена в invocation_expression (§12.8.10) или delegate_creation_expression (§12.8.17.6) и может быть неявно преобразована в совместимый тип делегата (§10.8). В любом другом контексте выражение, классифицирующееся как группа методов, вызывает ошибку во время компиляции.
  • Один доступ к событию. Каждый доступ к событию имеет связанный тип, а именно тип события. Кроме того, у доступа к событиям может быть связанное выражение экземпляра. Доступ к событию может отображаться в виде левого операнда операторов += и -= (§12.21.5). В любом другом контексте выражение, классифицирующееся как доступ к событию, приводит к ошибке во время компиляции. При вызове аксессора доступа к событию экземпляра результат вычисления выражения экземпляра становится экземпляром, представленным this (§12.8.14).
  • Выражение броска может использоваться в нескольких контекстах для генерации исключения в выражении. Выражение выброса может быть преобразовано неявно в любой тип.

Доступ к свойству или доступ индексатора всегда переквалифицируется как значение через вызов метода доступа get или set. Конкретный аксессор определяется контекстом доступа к свойству или индексатору: если это доступ для назначения, вызывается сеттер для присвоения нового значения (§12.21.2). В противном случае акцессор get вызывается для получения текущего значения (§12.2.2).

Аксессор экземпляра представляет собой доступ к свойству экземпляра, доступ к событию экземпляра или доступ к индексатору.

12.2.2 Значения выражений

Большинство конструкций, включающих выражение, в конечном счете требуют, чтобы выражение обозначило значение. В таких случаях, если фактическое выражение обозначает пространство имен, тип, группу методов или ничего, возникает ошибка во время компиляции. Однако если выражение обозначает доступ к свойству, доступ индексатора или переменную, значение свойства, индексатора или переменной неявно заменено:

  • Значение переменной — это просто значение, которое в настоящее время хранится в расположении хранилища, определяемом переменной. Переменная должна быть определенно назначена (§9.4) перед получением его значения или в противном случае возникает ошибка во время компиляции.
  • Значение выражения доступа к свойству получается путем вызова метода доступа к свойству. Если свойство не имеет метода доступа, возникает ошибка во время компиляции. В противном случае выполняется вызов члена функции (§12.6.6), а результат вызова становится значением выражения доступа к свойству.
  • Значение выражения доступа индексатора получается путем вызова аксессора получения индексатора. Если индексатор не имеет метода доступа, возникает ошибка во время компиляции. В противном случае вызов члена функции (§12.6.6) выполняется со списком аргументов, связанным с выражением доступа индексатора, и результат вызова становится значением выражения доступа индексатора.
  • Значение выражения кортежа определяется с помощью применения неявного преобразования кортежей (§10.2.13) к типу данного выражения кортежа. Ошибка возникает при попытке получить значение выражения кортежа, которое не имеет типа.

12.3 Статическая и динамическая привязка

12.3.1 Общие

связывание — это процесс определения того, к чему относится операция на основе типа или значения выражений (аргументов, операндов, получателей). Например, привязка вызова метода определяется на основе типа приемника и аргументов. Привязка оператора определяется на основе типа операндов.

В C# привязка операции обычно определяется во время компиляции на основе типа времени компиляции его вложенных выражений. Аналогичным образом, если выражение содержит ошибку, ошибка обнаруживается и сообщается во время компиляции. Этот подход называется статической привязкой.

Однако если выражение является динамическим выражением (т. е. имеет тип dynamic), это означает, что любая привязка, в которую она участвует, должна основываться на его типе времени выполнения, а не на типе, который он имеет во время компиляции. Поэтому привязка такой операции откладывается до того момента, когда операция будет исполнена в процессе выполнения программы. Это называется динамической привязкой.

При динамической привязке операции проверка не выполняется во время компиляции. Вместо этого, если привязка во время выполнения завершается ошибкой, ошибки передаются как исключения во время выполнения.

Следующие операции в C# подлежат привязке:

  • Доступ к члену: e.M
  • Вызов метода: e.M(e₁,...,eᵥ)
  • Вызов делегата: e(e₁,...,eᵥ)
  • Доступ к элементам: e[e₁,...,eᵥ]
  • Создание объекта: новый C(e₁,...,eᵥ)
  • Перегруженные унарные операторы: +, -, ! (только логические отрицания), ~, ++, --, true, false
  • Перегруженные двоичные операторы: +, -, *, /, %, &, &&, |, ||, ??, ^, <<>>, ==, !=, >, <, >=, <=
  • Операторы назначения: =, = ref, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=
  • Неявные и явные преобразования

Если динамические выражения не используются, C# по умолчанию использует статическую привязку, что означает, что в процессе выбора используются типы вложенных выражений во время компиляции. Однако если одно из вложенных выражений в перечисленных выше операциях является динамическим выражением, операция вместо этого динамически привязана.

Это ошибка времени компиляции, если вызов метода динамически привязан и любой из параметров, включая приемника, являются входными параметрами.

12.3.2 Время привязки

Статическая привязка выполняется во время компиляции, в то время как динамическая привязка выполняется во время выполнения. В следующих подклаузах термин время привязки относится к времени компиляции или времени выполнения в зависимости от того, когда выполняется привязка.

пример. Ниже показаны понятия статической и динамической привязки и времени привязки:

object o = 5;
dynamic d = 5;
Console.WriteLine(5); // static binding to Console.WriteLine(int)
Console.WriteLine(o); // static binding to Console.WriteLine(object)
Console.WriteLine(d); // dynamic binding to Console.WriteLine(int)

Первые два вызова статически привязаны: перегрузка функции Console.WriteLine выбирается на основе типа аргумента во время компиляции. Таким образом, время связывания — это время компиляции.

Третий вызов динамически привязан: перегрузка Console.WriteLine выбирается на основе типа времени выполнения аргумента. Это происходит потому, что аргумент является динамическим выражением— его тип времени компиляции является динамическим. Таким образом, время привязки для третьего вызова — время выполнения.

конечный пример

12.3.3 Динамическая привязка

Этот подпункт является информативным.

Динамическая привязка позволяет программам C# взаимодействовать с динамическими объектами, т. е. объектами, которые не соответствуют обычным правилам системы типов C#. Динамические объекты могут быть объектами из других языков программирования с различными системами типов, или они могут быть объектами, которые программно настраиваются для реализации собственной семантики привязки для различных операций.

Механизм, с помощью которого динамический объект реализует собственную семантику, определяется реализацией. Определенный интерфейс , который снова определен реализацией, реализуется динамическими объектами, чтобы сообщить времени выполнения C#, что они имеют специальную семантику. Таким образом, всякий раз, когда операции на динамическом объекте выполняются с динамическим связыванием, их собственная семантика привязки, а не семантика, определённая C#, начинает действовать.

Хотя динамическая привязка позволяет разрешить взаимодействие с динамическими объектами, C# разрешает динамическую привязку ко всем объектам, независимо от того, являются ли они динамическими или нет. Это позволяет более плавно интегрировать динамические объекты, так как результаты операций с ними могут не быть динамическими объектами, но по-прежнему являются типом, неизвестным программисту во время компиляции. Кроме того, динамическая привязка может помочь устранить подверженный ошибкам код на основе отражения, даже если ни один из объектов не является динамическим.

12.3.4 Типы вложенных выражений

При статической привязке операции тип вложенных выражений (например, приемника и аргумента, индекса или операнда) всегда считается типом времени компиляции этого выражения.

При динамической привязке операции тип подвыражений определяется разными способами в зависимости от типа подвыражения во время компиляции.

  • Подэкспрессия динамического типа компиляции считается типом фактического значения, которое выражение вычисляет во время выполнения.
  • Подвыражение, тип которого на момент компиляции является параметром типа, считается имеющим тип, к которому привязан параметр типа во время выполнения.
  • В противном случае подвыражение считается имеющим тип времени компиляции.

Операторы 12.4

12.4.1 Общие

Выражения создаются из операндов и операторов. Операторы выражения указывают, какие операции применяются к операндам.

пример. Примеры операторов включают +, -, *, /и new. Примеры операндов включают литералы, поля, локальные переменные и выражения. конечный пример

Существует три типа операторов:

  • Унарные операторы. Унарные операторы принимают один операнд и используют нотацию префикса (например, –x) или нотацию постфикса (например, x++).
  • Двоичные операторы. Двоичные операторы принимают два операнда и используют инфиксную нотацию (например, x + y).
  • Оператор тернарный. Существует только один тернарный оператор, ?:; он принимает три операнда и использует нотацию infix (c ? x : y).

Порядок оценки операторов в выражении определяется приоритетом и ассоциативностью операторов (§12.4.2).

Операнды в выражении вычисляются слева направо.

пример: в F(i) + G(i++) * H(i)метод F вызывается с использованием старого значения i, затем метод G вызывается со старым значением i, и, наконец, метод H вызывается с новым значением i. Это отличается от приоритета оператора и не связано с ним. конечный пример

Некоторые операторы могут быть перегружены . Перегрузка оператора (§12.4.3) позволяет указывать определяемые пользователем реализации операторов для операций, в которых один или оба операнда являются определяемым пользователем классом или типом структуры.

Приоритет оператора 12.4.2 и ассоциативность

Если выражение содержит несколько операторов, приоритет операторов определяет порядок оценки отдельных операторов.

примечание. Например, выражение x + y * z оценивается как x + (y * z), так как оператор * имеет более высокий приоритет, чем оператор двоичного +. конечная сноска

Приоритет оператора устанавливается определением связанной грамматической продукции.

Примечание. Например, additive_expression состоит из последовательности multiplicative_expression, разделенных операторами + или -, что дает операторам + и - более низкий приоритет, чем у операторов *, /и %. конечная сноска

примечание. В следующей таблице перечислены все операторы в порядке приоритета от самого высокого до самого низкого:

Подпункт категория Операторы
§12.8 Первичный x.y x?.y f(x) a[x] a?[x] x++ x-- x! new typeof default checked unchecked delegate stackalloc
§12.9 Одинарный + - !x ~ ++x --x (T)x await x
§12.10 Мультипликативный * / %
§12.10 Добавка + -
§12.11 Сдвиг << >>
§12.12 Реляционное и типопроверочное тестирование < > <= >= is as
§12.12 Равенство == !=
§12.13 Логическое AND &
§12.13 Логический XOR ^
§12.13 Логическое ИЛИ \|
§12.14 Условный И &&
§12.14 Условное ИЛИ \|\|
§12.15 и §12.16 Объединение и исключение значений NULL ?? throw x
§12.18 Условный ?:
§12.21 и §12.19 Присваивание и лямбда-выражение = = ref *= /= %= += -= <<= >>= &= ^= \|= =>

концевая заметка

При возникновении операнда между двумя операторами с одинаковым приоритетом ассоциативность операторов управляет порядком выполнения операций:

  • За исключением операторов назначения и оператора объединения null, все двоичные операторы левых ассоциативных, то есть операции выполняются слева направо.

    пример: x + y + z оценивается как (x + y) + z. конечный пример

  • Операторы присваивания, оператор объединения NULL и условный оператор (?:) являются правоассоциативными, то есть операции выполняются справа налево.

    пример: x = y = z оценивается как x = (y = z). конечный пример

Приоритет и ассоциативность можно контролировать с помощью круглых скобок.

пример: x + y * z сначала умножает y на z, а затем добавляет результат в x, но (x + y) * z сначала добавляет x и y, а затем умножает результат на z. конечный пример

Перегрузка оператора 12.4.3

Все унарные и двоичные операторы имеют предопределенные реализации. Кроме того, определяемые пользователем реализации можно вводить путём включения объявлений операторов (§15.10) в классах и структурах. Когда реализации операторов, определяемые пользователем, не существуют, только тогда будут рассматриваться предопределенные реализации операторов, как описано в §12.4.4 и §12.4.5.

Перегружаемые унарные операторы :

+ - ! (только логическое отрицание) ~ ++ -- true false

Примечание. Хотя true и false не используются явным образом в выражениях (и поэтому не включаются в таблицу приоритетов в §12.4.2), они считаются операторами, так как они вызываются в нескольких контекстах выражений Логические выражения (§12.24) и выражения, включающие условные (§12.18) и условные логические операторы (§12.14). конечная сноска

Примечание. Оператор игнорирования null (постфикс !, §12.8.9) не является оператором, поддерживающим перегрузку. конечная заметка

Перегружаемые двоичные операторы включают:

+  -  *  /  %  &  |  ^  <<  >>  ==  !=  >  <  <=  >=

Можно перегружать только указанные выше операторы. В частности, невозможно перегрузить доступ к членам, вызов метода, а также операторы =, &&, ||, ??, ?:, =>, checked, unchecked, new, typeof, default, asи is.

При перегрузке двоичного оператора соответствующий оператор составного назначения(если таковой) также неявно перегружен.

пример: перегрузка оператора * также является перегрузкой *=оператора. Это описано далее в §12.21. конечный пример

Сам оператор назначения (=) не может быть перегружен. Присваивание всегда осуществляет простое помещение значения в переменную (§12.21.2).

Операции приведения, такие как (T)x, перегружены путем предоставления определяемых пользователем преобразований (§10,5).

примечание. Определяемые пользователем преобразования не влияют на поведение операторов is или as. концевая сноска

Доступ к элементам, например a[x], не считается перегруженным оператором. Вместо этого определяемая пользователем индексация поддерживается с помощью индексаторов (§15.9).

В выражениях операторы ссылаются с помощью нотации оператора и в объявлениях операторы ссылаются с помощью функциональной нотации. В следующей таблице показана связь между оператором и функциональными нотациями для унарных и двоичных операторов. В первой записи "op" обозначает любой перегруженный унарный префикс оператор. Во второй записи «op» обозначает унарные постфиксные операторы ++ и --. В третьей записи "op" обозначает любой перегруженный двоичный оператор.

примечание. Пример перегрузки операторов ++ и -- см. в разделе §15.10.2. концевая сноска

Нотация оператора функциональная нотация
«op» x operator «op»(x)
x «op» operator «op»(x)
x «op» y operator «op»(x, y)

Объявления определяемых пользователем операторов всегда требуют, по крайней мере, одного из параметров класса или типа структуры, содержащего объявление оператора.

Примечание. Таким образом, определяемый пользователем оператор не может иметь ту же подпись, что и предопределенный оператор. конечная заметка

Объявления определяемых пользователем операторов не могут изменять синтаксис, приоритет или ассоциативность оператора.

примере: оператор / всегда является двоичным оператором, всегда имеет уровень приоритета, указанный в §12.4.2, и всегда является левым ассоциативным. конечный пример

Примечание. Хотя определяемый пользователем оператор может выполнять любые вычисления, реализации, которые создают результаты, отличные от тех, которые интуитивно ожидаются, настоятельно не рекомендуется. Например, реализация оператора == должна сравнивать два операнда для равенства и возвращать соответствующий результат bool. конец примечания

Описание отдельных операторов от §12.9 до §12.21 указывают предварительно определенные реализации операторов и любые дополнительные правила, которые применяются к каждому оператору. В описаниях используются термины разрешение перегрузки унарных операторов, разрешение перегрузки бинарных операторов, числовая промоцияи определения подъёмных операторов, которые находятся в следующих подразделах.

Разрешение перегрузки унарного оператора 12.4.4

Операция формы «op» x или x «op», где "op" является перегруженным унарным оператором, и x является выражением типа X, обрабатывается следующим образом:

  • Набор операторов, заданных пользователем и предоставляемых X для выполнения операции operator «op»(x), определяется в соответствии с правилами §12.4.6.
  • Если набор пользовательских операторов не является пустым, это становится набором операторов-кандидатов для операции. В противном случае предопределенные двоичные operator «op» реализации, включая их поднятые формы, становятся набором операторов-кандидатов для операции. Предопределенные реализации данного оператора указываются в описании оператора. Предопределенные операторы, предоставляемые типом перечисления или делегата, включаются в этот набор только в том случае, если тип времени привязки (или базовый тип, если он может принимать значение NULL) одного из операндов является типом перечисления или делегата.
  • Правила разрешения перегрузки §12.6.4 применяются к набору операторов-кандидатов, чтобы выбрать лучший оператор в отношении списка аргументов (x), и этот оператор становится результатом процесса разрешения перегрузки. Если разрешение перегрузки не удается выбрать один лучший оператор, возникает ошибка во время привязки.

12.4.5 Разрешение перегрузки двоичного оператора

Операция формы x «op» y, где "op" является перегруженным двоичным оператором, x является выражением типа X, а y является выражением типа Y, обрабатывается следующим образом:

  • Определяется набор кандидатных пользовательских операторов, предоставляемых X и Y для операции operator «op»(x, y). Набор состоит из объединения операторов-кандидатов, предоставляемых X, и операторов кандидатов, предоставляемых Y, каждый определяется правилами §12.4.6. Для объединенного набора кандидаты объединяются следующим образом:
    • Если X и Y являются тождественно преобразуемыми, или если X и Y являются производными от общего базового типа, то общие операторы-кандидаты встречаются в совмещенном наборе только один раз.
    • Если существует преобразование идентичности между X и Y, оператор «op»Y, предоставляемый Y, имеет тот же тип возврата, что и «op»X, предоставляемый X, а типы операндов «op»Y имеют преобразование идентичности в соответствующие типы операндов «op»X, то в наборе присутствует только «op»X.
  • Если набор операторов, определенных пользователем, не пуст, он становится набором операторов-кандидатов для операции. В противном случае предопределенные двоичные operator «op» реализации, включая их поднятые формы, становятся набором операторов-кандидатов для операции. Предопределенные реализации данного оператора указываются в описании оператора. Для предопределенных операторов перечисления и делегата рассматриваются только те операторы, которые определены типом перечисления или делегата, который соответствует типу на момент привязки одного из операндов.
  • Правила разрешения перегрузки §12.6.4 применяются к набору операторов-кандидатов, чтобы выбрать лучший оператор в отношении списка аргументов (x, y), и этот оператор становится результатом процесса разрешения перегрузки. Если разрешение перегрузки не удается выбрать один лучший оператор, возникает ошибка во время привязки.

Кандидатные операторы, определяемые пользователем 12.4.6

Учитывая тип T и операцию operator «op»(A), где «op» является оператором, поддающимся перегрузке, и A является списком аргументов, набор определяемых пользователем операторов, предоставляемых T для оператора «op»(A), определяется следующим образом:

  • Определите тип T₀. Если T является nullable типом значения, то T₀ является его базовым типом; в противном случае T₀ равно T.
  • Для всех объявлений operator «op» в T₀ и всех поднятых форм таких операторов, если применим хотя бы один оператор (§12.6.4.2) в отношении списка аргументов A, то набор операторов-кандидатов состоит из всех этих применимых операторов в T₀.
  • В противном случае, если T₀object, множество операторов-кандидатов пусто.
  • В противном случае набор операторов кандидатов, предоставляемых T₀, является набором операторов кандидатов, предоставляемых прямым базовым классом T₀, или эффективным базовым классом T₀, если T₀ является параметром типа.

12.4.7 Числовые акции

12.4.7.1 Общие

Этот подпункт является информативным.

§12.4.7 и его подпункты являются сводкой объединённого воздействия:

  • правила неявных числовых преобразований (§10.2.3);
  • правила для улучшения конверсии (§12.6.4.7); и.
  • доступные арифметические (§12.10), реляционные (§12.12) и целые логические (§12.13.2) операторы.

Числовое продвижение состоит из автоматического выполнения определенных неявных преобразований операндов предопределенных унарных и двоичных числовых операторов. Числовое повышение не является отдельным механизмом, а эффектом применения разрешения перегрузки к предопределенным операторам. Конкретно числовое повышение не влияет на оценку определяемых пользователем операторов, хотя определяемые пользователем операторы могут быть реализованы таким образом, чтобы создавать аналогичные эффекты.

В качестве примера числового продвижения рассмотрим предопределенные реализации двоичного оператора *:

int operator *(int x, int y);
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);
float operator *(float x, float y);
double operator *(double x, double y);
decimal operator *(decimal x, decimal y);

Если правила разрешения перегрузки (§12.6.4) применяются к этому набору операторов, эффект заключается в выборе первого из операторов, для которых неявные преобразования существуют из типов операнда.

пример. Для операции b * s, где b — это byte, а s — это short, разрешение перегрузки выбирает operator *(int, int) как лучший оператор. Таким образом, эффект заключается в том, что b и s преобразуются в int, а тип результата int. Аналогичным образом, для операции i * d, где i является int, а d это double, резолюция overload выбирает operator *(double, double) в качестве лучшего оператора. конечный пример

конец информативного текста.

12.4.7.2 Унарные числовые преобразования

Этот подпункт является информативным.

Унарное числовое продвижение происходит для операндов предопределенных +, -и ~ унарных операторов. Унарное числовое продвижение просто состоит из преобразования операндов типа sbyte, byte, short, ushortили char в тип int. Кроме того, для унарного оператора – унарная числовая промоция преобразует операнды типа uint в тип long.

конец информативного текста.

12.4.7.3 Двоичные числовые акции

этот подпункт информативный.

Двоичное числовое продвижение выполняется для операндов предопределенных двоичных операторов +, -, *, /, %, &, |, ^, ==, !=, >, <, >=и <=. Двоичное числовое повышение неявно преобразует оба операнда в общий тип, который, в случае нереляционных операторов, также становится типом результата операции. Двоичное числовое повышение состоит из применения следующих правил, в том порядке, в который они отображаются здесь:

  • Если либо операнд имеет тип decimal, другой операнд преобразуется в тип decimal, или возникает ошибка времени привязки, если другой операнд имеет тип float или double.
  • В противном случае, если какой-либо операнд имеет тип double, другой операнд преобразуется в тип double.
  • В противном случае, если какой-либо операнд имеет тип float, другой операнд преобразуется в тип float.
  • В противном случае, если один из операндов имеет тип ulong, другой операнд преобразуется в тип ulong; или возникает ошибка времени привязки, если другой операнд имеет тип type sbyte, short, intили long.
  • В противном случае, если какой-либо операнд имеет тип long, другой операнд преобразуется в тип long.
  • В противном случае, если либо операнд имеет тип uint, а другой операнда имеет тип sbyte, shortили int, оба операнда преобразуются в тип long.
  • В противном случае, если какой-либо операнд имеет тип uint, другой операнд преобразуется в тип uint.
  • В противном случае оба операнда преобразуются в тип int.

Примечание. Первое правило запрещает любые операции, которые смешивают тип decimal с типами double и float. Правило следует из того факта, что между типом decimal и типами double и float нет неявных преобразований. конец заметки

Примечание. Кроме того, обратите внимание, что операнд не может быть типа ulong, если другой операнд имеет подписанный целочисленный тип. Причина заключается в том, что целочисленный тип не существует, который может представлять полный диапазон ulong, а также подписанных целочисленных типов. конечная заметка

В обоих из указанных выше случаев выражение приведения можно использовать для явного преобразования одного операнда в тип, совместимый с другим операндом.

пример: в следующем коде

decimal AddPercent(decimal x, double percent) =>
    x * (1.0 + percent / 100.0);

Ошибка во время привязки возникает, так как decimal не может быть умножена на double. Ошибка устранена путем явного преобразования второго операнда в decimalследующим образом:

decimal AddPercent(decimal x, double percent) =>
    x * (decimal)(1.0 + percent / 100.0);

конечный пример

конец информативного текста.

Операторы, коих значение поднимается (lifted), 12.4.8

Операторы облегчённые позволяют предопределённым и пользовательским операторам, работающим с типами значений, не допускающими NULL, также работать с их формами, допускающими NULL. Операторы лифта создаются из предопределенных и определяемых пользователем операторов, которые соответствуют определенным требованиям, как описано в следующем примере:

  • Для унарных операторов +, ++, -, --, !(логическое отрицание) и ~существует поднятая форма оператора, если типы операндов и результатов являются ненулевыми типами значений. Поднятая форма создается путем добавления единственного модификатора ? к типам операндов и результатов. Поднятый оператор производит значение null, если операнд равен null. В противном случае оператор распаковывает операнд, применяет базовый оператор и оборачивает результат.
  • Для двоичных операторов +, -, *, /, %, &, |, ^, <<и >>существует повышенная форма оператора, если типы операндов и результаты являются всеми значениями типов, не допускающими значение NULL. Поднятая форма создается путем добавления одного модификатора ? к каждому операнду и типу результатов. Оператор лифта создает значение null, если один или оба операнда null (исключение является & и | операторами типа bool?, как описано в §12.13.5). В противном случае оператор распаковывает операнды, применяет базовый оператор и оборачивает результат.
  • Для операторов равенства == и !=существует расширенная форма оператора, если типы операндов являются ненуляемыми типами значений и если тип результата bool. Поднятая форма создается путем добавления одного модификатора ? к каждому типу операнда. Оператор поднятия рассматривает два значения null равными, а значение null неравным любому значению, отличному отnull. Если оба операнда не являютсяnull, повышенный оператор раскрывает операнды и применяет базовый оператор для получения результата bool.
  • Для реляционных операторов <, >, <=и >=существует расширенная форма оператора, если оба типа операндов являются ненулевыми типами значений и если тип результата bool. Поднятая форма создается путем добавления одного модификатора ? к каждому типу операнда. Поднятый оператор создает значение false, если один или оба операнда равны null. В противном случае оператор распаковывает операнды и применяет основной оператор для получения результата bool.

12.5 Поиск участников

12.5.1 Общие

Поиск члена — это процесс, в котором определяется значение имени в контексте типа. Поиск элементов может происходить в рамках оценки simple_name (§12.8.4) или member_access (§12.8.7) в выражении. Если simple_name или member_access выступает в качестве primary_expression в invocation_expression (§12.8.10.2), то говорят, что этот член вызывается.

Если элемент является методом или событием, либо является константой, полем или свойством любого типа делегата (§20) или типа dynamic (§8.2.4), то элемент называется вызываемым.

Поиск членов учитывает не только имя члена, но и количество параметров типа, которое у него есть, и доступен ли член. Для поиска элементов универсальные методы и вложенные универсальные типы имеют количество параметров типа, указанных в их соответствующих объявлениях, и все остальные члены имеют параметры нулевого типа.

Поиск члена N с помощью аргументов типа K в типе T выполняется следующим образом:

  • Сначала определяется набор доступных членов с именем N:
    • Если T является параметром типа, то набор является объединением наборов доступных элементов с именем N в типах, которые указаны как основное или дополнительное ограничение (§15.2.5) для T, вместе с набором доступных элементов с именем N в object.
    • В противном случае набор состоит из всех доступных элементов (§7.5) с именем N в T, включая унаследованные элементы и доступные члены с именем N в object. Если T является созданным типом, набор элементов получается путем замены аргументов типа, как описано в §15.3.3. Элементы, включающие модификатор override, исключаются из набора.
  • Затем, если K равно нулю, все вложенные типы, объявления которых включают параметры типа, удаляются. Если K не равно нулю, удаляются все члены с другим числом параметров типа. Если K равно нулю, методы с параметрами типа не удаляются, так как процесс вывода типов (§12.6.3) может иметь возможность выводить аргументы типа.
  • Затем, если вызывается член, все невызваемые члены удаляются из набора.
  • Затем элементы, скрытые другими элементами, удаляются из набора. Для каждого члена S.M в наборе, где S - это тип, в котором объявлен член M, применяются следующие правила:
    • Если M является константой, полем, свойством, событием или элементом перечисления, все элементы, объявленные в базовом типе S, удаляются из набора.
    • Если M является объявлением типа, все нетипы, объявленные в базовом типе S, удаляются из набора, а все объявления типов с одинаковым числом параметров типа, что и M объявленные в базовом типе S удаляются из набора.
    • Если M является методом, все члены, не являющиеся методом, объявленные в базовом типе S, удаляются из набора.
  • Затем элементы интерфейса, скрытые членами класса, удаляются из набора. Этот шаг действует только в том случае, если T является параметром типа и T имеет как эффективный базовый класс, отличный от object, так и непустый набор эффективных интерфейсов (§15.2.5). Для каждого элемента S.M в наборе, где S является типом, в котором объявлен M как член, применяются следующие правила, если S является объявлением класса, не равного object:
    • Если M является константой, полем, свойством, событием, элементом перечисления или объявлением типа, то все члены, объявленные в объявлении интерфейса, удаляются из набора.
    • Если M является методом, то все элементы, не являющиеся методами, объявленные в объявлении интерфейса, удаляются из набора, а все методы с той же сигнатурой, что и M, объявленные в объявлении интерфейса, удаляются из набора.
  • Наконец, при удалении скрытых элементов результат подстановки определяется:
    • Если набор состоит из одного элемента, который не является методом, этот элемент является результатом поиска.
    • В противном случае, если набор содержит только методы, эта группа методов является результатом поиска.
    • В противном случае поиск является неоднозначным, и возникает ошибка во время привязки.

Для поиска членов в типах, отличных от параметров типа и интерфейсов, и поиска членов в интерфейсах, которые строго соблюдают однонаследование (каждый интерфейс в цепочке наследования имеет ровно ноль или один прямой базовый интерфейс), эффект правил заключается в том, что производные члены скрывают базовые члены с тем же именем или сигнатурой. Такие поиски при одном наследовании никогда не являются неоднозначными. Неоднозначности, которые могут возникнуть из-за поиска элементов в интерфейсах с множественным наследованием, описаны в §18.4.6.

Примечание: этот этап учитывает только одну неоднозначность. Если поиск элемента приводит к образованию группы методов, дальнейшее использование группы методов может завершиться неудачей из-за неоднозначности, например, как описано в §12.6.4.1 и §12.6.6.2. конец примечания

Базовые типы 12.5.2

Для поиска членов тип T рассматривается как имеющий следующие базовые типы:

  • Если Tobject или dynamic, то T не имеет базового типа.
  • Если T это enum_type, базовые типы T — это типы классов System.Enum, System.ValueTypeи object.
  • Если T является структурного типа, базовые типы T являются классовыми типами System.ValueType и object.

    примечание: nullable_value_type — это struct_type (§8.3.1). конечная сноска

  • Если T является class_type, базовые типы T являются базовыми классами T, включая тип класса object.
  • Если T является interface_type, то базовыми типами T являются как базовые интерфейсы T, так и тип класса object.
  • Если T является array_type, базовые типы T являются типами классов System.Array и object.
  • Если является делегатом типа, то базовые типы являются классами типа и .

12.6 Члены функции

12.6.1 Общие

Члены функции — это члены, содержащие исполняемые инструкции. Члены функции всегда являются членами типов и не могут быть членами пространств имен. C# определяет следующие категории членов функции:

  • Методика
  • Свойства
  • События
  • Индексаторы
  • Определяемые пользователем операторы
  • Конструкторы экземпляров
  • Статические конструкторы
  • Финализаторы

Кроме методов завершения и статических конструкторов (которые не могут вызываться явным образом), инструкции, содержащиеся в членах функции, выполняются с помощью вызовов элементов функции. Фактический синтаксис для записи вызова элемента функции зависит от конкретной категории членов функции.

Список аргументов (§12.6.2) вызова элемента функции предоставляет фактические значения или ссылки на переменные для параметров элемента функции.

Вызовы универсальных методов могут использовать вывод типов для определения набора аргументов типа для передачи в метод. Этот процесс описан в §12.6.3.

Вызовы методов, индексаторов, операторов и конструкторов экземпляров используют разрешение перегрузки для определения, какой из множества кандидатов функций следует вызвать. Этот процесс описан в разделе §12.6.4.

После определения определенного члена функции во время привязки, возможно, путем разрешения перегрузки, фактический процесс выполнения вызова члена функции описывается в §12.6.6.

примечание. В следующей таблице приводится сводка обработки, которая выполняется в конструкциях, включающих шесть категорий членов функции, которые могут быть явно вызваны. В таблице e, x, yи value указывают выражения, классифицируемые как переменные или значения, T указывает выражение, классифицированное как тип, F — простое имя метода, а P — простое имя свойства.

Строить Пример Описание
Вызов метода F(x, y) Разрешение перегрузки применяется для выбора оптимального метода F в содержаемом классе или структуре. Метод вызывается со списком аргументов (x, y). Если метод не static, выражение экземпляра является this.
T.F(x, y) Разрешение перегрузки применяется для выбора оптимального метода F в классе или структуре T. Ошибка во время привязки возникает, если метод не static. Метод вызывается со списком аргументов (x, y).
e.F(x, y) Разрешение перегрузки применяется для выбора оптимального метода F в классе, структуре или интерфейсе, заданном типом e. Ошибка во время привязки возникает, если метод static. Метод вызывается с помощью выражения экземпляра e и списка аргументов (x, y).
Доступ к свойствам P Вызывается метод доступа свойства P в содержаемом классе или структуре. Ошибка во время компиляции возникает, если P доступна только для записи. Если P не равен static, то выражение экземпляра this.
P = value Метод доступа к набору свойств, P в содержаемом классе или структуре, вызывается со списком аргументов (value). Ошибка во время компиляции возникает, если P доступна только для чтения. Если P не static, то выражение экземпляра this.
T.P Вызывается метод доступа свойства P класса или структуры, T. Ошибка во время компиляции возникает, если P не static или если P доступна только для записи.
T.P = value Сеттер свойства P в классе или структуре T вызывается со списком аргументов (value). Ошибка во время компиляции возникает, если P не static или если P доступно только для чтения.
e.P Метод доступа свойства P класса, структуры или интерфейса, заданного типом E, вызывается с помощью выражения экземпляра e. Ошибка времени привязки возникает, если P является static или если P доступен только для записи.
e.P = value Метод доступа к свойству P в классе, структуре или интерфейсе, заданных типом E, вызывается с помощью выражения экземпляра e и списка аргументов (value). Ошибка времени привязки возникает, если P является static или если P доступно только для чтения.
Доступ к событиям E += value Вызывается метод доступа add события E в содержащем классе или структуре. Если E не static, выражение экземпляра this.
E -= value Вызывается метод удаления доступа к событию E в содержающем классе или структуре. Если E не static, выражение экземпляра this.
T.E += value Вызывается метод добавления доступа к событию E в классе или структуре T. Ошибка во время привязки возникает, если E не static.
T.E -= value Вызывается метод удаления события E в классе или структуре T. Ошибка во время привязки возникает, если E не static.
e.E += value Аксессор добавления события E класса, структуры или интерфейса, определённого типом E, вызывается с помощью выражения экземпляра e. Ошибка времени привязки возникает, если E является static.
e.E -= value Метод удаления доступа к событию E в классе, структуре или интерфейсе типа E вызывается с использованием выражения экземпляра e. Ошибка времени привязки возникает, если E равно static.
Доступ индексатора e[x, y] Разрешение перегрузки применяется для выбора лучшего индексатора в классе, структуре или интерфейсе, заданном типом e. Аксессор 'get' индексатора вызывается для экземпляра e со списком аргументов (x, y). Ошибка во время привязки возникает, если индексатор доступен только для записи.
e[x, y] = value Разрешение перегрузки применяется для выбора лучшего индексатора в классе, структуре или интерфейсе, заданном типом e. Метод доступа к набору индексатора вызывается с помощью выражения экземпляра e и списка аргументов (x, y, value). Ошибка во время привязки возникает, если индексатор доступен только для чтения.
Вызов оператора -x Разрешение перегрузки применяется для выбора лучшего унарного оператора в классе или структуре, заданной типом x. Выбранный оператор вызывается со списком аргументов (x).
x + y Разрешение перегрузки применяется для выбора лучшего двоичного оператора в классах или структурах, заданных типами x и y. Выбранный оператор вызывается со списком аргументов (x, y).
Вызов конструктора экземпляра new T(x, y) Разрешение перегрузки применяется для выбора лучшего конструктора экземпляра в классе или структуре T. Конструктор экземпляра вызывается со списком аргументов (x, y).

концевая заметка

Списки аргументов 12.6.2

12.6.2.1 Общие

Каждый член функции и вызов делегата включает список аргументов, предоставляющий фактические значения или ссылки на переменные для параметров элемента функции. Синтаксис для указания списка аргументов вызова элемента функции зависит от категории членов функции:

  • Например, конструкторы, методы, индексаторы и делегаты, аргументы указываются как argument_list, как описано ниже. Для индексаторов при вызове набора метода доступа список аргументов дополнительно включает выражение, указанное в качестве правого операнда оператора назначения.

    Примечание. Этот дополнительный аргумент не используется для разрешения перегрузки, только при вызове метода доступа. конечная сноска

  • Для свойств список аргументов пуст при вызове аксессора get и состоит из выражения, указанного в качестве правого операнда оператора присваивания при вызове аксессора set.
  • Для событий список аргументов состоит из выражения, указанного в качестве правого операнда оператора += или -=.
  • Для определяемых пользователем операторов список аргументов состоит из одного операнда унарного оператора или двух операндов двоичного оператора.

Аргументы свойств (§15.7) и события (§15.8) всегда передаются в качестве параметров значения (§15.6.2.2). Аргументы определяемых пользователем операторов (§15.10) всегда передаются в качестве параметров значения (§15.6.2.2) или входных параметров (§9.2.8). Аргументы индексаторов (§15.9) всегда передаются в качестве параметров значения (§15.6.2.2), входных параметров (§9.2.8) или массивов параметров (§15.6.2.4). Выходные и ссылочные параметры не поддерживаются для этих категорий членов функции.

Аргументы конструктора экземпляра, метода, индексатора или вызова делегата задаются как argument_list:

argument_list
    : argument (',' argument)*
    ;

argument
    : argument_name? argument_value
    ;

argument_name
    : identifier ':'
    ;

argument_value
    : expression
    | 'in' variable_reference
    | 'ref' variable_reference
    | 'out' variable_reference
    ;

argument_list состоит из одного или нескольких аргументов , разделенных запятыми. Каждый аргумент состоит из необязательного аргумента argument_name, за которым следует значение аргумента. Аргумент с argument_name называется именованным аргументом, а аргумент без argument_name является позиционным аргументом.

argument_value может принимать одну из следующих форм:

  • Выражение , указывающее, что аргумент передается в качестве параметра типа 'значение' или преобразуется во входной параметр и затем передается как таковой, как определено в§12.6.4.2 и описано в §12.6.2.3.
  • Ключевое слово in за переменной ссылка на переменную (§9.5), указывающее, что аргумент передается в качестве входного параметра (§15.6.2.3.2). Переменная должна быть определенно назначена (§9.4) перед передачей в качестве входного параметра.
  • Ключевое слово ref, следующее за variable_reference (§9.5), указывает на то, что аргумент передается в качестве параметра-ссылки (§15.6.2.3.3). Переменная должна быть определенно назначена (§9.4) перед передачей в качестве эталонного параметра.
  • Ключевое слово out затем ссылка_на_переменную (§9.5), указывающее, что аргумент передается в качестве выходного параметра (§15.6.2.3.4). Переменная считается определенно назначенной (§9.4) после вызова элемента функции, в котором переменная передается в качестве выходного параметра.

Форма определяет режим передачи параметра аргумента: значение, входное, ссылочноеили выходноесоответственно. Однако, как упоминалось выше, аргумент с режимом передачи значений может быть преобразован в один с режимом передачи входных данных.

Передача изменяющегося поля (§15.5.4) в качестве входного, выходного или ссылочного параметра вызывает предупреждение, так как поле может не рассматриваться как изменяющееся с помощью вызываемого метода.

12.6.2.2 Соответствующие параметры

Для каждого аргумента в списке аргументов должен быть соответствующий параметр в вызываемом элементе функции или делегате.

Список параметров, используемый в следующем, определяется следующим образом:

  • Для виртуальных методов и индексаторов, определенных в классах, список параметров выбирается из первого объявления или переопределения элемента функции, найденного при запуске статического типа приемника, и выполняется поиск по базовым классам.
  • Для частичных методов используется список параметров определения объявления частичного метода.
  • Для всех остальных членов функции и делегатов существует только один список параметров, который используется.

Позиция аргумента или параметра определяется как число аргументов или параметров, предшествующих ему в списке аргументов или списке параметров.

Соответствующие параметры для аргументов члена функции устанавливаются следующим образом:

  • Аргументы argument_list в конструкторах экземпляров, методах, индексаторах и делегатах:
    • Позиционный аргумент, в котором параметр возникает в той же позиции в списке параметров, соответствует указанному параметру, если только параметр не является массивом параметров, а член функции вызывается в его развернутой форме.
    • Позиционный аргумент элемента функции с массивом параметров, вызываемого в развернутой форме, которая возникает в расположении массива параметров в списке параметров или после нее, соответствует элементу в массиве параметров.
    • Именованный аргумент соответствует параметру того же имени в списке параметров.
    • Для индексаторов при вызове метода сеттера выражение, указанное в качестве правого операнда оператора присваивания, соответствует неявному параметру value объявления метода сеттера.
  • Для свойств при вызове аксессора get не используются аргументы. При вызове аксессора set выражение, указанное в качестве правого операнда оператора присваивания, соответствует неявному параметру значения в объявлении аксессора set.
  • Для определяемых пользователем унарных операторов (включая преобразования), один операнд соответствует одному параметру объявления оператора.
  • Для определяемых пользователем двоичных операторов левый операнд соответствует первому параметру, а правый операнду соответствует второму параметру объявления оператора.
  • Неименованный аргумент не соответствует параметру, если он находится после неверно расположенного именованного аргумента или именованного аргумента, который соответствует массиву параметров.

    Примечание: Это предотвращает вызов void M(bool a = true, bool b = true, bool c = true); с помощью M(c: false, valueB);. Первый аргумент используется вне позиции (аргумент используется в первой позиции, но параметр с именем c находится в третьей позиции), поэтому следует назвать следующие аргументы. Другими словами, именованные аргументы, не являющиеся конечными, разрешены только в том случае, если имя и позиция приводят к поиску того же соответствующего параметра. конечная сноска

12.6.2.3. Оценка списков аргументов во время выполнения

Во время выполнения вызова элемента функции (§12.6.6), выражения или переменные ссылки списка аргументов оцениваются слева направо следующим образом:

  • Для аргумента значения, если режим передачи параметра имеет значение

    • Выражение аргумента вычисляется и выполняется неявное преобразование (§10.2) в соответствующий тип параметра. Полученное значение становится начальным значением параметра значения в вызове элемента функции.

    • в противном случае режим передачи параметра является входным. Если аргумент является ссылкой на переменную и существует тождественное преобразование (§10.2.2) между типом аргумента и типом параметра, результирующее место хранения становится местом хранения, представленным параметром в вызове члена функции. В противном случае создается место для хранения с тем же типом, что и у соответствующего параметра. Выражение аргумента вычисляется и выполняется неявное преобразование (§10.2) в соответствующий тип параметра. Полученное значение хранится в этом хранилище. Это расположение хранилища представлено входным параметром в вызове элемента функции.

      Пример: Учитывая следующие объявления и вызовы методов:

      static void M1(in int p1) { ... }
      int i = 10;
      M1(i);         // i is passed as an input argument
      M1(i + 5);     // transformed to a temporary input argument
      

      В вызове метода M1(i) сам i передается в качестве входного аргумента, так как он классифицируется как переменная и имеет тот же тип int, что и входной параметр. В вызове метода M1(i + 5) создается неименованная переменная int, инициализируется со значением аргумента, а затем передается в качестве входного аргумента. См. §12.6.4.2 и §12.6.4.4.

      конечный пример

  • Для входного, выходного или ссылочного аргумента вычисляется ссылка на переменную, а итоговое расположение хранилища становится местом хранения, представленным параметром в вызове элемента функции. Для входного или ссылочного аргумента переменная должна быть определенно назначена в точке вызова метода. Если ссылка на переменную предоставляется в качестве выходного аргумента или является элементом массива reference_type, выполняется проверка во время выполнения, чтобы убедиться, что тип элемента массива идентичен типу параметра. Если эта проверка завершается ошибкой, выбрасывается System.ArrayTypeMismatchException.

примечание: эта проверка времени выполнения требуется из-за ковариации массива (§17.6). конечная сноска

пример: в следующем коде

class Test
{
    static void F(ref object x) {...}

    static void Main()
    {
        object[] a = new object[10];
        object[] b = new string[10];
        F(ref a[0]); // Ok
        F(ref b[1]); // ArrayTypeMismatchException
    }
}

второй вызов F приводит к выбрасыванию System.ArrayTypeMismatchException, потому что фактический тип элемента b является string, а не object.

конечный пример

Методы, индексаторы и конструкторы экземпляров могут объявлять свой последний параметр массивом параметров (§15.6.2.4). Такие элементы функции вызываются либо в обычной, либо в развернутой форме в зависимости от того, чему это применимо (§12.6.4.2).

  • Если член функции с массивом параметров вызывается в обычной форме, аргумент, заданный для массива параметров, должен быть одним выражением, неявно преобразованным (§10.2) к типу массива параметров. В этом случае массив параметров действует точно так же, как параметр значения.
  • Если член функции с массивом параметров вызывается в раскрытой форме, вызов должен указывать ноль или более позиционных аргументов для массива параметров, где каждый аргумент представляет собой выражение, которое неявно преобразуется (§10.2) к типу элемента массива параметров. В этом случае вызов создает экземпляр типа массива параметров с длиной, соответствующей количеству аргументов, инициализирует элементы экземпляра массива с заданными значениями аргументов и использует только что созданный экземпляр массива в качестве фактического аргумента.

Выражения списка аргументов всегда вычисляются в текстовом порядке.

пример: Таким образом, этот пример

class Test
{
    static void F(int x, int y = -1, int z = -2) =>
        Console.WriteLine($"x = {x}, y = {y}, z = {z}");

    static void Main()
    {
        int i = 0;
        F(i++, i++, i++);
        F(z: i++, x: i++);
    }
}

создает выходные данные

x = 0, y = 1, z = 2
x = 4, y = -1, z = 3

конечный пример

Если член функции с массивом параметров вызывается в развернутой форме с по крайней мере одним развернутым аргументом, вызов обрабатывается так же, как если бы выражение создания массива с инициализатором массива (§12.8.17.5) было вставлено вокруг развернутых аргументов. Пустой массив передается при отсутствии аргументов для массива параметров; Не указано, передается ли ссылка в только что выделенный или существующий пустой массив.

Пример: Дано объявление

void F(int x, int y, params object[] args);

следующие вызовы метода в его расширенной форме

F(10, 20, 30, 40);
F(10, 20, 1, "hello", 3.0);

точно соответствует

F(10, 20, new object[] { 30, 40 });
F(10, 20, new object[] { 1, "hello", 3.0 });

конечный пример

Когда аргументы не указаны для элемента функции с соответствующими необязательными параметрами, аргументы, заданные по умолчанию в объявлении функции или метода, неявно передаются. (Это может включать создание расположения хранилища, как описано выше.)

Примечание. Так как они всегда являются постоянными, их оценка не влияет на оценку оставшихся аргументов. заключительное примечание

12.6.3 Вывод типа

12.6.3.1 Общие

При вызове универсального метода без указания аргументов типа метод вывода пытается выводить аргументы типа для вызова. Наличие вывода типа позволяет использовать более удобный синтаксис для вызова универсального метода и позволяет программисту избегать указания сведений о избыточном типе.

пример:

class Chooser
{
    static Random rand = new Random();

    public static T Choose<T>(T first, T second) =>
        rand.Next(2) == 0 ? first : second;
}

class A
{
    static void M()
    {
        int i = Chooser.Choose(5, 213); // Calls Choose<int>
        string s = Chooser.Choose("apple", "banana"); // Calls Choose<string>
    }
}

С помощью вывода типов аргументы типа int и string определяются из аргументов метода.

конечный пример

Вывод типов происходит в процессе привязки вызова метода (§12.8.10.2) и выполняется перед этапом разрешения перегрузки вызова. Если в вызове метода указана определенная группа методов, а аргументы типа не указываются в рамках вызова метода, вывод типа применяется к каждому универсальному методу в группе методов. Если вывод типа выполнен успешно, аргументы выводимых типов используются для определения типов аргументов для последующего разрешения перегрузки. Если при разрешении перегрузки выбирается универсальный метод для вызова, то выведенные аргументы типа используются в качестве аргументов типа для вызова. Если вывод типа для определенного метода завершается ошибкой, этот метод не участвует в разрешении перегрузки. Сбой вывода типа сам по себе не приводит к ошибке времени связывания. Однако часто это приводит к ошибке во время привязки, когда разрешение перегрузки затем не удается найти применимые методы.

Если каждый предоставленный аргумент не соответствует ровно одному параметру в методе (§12.6.2.2), или нет необязательного параметра без соответствующего аргумента, то вывод немедленно завершается ошибкой. В противном случае предположим, что универсальный метод имеет следующую подпись:

Tₑ M<X₁...Xᵥ>(T₁ p₁ ... Tₓ pₓ)

При вызове метода формы M(E₁ ...Eₓ) задача вывода типа — найти уникальные аргументы типа S₁...Sᵥ для каждого из параметров типа X₁...Xᵥ, чтобы вызов M<S₁...Sᵥ>(E₁...Eₓ) стал допустимым.

Процесс вывода типа описывается ниже как алгоритм. Соответствующий компилятор может быть реализован с помощью альтернативного подхода, если он достигает одного результата во всех случаях.

В процессе вывода каждого параметра типа Xᵢфиксированным для определенного типа Sᵢ или нефиксированных с соответствующим набором границ . Каждая из границ является некоторым типом T. Изначально каждая переменная типа Xᵢ не фиксируется с пустым набором ограничений.

Вывод типов выполняется на этапах. Каждый этап пытается определить аргументы типа для дополнительных переменных типа на основе результатов предыдущего этапа. Первый этап выводит начальные границы, тогда как второй этап связывает переменные типа с конкретными типами и выводит дополнительные границы. Второй этап может повторяться несколько раз.

Примечание. Вывод типов также используется в других контекстах, включая преобразование групп методов (§12.6.3.14) и поиск наиболее распространенного типа набора выражений (§12.6.3.15). конечная сноска

12.6.3.2 Первый этап

Для каждого аргумента метода Eᵢ:

  • Если является анонимной функцией, явного типа параметра (§12.6.3.8) выполняется изна
  • В противном случае, если имеет тип , а соответствующий параметр является параметром значения (§15.6.2.2), то вывод (§12.6.3.10) выполняется изв.
  • В противном случае Если Eᵢ имеет тип U, а соответствующий параметр является ссылочным параметром (§15.6.2.3.3) или выходной параметр (§15.6.2 .3.4) то точный вывод (§12.6.3.9) делается отUдоTᵢ.
  • В противном случае Если Eᵢ имеет тип U, а соответствующий параметр является входным параметром (§15.6.2.3.2) и Eᵢ является входным аргументом, то точный вывода (§12.6.3.9) выполняется изUнаTᵢ.
  • В противном случае, если Eᵢ имеет тип U, а соответствующий параметр является входным параметром (§15.6.2.3.2), то нижней границы вывода (§12.6.3.10) выполняется изUнаTᵢ.
  • В противном случае для этого аргумента не делается вывод.

12.6.3.3 Второй этап

Второй этап продолжается следующим образом:

  • Все переменные типа Xᵢ, которые не зависят от (§12.6.3.6Xₑ) являются фиксированными (§12.6.3.12).
  • Если такие переменные типа отсутствуют, все нефиксированные переменные типаXᵢзафиксированы, для которых выполняются все следующие условия:
    • Существует по крайней мере одна переменная типа Xₑ, зависит отXᵢ
    • Xᵢ имеет непустый набор границ
  • Если такие переменные типа не существуют и все еще остаются нефиксированные переменные типа, вывод типов не удается.
  • В противном случае, если дополнительных нефиксированных переменных типа не существует, вывод типов будет успешным.
  • В противном случае для всех аргументов с соответствующим типом параметра , где типы выходных данных (§12.6.3.5) содержат нефиксированные переменные типов , но типы входных данных (§12.6.3.4) нет, вывода типа (§12.6.3.7) выполняется отдо. Затем второй этап повторяется.

Типы входных данных 12.6.3.4

Если E является группой методов или неявно типизированной анонимной функцией и T является типом делегата или типом дерева выражений, то все типы параметров T являются входными типами дляEс типомT.

Типы выходных данных 12.6.3.5

Если E является группой методов или анонимной функцией, а T — типом делегата или деревом выражений, то возвращаемый тип T является выходным типом дляEс типомT.

Зависимость 12.6.3.6

Переменная типа типа зависит непосредственно от переменной типа , если для некоторых аргументов с типом происходит входного типа с типом и происходит в выходного типа с типом .

Xₑ зависит отXᵢ, если Xₑзависит напрямую отXᵢ или от Xᵢзависит непосредственно отXᵥ и Xᵥзависит отXₑ. Таким образом, "зависит от" является транзитивным, но не рефлексивным замыканием "зависит напрямую от".

Вывод типов выходных данных 12.6.3.7

Вывод типа вывода производится из выражения Eдля типа T следующим образом:

  • Если является анонимной функцией с выводным типом возврата (§12.6.3.13) и — это тип делегата или тип дерева выражений с типом возврата , то вывода нижней границы (§12.6.3.10) выполняется отдо.
  • В противном случае, если E является группой методов и T является типом делегата или деревом выражений с типами параметров T₁...Tᵥ и типом возвращаемого значения Tₓ, а результат разрешения перегрузки E с типами T₁...Tᵥ — это единственный метод с типом возвращаемого значения U, то нижняя граница вывода выполняется отUдоTₓ.
  • В противном случае, если E является выражением с типом U, вывода с нижней границой выполняется отUдоT.
  • В противном случае никаких выводов не производится.

Явный вывод типа параметра 12.6.3.8

Вывод явного типа параметра выполняется из выражения Eдля типа T следующим образом:

  • Если E является явно типизированной анонимной функцией с типами параметров U₁...Uᵥ и T является типом делегата или деревом выражений с типами параметров V₁...Vᵥ то для каждого Uᵢточного вывода (§12.6.3.9) выполняется изUᵢ, чтобы соответствующего Vᵢ.

12.6.3.9 Точные выводы

точное выводиз типа Uдля типа V выполняется следующим образом:

  • Если является одним из нефиксированных , то добавляется в набор точных границ для .
  • В противном случае наборы V₁...Vₑ и U₁...Uₑ определяются путем проверки, применяются ли какие-либо из следующих случаев:
    • V — это тип массива V₁[...], а U — это тип массива U₁[...] того же ранга.
    • V относится к типу V₁?, а U относится к типу U₁
    • V является созданным типом C<V₁...Vₑ> и U является созданным типом C<U₁...Uₑ>
      Если любое из этих случаев применяется, то точный вывод производится из каждого Uᵢ к соответствующей Vᵢ.
  • В противном случае никаких выводов не производится.

12.6.3.10 Заключения с нижней границей

Вывод нижней границы от типа Uдля типа V выполняется следующим образом:

  • Если V является одним из нефиксированныхXᵢ, U добавляется в набор нижних границ для Xᵢ.
  • В противном случае, если V является типом V₁? и U является типом U₁? то вывод нижней границы выполняется из U₁ до V₁.
  • В противном случае наборы U₁...Uₑ и V₁...Vₑ определяются путем проверки, применяются ли какие-либо из следующих случаев:
    • V — это тип массива V₁[...], а U — это тип массива U₁[...]одного ранга.
    • V является одним из IEnumerable<V₁>, ICollection<V₁>, IReadOnlyList<V₁>>, IReadOnlyCollection<V₁> или IList<V₁> и U является одномерным типом массива U₁[]
    • V является class, struct, interface или delegate типа C<V₁...Vₑ>, и существует уникальный тип C<U₁...Uₑ>, который U (или, если U является типом parameter, его эффективным базовым классом или любым членом его эффективного набора интерфейсов) идентичен inherits (прямо или косвенно) или реализует (прямо или косвенно) C<U₁...Uₑ>.
    • (Ограничение "уникальность" означает, что в интерфейсе C<T>{} class U: C<X>, C<Y>{}вывод из U на C<T> не осуществляется, так как U₁ может быть X или Y.)
      Если любое из этих случаев применяется, вывод производится из каждой Uᵢ к соответствующему Vᵢ следующим образом:
    • Если Uᵢ не известно как ссылочный тип, тогда делается точный вывод
    • В противном случае, если U является типом массива, выполняется вывод с нижней границой
    • В противном случае, если VC<V₁...Vₑ>, вывод основывается на параметре типа i-thC:
      • Если он ковариантный, то производится вывод с нижней границой.
      • Если это контравариант, то производится вывод с верхней границой.
      • Если инвариантный, то производится точный вывод.
  • В противном случае никаких выводов не производится.

12.6.3.11 Вывод верхнего предела

Вывод верхней границы от типа Uдля типа V выполняется следующим образом:

  • Если V является одним из нефиксированныхXᵢ, то U добавляется в набор верхних границ для Xᵢ.
  • В противном случае наборы V₁...Vₑ и U₁...Uₑ определяются путем проверки, применяются ли какие-либо из следующих случаев:
    • U — это тип массива U₁[...], а V — это тип массива V₁[...]одного ранга.
    • U является одним из IEnumerable<Uₑ>, ICollection<Uₑ>, IReadOnlyList<Uₑ>, IReadOnlyCollection<Uₑ> или IList<Uₑ> и V является одномерным типом массива Vₑ[]
    • U является типом U1?, а V — типом V1?
    • U является классом, структурой, интерфейсом или типом делегата C<U₁...Uₑ>, а V — это тип class, struct, interface или delegate, который является identical для, inherits из (прямо или косвенно), или реализует (прямо или косвенно) уникальный тип C<V₁...Vₑ>
    • (Ограничение "уникальность" означает, что при использовании интерфейса C<T>{} class V<Z>: C<X<Z>>, C<Y<Z>>{}, при выводе из C<U₁> на V<Q>вывод не выполняется. Вывод не производится из U₁ в X<Q> или Y<Q>.)
      Если любое из этих случаев применяется, вывод производится из каждой Uᵢ к соответствующему Vᵢ следующим образом:
    • Если Uᵢ не является ссылочным типом, создается точный вывод
    • В противном случае, если V является типом массива, осуществляется вывод верхнего предела .
    • В противном случае, если U равен C<U₁...Uₑ>, вывод зависит от параметра типа i-th в C:
      • Если он коваричен, делается вывод о верхней границе.
      • Если он является контравариантным, то производится вывод с нижней границой.
      • Если он инвариантный, то производится точный вывод.
  • В противном случае никаких выводов не производится.

12.6.3.12 Исправление

Переменная типа Xᵢ без с набором границ исправлена следующим образом:

  • Набор типов кандидатов Uₑ изначально представляет собой набор всех типов в пределах набора границ для Xᵢ.
  • Каждая граница для Xᵢ рассматривается последовательно: для каждой точной границы U Xᵢ все типы Uₑ, которые не идентичны U, удаляются из набора кандидатов. Для каждой нижней границы U всех типов Uₑ из Xᵢ, к которым отсутствует неявное преобразование из U, исключаются из набора кандидатов. Для каждого верхнего предела U Xᵢ всех типов Uₑ, из которых не неявное преобразование в U удаляются из набора кандидатов.
  • Если среди оставшихся типов кандидатов Uₑ существует уникальный тип V, к которому существует неявное преобразование всех остальных типов кандидатов, то Xᵢ устанавливается как V.
  • В противном случае вывод типа завершается ошибкой.

Тип выводимого возвращаемого значения 12.6.3.13

Выводимый тип возврата анонимной функции F используется во время вывода типов и разрешения перегрузки. Выводимый тип возвращаемого значения можно определить только для анонимной функции, в которой известны все типы параметров, либо потому, что они явно указаны, предоставлены посредством преобразования анонимной функции или определены во время вывода типа для вызова универсального метода.

эффективный тип возврата выводится следующим образом:

  • Если тело F является выражением с типом, то выводимый эффективный возвращаемый тип F является типом этого выражения.
  • Если тело F является блоком, и множество выражений в операторах блока return имеет наилучший общий тип T (§12.6.3.15), то выводимый эффективный тип возвращаемого значения F равен T.
  • В противном случае для Fневозможно вывести эффективный тип возвращаемого значения.

Выводимый тип возврата определяется следующим образом:

  • Если F является асинхронным, и тело F представляет собой либо выражение, классифицируемое как пустое (§12.2), либо блок, в котором ни одно из утверждений return не содержит выражений, то предполагаемый тип возвращаемого значения — «TaskType» (§15.15.1).
  • Если F является асинхронным и имеет выводимый эффективный тип возвращаемого значения T, выводимый тип возвращаемого значения «TaskType»<T>»(§15.15.1).
  • Если F не является асинхронным и имеет выведенный эффективный тип возврата T, то выведенный тип возврата T.
  • В противном случае возвращаемый тип не может быть выведен для F.

пример. В качестве примера вывода типа с участием анонимных функций рассмотрим метод расширения Select, объявленный в классе System.Linq.Enumerable:

namespace System.Linq
{
    public static class Enumerable
    {
        public static IEnumerable<TResult> Select<TSource,TResult>(
            this IEnumerable<TSource> source,
            Func<TSource,TResult> selector)
        {
            foreach (TSource element in source)
            {
                yield return selector(element);
            }
        }
   }
}

Предположим, что пространство имен System.Linq импортировано с директивой using namespace, и дан класс Customer с свойством Name типа string, метод Select можно использовать для выбора имен из списка клиентов.

List<Customer> customers = GetCustomerList();
IEnumerable<string> names = customers.Select(c => c.Name);

Вызов метода расширения (§12.8.10.3) Select обрабатывается путем перезаписи вызова в вызов статического метода:

IEnumerable<string> names = Enumerable.Select(customers, c => c.Name);

Поскольку аргументы типа не были указаны явно, используется вывод типов для их определения. Во-первых, аргумент клиентов связан с исходным параметром, выводя, что TSource является Customer. Затем, используя описанный выше процесс вывода анонимного типа функции, c присваивается тип Customer, а выражение c.Name связано с типом возврата параметра селектора, выводя TResult для string. Таким образом, вызов эквивалентен

Sequence.Select<Customer,string>(customers, (Customer c) => c.Name)

и тип результата — IEnumerable<string>.

В следующем примере показано, как вывод типа анонимной функции позволяет информации о типах перетекать между аргументами при вызове универсального метода. Учитывая следующий метод и вызов:

class A
{
    static Z F<X,Y,Z>(X value, Func<X,Y> f1, Func<Y,Z> f2)
    {
        return f2(f1(value));
    }

    static void M()
    {
        double hours = F("1:15:30", s => TimeSpan.Parse(s), t => t.TotalHours);
    }
}

Вывод типа для вызова продолжается следующим образом: во-первых, аргумент "1:15:30" связан с параметром значения, выводя, что X имеет тип строка. Затем параметр первой анонимной функции, s, присваивается выводимому типу string, а выражение TimeSpan.Parse(s) связано с типом возврата f1, выводя YSystem.TimeSpan. Наконец, параметр второй анонимной функции, t, присваивается выводимому типу System.TimeSpan, а выражение t.TotalHours связано с возвращаемым типом f2, выводя Z для double. Таким образом, результат вызова имеет тип double.

конечный пример

Вывод типа для конверсии групп методов 12.6.3.14

Как и вызовы универсальных методов, вывод типов также применяется, если группа методов M, содержащая универсальный метод, преобразуется в заданный тип делегата D (§10.8). Дан метод

Tₑ M<X₁...Xᵥ>(T₁ x₁ ... Tₑ xₑ)

и группа методов M назначается типу делегата D, задача вывода типов заключается в нахождении аргументов типа S₁...Sᵥ, чтобы выражение:

M<S₁...Sᵥ>

становится совместимым (§20.2) с D.

В отличие от алгоритма вывода типов для вызовов универсальных методов, в данном случае есть только типы аргументов , но отсутствуют выражения аргументов . В частности, нет анонимных функций и поэтому нет необходимости в нескольких этапах вывода.

Вместо этого все Xᵢ считаются нефиксированных, а вывод с нижней границой производится из каждого типа аргумента UₑD, чтобы соответствующий тип параметра TₑM. Если ни для одной из Xᵢ границ не найдены, вывод типов завершается ошибкой. В противном случае все Xᵢзафиксированы к соответствующим Sᵢ, которые являются результатом вывода типа.

12.6.3.15 Поиск наиболее распространенного типа набора выражений

В некоторых случаях для набора выражений необходимо выводить общий тип. В частности, таким образом определяются типы элементов неявно типизированных массивов и возвращаемые типы анонимных функций с блоками в телах.

Лучший распространенный тип для набора выражений E₁...Eᵥ определяется следующим образом:

  • Введена новая переменная типа без фиксации на определённый тип, переменная X.
  • Для каждого выражения Ei выполняется вывод типа (§12.6.3.7) из него в X.
  • X фиксированной (§12.6.3.12), если это возможно, и результирующий тип является лучшим распространенным типом.
  • В противном случае логический вывод завершается ошибкой.

примечание. Этот вывод интуитивно эквивалентен вызову метода void M<X>(X x₁ ... X xᵥ) с Eᵢ в качестве аргументов и выводу X. концевая сноска

Разрешение перегрузки 12.6.4

12.6.4.1 General

Разрешение перегрузки — это механизм времени привязки, используемый для выбора лучшего члена функции для вызова, учитывая список аргументов и набор кандидатов на роль членов функции. Разрешение перегрузки выбирает член функции для вызова в следующих отдельных контекстах в C#:

  • Вызов метода, указанного в invocation_expression (§12.8.10).
  • Вызов конструктора экземпляра, названного в object_creation_expression (§12.8.17.2).
  • Вызов аксессора индексатора с помощью element_access (§12.8.12).
  • Вызов предопределенного или определяемого пользователем оператора в выражении (§12.4.4 и §12.4.5).

Каждый из этих контекстов по-своему определяет набор кандидатов на функцию и список аргументов. Например, набор кандидатов для вызова метода не включает методы, помеченные как переопределение (§12.5), а методы в базовом классе не являются кандидатами, если любой метод в производном классе применим (§12.8.10.2).

После идентификации членов-кандидатов и списка аргументов выбор лучшего элемента функции одинаков во всех случаях:

  • Во-первых, набор членов-кандидатов функции уменьшается до тех элементов функции, которые применимы к указанному списку аргументов (§12.6.4.2). Если этот сокращенный набор пуст, возникает ошибка во время компиляции.
  • Затем находится лучший член функции из набора применимых членов функции-кандидатов. Если набор содержит только один член функции, то этот элемент функции является лучшим элементом функции. В противном случае лучший элемент функции — это один элемент функции, который лучше, чем все остальные члены функции в отношении заданного списка аргументов, если каждый член функции сравнивается со всеми другими элементами функции, использующими правила в §12.6.4.3. Если нет ровно одного члена функции, который лучше всех остальных элементов функции, вызов члена функции является неоднозначным, и возникает ошибка во время привязки.

В следующих подклаузах определяются точное значение терминов применимый член функции и лучший член функции.

12.6.4.2 Применимый функциональный член

Член функции называется применимым членом функции относительно списка аргументов A, если выполнены все следующие условия:

  • Каждый аргумент в A соответствует параметру в объявлении члена функции, как описано в разделе §12.6.2.2, по крайней мере один аргумент соответствует каждому параметру, и любой параметр, к которому аргумент не соответствует, является необязательным параметром.
  • Для каждого аргумента в Aрежим передачи параметров аргумента идентичен режиму передачи параметров соответствующего параметра и
    • для параметра значения или массива параметров неявное преобразование (§10.2) существует из выражения аргумента в тип соответствующего параметра или
    • для ссылочного или выходного параметра существует идентичное преобразование между типом выражения аргумента (если он имеется) и типом совпадающего параметра, или
    • для входного параметра, если соответствующий аргумент имеет модификатор in, имеется тождественное преобразование между типом выражения аргумента (если таковое имеется) и типом соответствующего параметра.
    • для входного параметра, если соответствующий аргумент пропускает модификатор in, неявное преобразование (§10.2) существует из выражения аргумента в тип соответствующего параметра.

Для элемента функции, включающего массив параметров, если член функции применяется в приведенных выше правилах, он, как утверждается, применяется в его обычной форме. Если член функции, содержащий массив параметров, неприменим в обычной форме, член функции может быть применим в своей развернутой форме:

  • Расширенная форма создается путем замены массива параметров в объявлении члена функции нулевыми или более параметрами значения типа элемента массива параметров, таким образом, что число аргументов в списке аргументов A соответствует общему числу параметров. Если A имеет меньше аргументов, чем число фиксированных параметров в объявлении члена функции, расширенная форма члена функции не может быть создана и поэтому неприменимо.
  • В противном случае развернутая форма применима, если для каждого аргумента в Aодно из следующих значений имеет значение true:
    • режим передачи параметров аргумента идентичен режиму передачи параметров соответствующего параметра и
      • для параметра фиксированного значения или параметра значения, созданного расширением, неявное преобразование (§10.2) существует из выражения аргумента в тип соответствующего параметра или
      • для параметра путем ссылки тип выражения аргумента идентичен типу соответствующего параметра.
    • Режим передачи параметров аргумента — значение, а режим передачи параметров соответствующего параметра — входные данные, а неявное преобразование (§10.2) существует из выражения аргумента в тип соответствующего параметра.

Если неявное преобразование типа аргумента в тип входного параметра является динамическим неявным преобразованием (§10.2.10), результаты не определены.

Пример: Приняв во внимание следующие объявления и вызовы методов:

public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }
public static void M2(in int p1) { ... }
public static void Test()
{
    int i = 10; uint ui = 34U;

    M1(in i);   // M1(in int) is applicable
    M1(in ui);  // no exact type match, so M1(in int) is not applicable
    M1(i);      // M1(int) and M1(in int) are applicable
    M1(i + 5);  // M1(int) and M1(in int) are applicable
    M1(100u);   // no implicit conversion exists, so M1(int) is not applicable

    M2(in i);   // M2(in int) is applicable
    M2(i);      // M2(in int) is applicable
    M2(i + 5);  // M2(in int) is applicable
}

конечный пример

  • Статический метод применяется только в том случае, если группа методов образуется из simple_name или member_access через тип.
  • Метод экземпляра применяется только в том случае, если группа методов приводит к simple_name, member_access через переменную или значение или base_access.
    • Если группа методов приводит к simple_name, метод экземпляра применяется только в том случае, если доступ this разрешен §12.8.14.
  • Если группа методов образуется из member_access, который может быть как через экземпляр, так и через тип, как описано в §12.8.7.2, применимы как методы экземпляра, так и статические методы.
  • Универсальный метод, аргументы типа которого (явно указанные или выводимые), не соответствующие их ограничениям, неприменим.
  • В контексте преобразования группы методов должно существовать тождественное преобразование (§10.2.2) или неявное преобразование ссылок (§10.2.8) из типа возврата метода в тип возвращаемого значения делегата. В противном случае кандидатный метод не применим.

12.6.4.3 Улучшенный член функции

В целях определения лучшего члена функции создается упрощенный список аргументов A, содержащий только сами выражения аргументов в том порядке, в котором они появляются в исходном списке аргументов, и исключающий любые аргументы out или ref.

Списки параметров для каждого элемента функции-кандидата создаются следующим образом:

  • Развернутая форма используется, если член функции применим только в развернутой форме.
  • Необязательные параметры без соответствующих аргументов удаляются из списка параметров
  • Ссылочные и выходные параметры удаляются из списка параметров
  • Параметры переупорядочены таким образом, чтобы они происходили в той же позиции, что и соответствующий аргумент в списке аргументов.

Учитывая список аргументов A с набором выражений аргументов {E₁, E₂, ..., Eᵥ} и двумя применимыми элементами функции Mᵥ и Mₓ с типами параметров {P₁, P₂, ..., Pᵥ} и {Q₁, Q₂, ..., Qᵥ}, Mᵥ определяется как более подходящий элемент функции, чем Mₓ, если

  • для каждого аргумента неявное преобразование из Eᵥ в Qᵥ не лучше неявного преобразования из Eᵥ в Pᵥи
  • для хотя бы одного аргумента преобразование из Eᵥ в Pᵥ лучше, чем преобразование из Eᵥ в Qᵥ.

Если последовательности типов параметров {P₁, P₂, ..., Pᵥ} и {Q₁, Q₂, ..., Qᵥ} эквивалентны (т. е. каждый Pᵢ имеет преобразование идентичности в соответствующую Qᵢ), то для определения лучшего элемента функции применяются следующие правила разрешения конфликтов.

  • Если Mᵢ является не универсальным методом и Mₑ является универсальным методом, то Mᵢ лучше, чем Mₑ.
  • В противном случае, если Mᵢ применимо в обычной форме и Mₑ имеет массив парамс и применим только в развернутой форме, то Mᵢ лучше, чем Mₑ.
  • В противном случае, если оба метода имеют массивы params и применимы только в развернутых формах, а если массив params Mᵢ имеет меньше элементов, чем массив params Mₑ, то Mᵢ лучше, чем Mₑ.
  • В противном случае, если Mᵥ имеет более конкретные типы параметров, чем Mₓ, Mᵥ лучше, чем Mₓ. Позвольте {R1, R2, ..., Rn} и {S1, S2, ..., Sn} представлять неинициализированные и неразвёрнутые типы параметров Mᵥ и Mₓ. Mᵥтипы параметров более конкретны, чем Mₓ, если для каждого параметра Rx не менее конкретен, чем Sx, а для хотя бы одного параметра Rx более конкретен, чем Sx:
    • Параметр типа менее специфичен, чем параметр, не являющийся типом.
    • Рекурсивно созданный тип более конкретный, чем другой созданный тип (с одинаковым числом аргументов типа), если по крайней мере один аргумент типа более конкретный, а аргумент типа не является менее конкретным, чем соответствующий аргумент типа в другом.
    • Тип массива более конкретный, чем другой тип массива (с тем же числом измерений), если тип элемента первого является более конкретным, чем тип элемента второго.
  • В противном случае, если один элемент является неподнятым оператором, а другой — оператором, не поднимаемый, лучше.
  • Если ни один член функции не был найден лучше, и все параметры Mᵥ имеют соответствующий аргумент, в то время как аргументы по умолчанию должны быть заменены по крайней мере одним необязательным параметром в Mₓ, то Mᵥ лучше, чем Mₓ.
  • Если для хотя бы одного параметра Mᵥ используется более подходящий вариант передачи параметров (§12.6.4.4), чем для соответствующего параметра в Mₓ, и ни один из параметров в Mₓ не использует более подходящий вариант передачи параметров, чем Mᵥ, тогда Mᵥ лучше, чем Mₓ.
  • В противном случае нет лучшего члена функции.

12.6.4.4 Лучший режим передачи параметров

Допускается иметь соответствующие параметры в двух перегруженных методах только в режиме передачи параметров, если один из двух параметров имеет режим передачи значений, как показано ниже.

public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }

Учитывая int i = 10;, согласно §12.6.4.2, вызовы M1(i) и M1(i + 5) приводят к применению обоих перегрузок. В таких случаях метод с режимом передачи параметров является более подходящим вариантом режима передачи параметров.

Примечание. Для аргументов входных, выходных или ссылочных режимов передачи такой выбор не требуется, так как эти аргументы соответствуют только тем же режимам передачи параметров. конечная сноска

12.6.4.5 Лучшее преобразование из выражения

Учитывая неявное преобразование C₁, представляющее собой преобразование из выражения E в тип T₁, и неявное преобразование C₂, представляющее собой преобразование из выражения E в тип T₂, C₁ — это более подходящее преобразование, чем C₂, если выполняется одно из следующих условий:

  • E точно соответствует T₁ и E точно не соответствует T₂ (§12.6.4.6)
  • E точно соответствует либо обоим из T₁ и T₂, либо ни одному из них, а T₁ является лучшей целью преобразования, чем T₂ (§12.6.4.7)
  • E — это группа методов (§12.2), T₁ совместима (§20.4) с одним лучшим методом из группы методов для преобразования C₁, а T₂ несовместима с одним лучшим методом из группы методов для преобразования C₂

12.6.4.6 Точное сопоставление выражений

Учитывая выражение E и тип T, Eточно соответствуетT, если выполняется одно из следующих условий:

  • E имеет тип S, и тождественное преобразование существует от S до T
  • E является анонимной функцией, T является либо типом делегата D, либо типом дерева выражений Expression<D>, и справедливо одно из следующих.
    • Тип возвращаемого значения X выводится для E в контексте списка параметров D (§12.6.3.12), и существует тождественное преобразование от X к возвращаемому типу D.
    • E является лямбда-async без возвращаемого значения, и D имеет тип возвращаемого значения, который является не универсальным «TaskType»
    • Либо E не является асинхронным, и D имеет тип возврата Y, либо E асинхронный и D имеет тип возврата «TaskType»<Y>(§15.15.1), и выполняется одно из следующих утверждений:
      • Содержимое E — это выражение, что точно соответствует Y
      • Текст E — это блок, в котором каждый оператор return возвращает выражение, которое точно соответствует Y

12.6.4.7 Лучший целевой объект преобразования

Учитывая два типа T₁ и T₂, T₁ является лучшей целью преобразования, чем T₂, если выполняется одно из следующих условий:

  • Неявное преобразование из T₁ в T₂ существует, а неявное преобразование из T₂ в T₁ не существует.
  • T₁ «TaskType»<S₁>(§15.15.1), T₂«TaskType»<S₂>, а S₁ является лучшей целью преобразования, чем S₂
  • T₁ «TaskType»<S₁>(§15.15.1), T₂«TaskType»<S₂>, а T₁ более специализирован, чем T₂
  • T₁ S₁ или S₁?, где S₁ является подписанным целочисленным типом, T₂S₂ или S₂?, где S₂ является целочисленным типом без знака. Конкретно:
    • S₁ sbyte и S₂byte, ushort, uintили ulong
    • S₁ равно short и S₂ равно ushort, uintили ulong
    • S₁ int и S₂uintили ulong
    • S₁ это long и S₂ это ulong

12.6.4.8 Перегрузка в обобщённых классах

Примечание. Хотя подписи, объявленные, должны быть уникальными (§8.6), возможно, что подстановка аргументов типа приводит к идентичным сигнатурам. В такой ситуации разрешение перегрузки выбирает наиболее конкретные из (§12.6.4.3) исходных подписей (перед подстановкой аргументов типа), если они существуют, и в противном случае сообщает об ошибке. концевая сноска

пример. В следующих примерах показаны допустимые и недопустимые перегрузки в соответствии с этим правилом:

public interface I1<T> { ... }
public interface I2<T> { ... }

public abstract class G1<U>
{
    public abstract int F1(U u);           // Overload resolution for G<int>.F1
    public abstract int F1(int i);         // will pick non-generic

    public abstract void F2(I1<U> a);      // Valid overload
    public abstract void F2(I2<U> a);
}

abstract class G2<U,V>
{
    public abstract void F3(U u, V v);     // Valid, but overload resolution for
    public abstract void F3(V v, U u);     // G2<int,int>.F3 will fail

    public abstract void F4(U u, I1<V> v); // Valid, but overload resolution for
    public abstract void F4(I1<V> v, U u); // G2<I1<int>,int>.F4 will fail

    public abstract void F5(U u1, I1<V> v2);   // Valid overload
    public abstract void F5(V v1, U u2);

    public abstract void F6(ref U u);      // Valid overload
    public abstract void F6(out V v);
}

конечный пример

12.6.5 Проверка во время компиляции вызова динамического члена

Несмотря на то, что разрешение перегрузки динамической связанной операции происходит во время выполнения, иногда можно узнать список членов функции, из которых будет выбрана перегрузка:

  • Для вызова делегата (§12.8.10.4), список состоит из одного члена функции, имеющего тот же список параметров, что и тип делегата в вызове.
  • Для вызова метода (§12.8.10.2) для типа или значения, статический тип которого не является динамическим, набор доступных методов в группе методов известен во время компиляции.
  • Для выражения создания объекта (§12.8.17.2) набор доступных конструкторов в типе известен во время компиляции.
  • Для доступа индексатора (§12.8.12.3) набор доступных индексаторов в приемнике известен во время компиляции.

В таких случаях для каждого члена в известном наборе членов функции выполняется ограниченная проверка на этапе компиляции, чтобы можно было точно установить, что он никогда не будет вызван во время выполнения. Для каждого элемента функции F сформирован измененный параметр и список аргументов:

  • Во-первых, если F является универсальным методом и аргументы типа были предоставлены, то они заменяются параметрами типа в списке параметров. Однако если аргументы типа не заданы, такая подстановка не происходит.
  • Затем любой параметр, тип которого является открытым (т. е. содержит параметр типа; см. раздел §8.4.3), исключается вместе с соответствующими параметрами.

Чтобы F пройти проверку, все перечисленные ниже условия должны выполняться:

  • Измененный список параметров для F применим к измененному списку аргументов в соответствии с §12.6.4.2.
  • Все созданные типы в измененном списке параметров удовлетворяют их ограничениям (§8.4.5).
  • Если параметры типа F были заменены на шаге выше, их ограничения удовлетворяются.
  • Если F является статическим методом, группа методов не должна быть получена из member_access, получатель которой на этапе компиляции известен как переменная или значение.
  • Если F является методом экземпляра, группа методов не должна быть результатом member_access, получатель которого на этапе компиляции известен как тип.

Если кандидат не проходит этот тест, возникает ошибка во время компиляции.

Вызов члена функции 12.6.6

12.6.6.1 General

В этом подклаузе описывается процесс, который выполняется во время выполнения для вызова определенного члена функции. Предполагается, что процесс привязки времени выполнения уже определил конкретный элемент для вызова, возможно, путем применения разрешения перегрузки к набору кандидатных функциональных членов.

Для описания процесса вызова члены функции делятся на две категории:

  • Статические члены функции. Это статические методы, методы доступа к статическим свойствам и определяемые пользователем операторы. Статические члены функции всегда не являются виртуальными.
  • Члены функции экземпляра. Это методы экземпляров, конструкторы экземпляров, методы доступа к свойствам экземпляра и методы доступа индексатора. Члены функций экземпляра бывают виртуальными и не виртуальными и всегда вызываются для конкретного экземпляра. Экземпляр вычисляется выражением экземпляра и становится доступным в члене функции как this (§12.8.14). Для конструктора экземпляра выражение экземпляра принимается как вновь выделенный объект.

Обработка вызова члена функции во время выполнения состоит из следующих шагов, где M является членом функции и, если M является членом экземпляра, E является выражением экземпляра:

  • Если M является статическим членом функции:

    • Список аргументов оценивается, как описано в §12.6.2.
    • вызывается M.
  • В противном случае, если тип E является значимым типом V, а M объявлен или переопределен в V:

    • E вычисляется. Если эта оценка вызывает исключение, дальнейшие действия не выполняются. Для конструктора экземпляра эта оценка состоит из выделения хранилища (обычно из стека выполнения) для нового объекта. В этом случае E классифицируется как переменная.
    • Если E не классифицируется как переменная или V не является типом структуры чтения (§16.2.2), а E является одним из следующих:
      • входной параметр (§15.6.2.3.2) или
      • поле readonly (§15.5.3) или
      • readonly ссылочной переменной или возвращаемой переменной (§9.7),

    Затем создается временная локальная переменная типа E, а значение E назначается этой переменной. E затем переклассифицируется как ссылка на временную локальную переменную. Временная переменная доступна как this в M, но не каким-либо другим способом. Таким образом, только когда E может быть записан, вызывающий может наблюдать за изменениями, которые M вносит в this.

    • Список аргументов оценивается, как описано в §12.6.2.
    • M вызывается. Переменная, на которую ссылается E, становится переменной, на которую ссылается this.
  • Иначе:

    • E вычисляется. Если эта оценка вызывает исключение, дальнейшие действия не выполняются.
    • Список аргументов оценивается, как описано в §12.6.2.
    • Если тип E является value_type, преобразование бокса (§10.2.9) выполняется для преобразования E в class_type, а E считается class_type на последующих этапах. Если значение value_type является enum_type, то class_typeSystem.Enum;, в противном случае — System.ValueType.
    • Проверяется, является ли значение E допустимым. Если значение E равно null, выбрасывается System.NullReferenceException и дальнейшие шаги не выполняются.
    • Для вызова определяется реализация члена функции.
      • Если тип времени привязки E является интерфейсом, то вызывающий элемент функции является реализацией M, предоставляемой типом времени выполнения экземпляра, на который ссылается E. Этот член функции определяется применением правил сопоставления интерфейсов (§18.6.5) для определения реализации M, предоставляемой типом времени выполнения экземпляра, на который ссылается E.
      • В противном случае, если M является членом виртуальной функции, то вызывающий член функции является реализацией M, предоставляемой типом времени выполнения экземпляра, на который ссылается E. Этот член функции определяется путем применения правил определения наиболее производной реализации (§15.6.4) M относительно типа времени выполнения экземпляра, на который ссылается E.
      • В противном случае M является членом не виртуальной функции, и член функции, который нужно вызвать, - это M.
    • Вызывается реализация члена функции, определенная на предыдущем шаге. Объект, на который ссылается E, становится объектом, на который ссылается этот объект.

Результат вызова конструктора экземпляра (§12.8.17.2) — это значение, которое создается. Результатом вызова любого другого члена функции является значение, если оно имеется, возвращенное (§13.10.5) из тела функции.

12.6.6.2 Вызовы для упакованных экземпляров

Член функции, реализованный в value_type, можно вызвать через упакованный экземпляр этого value_type в следующих ситуациях:

  • Если член функции является переопределением метода, унаследованного от типа class_type, и вызывается через выражение экземпляра этого class_type.

    примечание. class_type всегда будет одним из , или концевая сноска

  • Если элемент функции является реализацией элемента функции интерфейса и вызывается посредством выражения экземпляра типа интерфейса.
  • При вызове члена функции через делегат.

В таких ситуациях боксированный экземпляр считается содержащим переменную value_type, и эта переменная становится переменной, на которую ссылаются в данном вызове элемента функции.

Примечание. В частности, это означает, что при вызове члена функции на упакованном экземпляре можно изменить значение, содержащееся в этом экземпляре. концевая сноска

12.7 Деконструкция

Деконструкция — это процесс, в котором выражение превращается в кортеж отдельных выражений. Деконструкция применяется, когда целью простого присваивания является выражение кортежа, чтобы получить значения для присвоения каждому из элементов этого кортежа.

Выражение Eдеконструируется в кортежное выражение с n элементами следующим образом:

  • Если E является выражением кортежа с n элементами, результат деконструкции — это само выражение E.
  • В противном случае, если E имеет тип кортежа (T1, ..., Tn) с элементами n, то E вычисляется во временную переменную __v, а результат деконструкции представляет собой выражение (__v.Item1, ..., __v.Itemn).
  • В противном случае, если выражение E.Deconstruct(out var __v1, ..., out var __vn) определяется на этапе компиляции до уникального экземпляра или метода расширения, это выражение вычисляется, а результат деконструкции — выражение (__v1, ..., __vn). Такой метод называется деконструктор.
  • В противном случае E нельзя деконструировать.

Здесь __v и __v1, ..., __vn ссылаются на иначе невидимые и недоступные временные переменные.

Примечание. Выражение типа dynamic нельзя деконструировать. конечная сноска

12.8 Первичные выражения

12.8.1 Общие

Основные выражения включают простейшие формы выражений.

primary_expression
    : primary_no_array_creation_expression
    | array_creation_expression
    ;

primary_no_array_creation_expression
    : literal
    | interpolated_string_expression
    | simple_name
    | parenthesized_expression
    | tuple_expression
    | member_access
    | null_conditional_member_access
    | invocation_expression
    | element_access
    | null_conditional_element_access
    | this_access
    | base_access
    | post_increment_expression
    | post_decrement_expression
    | null_forgiving_expression
    | object_creation_expression
    | delegate_creation_expression
    | anonymous_object_creation_expression
    | typeof_expression
    | sizeof_expression
    | checked_expression
    | unchecked_expression
    | default_value_expression
    | nameof_expression    
    | anonymous_method_expression
    | pointer_member_access     // unsafe code support
    | pointer_element_access    // unsafe code support
    | stackalloc_expression
    ;

Примечание. Эти правила грамматики не готовы к ANTLR, так как они являются частью набора взаимоисключаемых правил (primary_expression, primary_no_array_creation_expression, member_access, invocation_expression, element_access, post_increment_expression, post_decrement_expression, null_forgiving_expression,, pointer_member_access и pointer_element_access), которые ANTLR не обрабатывает. Стандартные методы можно использовать для преобразования грамматики для удаления взаимной рекурсии слева. Это не было сделано, так как не все стратегии синтаксического анализа требуют этого (например, LALR-анализатор в этом не нуждается), и его выполнение скрывало бы структуру и описание. конец заметки

pointer_member_access (§23.6.3) и pointer_element_access (§23.6.6.4) доступны только в небезопасном коде (§23).

Первичные выражения делятся между array_creation_expressionи primary_no_array_creation_expression. Обработка array_creation_expression таким образом, а не перечисление его вместе с другими формами простых выражений, позволяет грамматике запретить потенциально запутанный код, например

object o = new int[3][1];

что в противном случае будет интерпретировано как

object o = (new int[3])[1];

12.8.2 Литералы

Основное_выражение , состоящее из литерала (§6.4.5), классифицируется как значение.

Интерполированные строковые выражения 12.8.3

interpolated_string_expression состоит из $, $@или @$, сразу за текстом в " символах. В цитируемом тексте может содержаться ноль или более интерполяций, разделенных символами и , каждая из которых вмещает в себя выражение и необязательные спецификации форматирования.

Интерполированные строковые выражения имеют две формы: обычная (interpolated_regular_string_expression) и дословная (interpolated_verbatim_string_expression), которые лексически похожи на две формы строковых литералов, но отличаются от них семантически (§6.4.5.6).

interpolated_string_expression
    : interpolated_regular_string_expression
    | interpolated_verbatim_string_expression
    ;

// interpolated regular string expressions

interpolated_regular_string_expression
    : Interpolated_Regular_String_Start Interpolated_Regular_String_Mid?
      ('{' regular_interpolation '}' Interpolated_Regular_String_Mid?)*
      Interpolated_Regular_String_End
    ;

regular_interpolation
    : expression (',' interpolation_minimum_width)?
      Regular_Interpolation_Format?
    ;

interpolation_minimum_width
    : constant_expression
    ;

Interpolated_Regular_String_Start
    : '$"'
    ;

// the following three lexical rules are context sensitive, see details below

Interpolated_Regular_String_Mid
    : Interpolated_Regular_String_Element+
    ;

Regular_Interpolation_Format
    : ':' Interpolated_Regular_String_Element+
    ;

Interpolated_Regular_String_End
    : '"'
    ;

fragment Interpolated_Regular_String_Element
    : Interpolated_Regular_String_Character
    | Simple_Escape_Sequence
    | Hexadecimal_Escape_Sequence
    | Unicode_Escape_Sequence
    | Open_Brace_Escape_Sequence
    | Close_Brace_Escape_Sequence
    ;

fragment Interpolated_Regular_String_Character
    // Any character except " (U+0022), \\ (U+005C),
    // { (U+007B), } (U+007D), and New_Line_Character.
    : ~["\\{}\u000D\u000A\u0085\u2028\u2029]
    ;

// interpolated verbatim string expressions

interpolated_verbatim_string_expression
    : Interpolated_Verbatim_String_Start Interpolated_Verbatim_String_Mid?
      ('{' verbatim_interpolation '}' Interpolated_Verbatim_String_Mid?)*
      Interpolated_Verbatim_String_End
    ;

verbatim_interpolation
    : expression (',' interpolation_minimum_width)?
      Verbatim_Interpolation_Format?
    ;

Interpolated_Verbatim_String_Start
    : '$@"'
    | '@$"'
    ;

// the following three lexical rules are context sensitive, see details below

Interpolated_Verbatim_String_Mid
    : Interpolated_Verbatim_String_Element+
    ;

Verbatim_Interpolation_Format
    : ':' Interpolated_Verbatim_String_Element+
    ;

Interpolated_Verbatim_String_End
    : '"'
    ;

fragment Interpolated_Verbatim_String_Element
    : Interpolated_Verbatim_String_Character
    | Quote_Escape_Sequence
    | Open_Brace_Escape_Sequence
    | Close_Brace_Escape_Sequence
    ;

fragment Interpolated_Verbatim_String_Character
    : ~["{}]    // Any character except " (U+0022), { (U+007B) and } (U+007D)
    ;

// lexical fragments used by both regular and verbatim interpolated strings

fragment Open_Brace_Escape_Sequence
    : '{{'
    ;

fragment Close_Brace_Escape_Sequence
    : '}}'
    ;

Шесть вышеопределенных лексических правил чувствительны к контексту, как следует:

правило контекстные требования
Interpolated_Regular_String_Mid Распознается только после Interpolated_Regular_String_Start, между любыми приведенными ниже интерполяциями и до соответствующего Interpolated_Regular_String_End.
Регулярное_интерполяционное_форматирование Распознаётся только в пределах regular_interpolation и когда начальное двоеточие (:) не вложено в какую-либо скобку (круглые/фигурные/квадратные).
Interpolated_Regular_String_End Распознается только после Interpolated_Regular_String_Start и только если какие-либо промежуточные маркеры являются Interpolated_Regular_String_Midили маркерами, которые могут быть частью regular_interpolations, включая маркеры для любых interpolated_regular_string_expression, содержащихся в таких интерполяциях.
Interpolated_Verbatim_String_MidVerbatim_Interpolation_FormatInterpolated_Verbatim_String_End Распознавание этих трех правил следует за соответствующими правилами, приведенными выше, с каждым упомянутым регулярное правило грамматики заменено соответствующим .

примечание. Приведенные выше правила чувствительны к контексту, так как их определения перекрываются с другими маркерами на языке. концевая сноска

примечание: указанная выше грамматика не готова к ANTLR из-за лексических правил, чувствительных к контексту. Как и в случае с другими генераторами лексеров, ANTLR поддерживает контекстно-зависимые лексические правила, например, используя лексические режимы, но это является сведением о реализации и поэтому не является частью этой спецификации. конечная заметка

interpolated_string_expression классифицируется как значение. Если он немедленно преобразуется в System.IFormattable или System.FormattableString с неявным интерполированным преобразованием строки (§10.2.5), интерполированное строковое выражение имеет этот тип. В противном случае он имеет тип string.

примечание. Различия между возможными типами interpolated_string_expression могут быть определены в документации по System.String (§C.2) и System.FormattableString (§C.3). конечная сноска

Смысл интерполяции, как regular_interpolation, так и verbatim_interpolation, заключается в форматировании значения выражения как string в соответствии с форматом, указанным Regular_Interpolation_Format или Verbatim_Interpolation_Format, или в соответствии с форматом по умолчанию для типа выражения . Затем форматированная строка изменяется через interpolation_minimum_width, если таковой имеется, чтобы создать окончательный string для интерполяции в interpolated_string_expression.

примечание. Определение формата по умолчанию для типа подробно описано в документации по System.String (§C.2) и System.FormattableString (§C.3). Описание стандартных форматов, идентичных Regular_Interpolation_Format и Verbatim_Interpolation_Format, можно найти в документации по System.IFormattable (§C.4) и в других типах стандартной библиотеки (§C). конечная сноска

В interpolation_minimum_width выражение constant_expression должно иметь неявное преобразование в int. Пусть ширина поля будет абсолютным значением этого константного_выражения, а выравнивание должно быть знаком (положительным или отрицательным) значения этого константного_выражения:

  • Если значение ширины поля меньше или равно длине форматированной строки, форматированная строка не изменяется.
  • В противном случае форматированная строка заполняется пробелами, чтобы ее длина была равна ширине поля.
    • Если выравнивание положительное, строка форматируется по правому краю с добавлением заполнения.
    • В противном случае оно выравнивается по левому краю за счет добавления заполнения.

Общее значение interpolated_string_expression, включая приведенное выше форматирование и заполнение интерполяций, определяется преобразованием выражения в вызов метода: если тип выражения System.IFormattable или System.FormattableString, то метод — System.Runtime.CompilerServices.FormattableStringFactory.Create (§C.3), который возвращает значение типа System.FormattableString; в противном случае тип должен быть string, и метод — string.Format (§C.2), который возвращает значение типа string.

В обоих случаях список аргументов вызова состоит из строкового литерала формата с спецификациями формата для каждой интерполяции и аргументом для каждого выражения, соответствующего спецификациям формата.

Строковый литерал формата создается следующим образом: N — это количество интерполяций в выражении интерполированной строки . Строковый литерал формата состоит из следующего порядка:

  • Символы Interpolated_Regular_String_Start или Interpolated_Verbatim_String_Start
  • Символы Interpolated_Regular_String_Mid или Interpolated_Verbatim_String_Mid, если таковые есть
  • Затем, если N ≥ 1 для каждого числа I от 0 до N-1:
    • Спецификация заполнителя:
      • Символ левой скобки ({)
      • Десятичное представление I
      • Затем, если соответствующие regular_interpolation или verbatim_interpolation имеют минимальную ширину интерполяции interpolation_minimum_width, вставляется запятая (,), за которой следует десятичное представление значения constant_expression.
      • Символы Regular_Interpolation_Format или Verbatim_Interpolation_Format, если таковые имеются, соответствующих regular_interpolation или verbatim_interpolation
      • Символ правой фигурной скобки (})
    • Символы Interpolated_Regular_String_Mid или Interpolated_Verbatim_String_Mid, находящиеся прямо после соответствующей интерполяции, если она имеется.
  • Наконец, символы Interpolated_Regular_String_End или Interpolated_Verbatim_String_End.

Последующие аргументы — это выражение из интерполяций, если таковые имеются.

Если interpolated_string_expression содержит несколько интерполяций, выражения в этих интерполяциях оцениваются в текстовом порядке слева направо.

пример:

В этом примере используются следующие функции спецификации формата:

  • спецификация формата X, которая форматирует целые числа в виде шестнадцатеричного верхнего регистра,
  • Формат по умолчанию для значения string — это само значение.
  • положительные значения выравнивания, которые оправдываются в пределах указанной минимальной ширины поля,
  • отрицательные значения выравнивания, которые оправдываются в пределах указанной минимальной ширины поля,
  • определены константы для минимальной ширины интерполяциии
  • {{ и }} форматируются как { и } соответственно.

Данный:

string text = "red";
int number = 14;
const int width = -4;

Тогда:

Интерполированное строковое выражение эквивалент string значение
$"{text}" string.Format("{0}", text) "red"
$"{{text}}" string.Format("{{text}}) "{text}"
$"{ text , 4 }" string.Format("{0,4}", text) " red"
$"{ text , width }" string.Format("{0,-4}", text) "red "
$"{number:X}" string.Format("{0:X}", number) "E"
$"{text + '?'} {number % 3}" string.Format("{0} {1}", text + '?', number % 3) "red? 2"
$"{text + $"[{number}]"}" string.Format("{0}", text + string.Format("[{0}]", number)) "red[14]"
$"{(number==0?"Zero":"Non-zero")}" string.Format("{0}", (number==0?"Zero":"Non-zero")) "Non-zero"

конечный пример

12.8.4 Простые имена

simple_name состоит из идентификатора, после которого при необходимости может следовать список аргументов типа.

simple_name
    : identifier type_argument_list?
    ;

simple_name является I формы или формы I<A₁, ..., Aₑ>, где I является одним идентификатором и I<A₁, ..., Aₑ> является необязательным type_argument_list. Если type_argument_list не указано, считайте e равным нулю. simple_name вычисляется и классифицируется следующим образом:

  • Если e равно нулю, а simple_name отображается в пространстве объявления локальной переменной (§7.3), который непосредственно содержит локальную переменную, параметр или константу с именем I, то simple_name ссылается на ту локальную переменную, параметр или константу и классифицируется как переменная или значение.
  • Если e равно нулю, и simple_name встречается в объявлении универсального метода, но вне атрибутов его method_declaration, и если это объявление включает параметр типа с именем I, то simple_name ссылается на этот параметр типа.
  • В противном случае для каждого экземпляра типа T (§15.3.2), начиная с экземпляра типа непосредственно охватывающей декларации типа и продолжая с экземпляра типа каждого охватывающего класса или декларации структуры (если таковой имеется):
    • Если e равно нулю, а объявление T включает параметр типа с именем I, то simple_name ссылается на этот параметр типа.
    • В противном случае, если запрос элемента (§12.5) I в T с аргументами типа e создает совпадение:
      • Если T является типом экземпляра непосредственно окружающего класса или структуры, и поиск находит один или несколько методов, результатом будет группа методов со связанным выражением экземпляра this. Если указан список аргументов типа, он используется при вызове универсального метода (§12.8.10.2).
      • В противном случае, если T является типом экземпляра непосредственно охватывающего класса или типа структуры, если поиск идентифицирует элемент экземпляра, и если ссылка возникает в блоке конструктора экземпляра, метода экземпляра, или метода доступа экземпляра (§12.2.1), результат такой же, как доступ к члену (§12.8.7) формы this.I. Это может произойти только в том случае, если e равно нулю.
      • В противном случае результат совпадает с доступом к члену (§12.8.7) формы T.I или T.I<A₁, ..., Aₑ>.
  • В противном случае, для каждого пространства имен N, начиная с пространства имен, в котором происходит simple_name, продолжая каждым заключающим пространством имен (если такое имеется) и заканчивая глобальным пространством имен, выполняются следующие действия до тех пор, пока не будет найдена сущность:
    • Если e равно нулю, а I — имя пространства имен в N, то:
      • Если место, в котором встречается simple_name, заключено в объявлении пространства имен для N, и это объявление пространства имен содержит extern_alias_directive или using_alias_directive, связывающее имя I с пространством имен или типом, то simple_name считается неоднозначным и приводит к ошибке на стадии компиляции.
      • В противном случае simple_name ссылается на пространство имен, именуемое I, в N.
    • В противном случае, если N содержит доступный тип с именем I и параметрами типа e, то:
      • Если e имеет значение ноль, а расположение, в котором возникает simple_name, находится в области действия объявления пространства имен для N и это объявление содержит extern_alias_directive или using_alias_directive, которое ассоциирует имя I с пространством имен или типом данных, то simple_name является неоднозначным и возникает ошибка во время компиляции.
      • В противном случае namespace_or_type_name ссылается на тип, созданный с заданными аргументами типа.
    • В противном случае, если место, где находится simple_name, охвачено объявлением пространства имен для N:
      • Если e равно нулю, и объявление пространства имен содержит директиву extern_alias_directive или директиву using_alias_directive, которые связывают имя I с импортированным пространством имен или типом, то простое имя ссылается на это пространство имен или тип.
      • В противном случае, если пространства имен, импортированные using_namespace_directiveобъявления пространства имен, содержат ровно один тип с параметрами имени I и e типа, то simple_name ссылается на этот тип, созданный с заданными аргументами типа.
      • В противном случае, если пространства имен, импортированные using_namespace_directiveобъявления пространства имен, содержат несколько типов с параметрами типа I и e типа, то simple_name неоднозначно и возникает ошибка во время компиляции.

    примечание. Этот шаг полностью параллелен соответствующему шагу обработки namespace_or_type_name (§7.8). конечная сноска

  • В противном случае, если e равно нулю и I является идентификатором _, простое_имя — это простое отбрасывание, которое представляет собой форму выражения декларации (§12.17).
  • В противном случае simple_name не определен и возникает ошибка во время компиляции.

12.8.5 Выражения в круглых скобках

parenthesized_expression состоит из выражения, заключённого в скобки.

parenthesized_expression
    : '(' expression ')'
    ;

parenthesized_expression вычисляется путем вычисления выражения в скобках. Если выражение в скобках обозначает пространство имен или тип, возникает ошибка во время компиляции. В противном случае результатом parenthesized_expression является результат оценки выражения , содержащегося в.

Выражения кортежа 12.8.6

tuple_expression представляет кортеж, который состоит из двух или более разделенных запятыми и, возможно, именованных выраженийв скобках. deconstruction_expression — это сокращенный синтаксис кортежа, содержащего неявно типизированные выражения инициализации.

tuple_expression
    : '(' tuple_element (',' tuple_element)+ ')'
    | deconstruction_expression
    ;
    
tuple_element
    : (identifier ':')? expression
    ;
    
deconstruction_expression
    : 'var' deconstruction_tuple
    ;
    
deconstruction_tuple
    : '(' deconstruction_element (',' deconstruction_element)+ ')'
    ;

deconstruction_element
    : deconstruction_tuple
    | identifier
    ;

tuple_expression классифицируется как кортеж.

deconstruction_expressionvar (e1, ..., en) является сокращенным для tuple_expression(var e1, ..., var en) и следует тому же поведению. Это применяется рекурсивно ко всем вложенным deconstruction_tupleв deconstruction_expression. Таким образом, каждый идентификатор, включенный в deconstruction_expression, вводит выражение объявления (§12.17). В результате deconstruction_expression может происходить только в левой части простого присваивания.

Выражение кортежа имеет тип, если и только если каждое из его выражений элементов Ei имеет тип Ti. Тип должен быть кортежем такой же арности, что и выражение кортежа, где каждый элемент задается следующим образом:

  • Если элемент кортежа в соответствующей позиции имеет имя Ni, элемент типа кортежа должен быть Ti Ni.
  • В противном случае, если Ei имеет форму Ni или E.Ni или E?.Ni, элемент типа кортежа должен быть Ti Ni, , если не выполняется ни одно из следующих условий:
    • Один из элементов выражения кортежа имеет имя Niили
    • Другой элемент кортежа без имени имеет выражение элемента кортежа формы Ni или E.Ni или E?.Niили
    • Ni представляет собой форму ItemX, где X представляет собой последовательность десятичных знаков, инициированных не0, которые могут представлять позицию элемента кортежа, а X не представляет позицию элемента.
  • В противном случае тип элемента кортежа должен быть Ti.

Выражение кортежа вычисляется путем вычисления каждого из его выражений элементов в порядке от левого до правого.

Значение кортежа можно получить из выражения кортежа путем преобразования его в тип кортежа (§10.2.13), путем переклассификации его как значение (§12.2.2) или сделав его целью деконструирующего присваивания (§12.21.2).

пример:

(int i, string) t1 = (i: 1, "One");
(long l, string) t2 = (l: 2, null);
var t3 = (i: 3, "Three");          // (int i, string)
var t4 = (i: 4, null);             // Error: no type

В этом примере все четыре кортежных выражения являются допустимыми. Первые два, t1 и t2, не используют тип выражения кортежа, а вместо этого применяют неявное преобразование кортежей. В случае t2неявное преобразование кортежей зависит от неявных преобразований из 2 в long и из null в string. Третье выражение кортежа имеет тип (int i, string), поэтому его можно переклассифицировать как значение этого типа. Объявление t4, с другой стороны, является ошибкой: выражение кортежа не имеет типа, так как его второй элемент не имеет типа.

if ((x, y).Equals((1, 2))) { ... };

В этом примере показано, что кортежи иногда могут привести к появлению нескольких уровней круглых скобок, особенно если выражение кортежа является единственным аргументом вызова метода.

конечный пример

12.8.7 Доступ к члену

12.8.7.1 Общие

member_access состоит из primary_expression, predefined_typeили qualified_alias_member, за которым следует маркер., за которым следует идентификатор, возможно, с последующим type_argument_list.

member_access
    : primary_expression '.' identifier type_argument_list?
    | predefined_type '.' identifier type_argument_list?
    | qualified_alias_member '.' identifier type_argument_list?
    ;

predefined_type
    : 'bool' | 'byte' | 'char' | 'decimal' | 'double' | 'float' | 'int'
    | 'long' | 'object' | 'sbyte' | 'short' | 'string' | 'uint' | 'ulong'
    | 'ushort'
    ;

Производство qualified_alias_member определяется в §14.8.

member_access это либо E.I, либо E.I<A₁, ..., Aₑ>, где E — это primary_expression, predefined_type или qualified_alias_member,I — это один идентификатор, а <A₁, ..., Aₑ> — необязательный type_argument_list. Если type_argument_list не указана, считайте e ноль.

member_access с primary_expression типа dynamic динамически привязан (§12.3.3). В этом случае компилятор классифицирует доступ к члену как доступ к свойству типа dynamic. Следующие правила для определения значения member_access затем применяются во время выполнения программы, с использованием типа времени выполнения вместо типа времени компиляции primary_expression. Если эта классификация во время выполнения приводит к группе методов, то доступ к члену должен быть primary_expression в invocation_expression.

членский_доступ оценивается и классифицируется следующим образом:

  • Если e равно нулю, и E является пространством имен, и E содержит вложенное пространство имен с именем I, то результатом является это пространство имен.
  • В противном случае, если E является пространством имен и E содержит доступный тип с именем I и K параметрами типа, результатом является этот тип, созданный с заданными аргументами типа.
  • Если E классифицируется как тип, если E не является параметром типа, и если поиск элемента (§12.5) I в E с параметрами типа K дает совпадение, тогда E.I рассматривается и классифицируется следующим образом:

    Примечание. Если результат такого поиска элемента является группой методов и K равно нулю, группа методов может содержать методы с параметрами типа. Это позволяет рассматривать такие методы для вывода аргументов типа. концевая сноска

    • Если I идентифицирует тип, результатом является этот тип, созданный с помощью аргументов любого заданного типа.
    • Если I идентифицирует один или несколько методов, результатом является группа методов без связанного выражения экземпляра.
    • Если I идентифицирует статическое свойство, результатом является доступ к свойствам без связанного выражения экземпляра.
    • Если I идентифицирует статическое поле:
      • Если поле является только для чтения и ссылка возникает за пределами статического конструктора класса или структуры, в которой объявлено поле, то результатом является значение, а именно значение статического поля I в E.
      • В противном случае результатом является переменная, а именно статическое поле I в E.
    • Если I идентифицирует статическое событие:
      • Если ссылка возникает в классе или структуре, в которой объявлено событие, и событие было объявлено без event_accessor_declarations (§15.8.1), то E.I обрабатывается точно так же, как если бы I были статическим полем.
      • В противном случае результатом является доступ к событиям без связанного выражения экземпляра.
    • Если I определяет константу, результатом является значение, а именно значение этой константы.
    • Если I идентифицирует элемент перечисления, результатом является значение, а именно значение этого элемента перечисления.
    • В противном случае E.I является недопустимой ссылкой на элемент, и возникает ошибка во время компиляции.
  • Если E является доступом к свойству, доступом индексатора, переменной или значением, типом которого является T, и поиск члена (§12.5) I в T с аргументами типа K приводит к совпадению, то E.I вычисляется и классифицируется следующим образом:
    • Во-первых, если E является свойством или доступом индексатора, то получается значение свойства или доступа индексатора (§12.2.2) и E будет переклассифицировано как значение.
    • Если I определяет один или несколько методов, то результатом является группа методов, соответствующая выражению экземпляра E.
    • Если I идентифицирует свойство экземпляра, результатом является доступ к свойству с соответствующим выражением экземпляра E и связанным типом свойства. Если T является типом класса, связанный тип выбирается из первого объявления или переопределения свойства, найденного при запуске поиска с Tи в процессе поиска по его базовым классам.
    • Если T является типом класса и I определяет поле экземпляра этого типа класса:
      • Если значение E равно null, то выбрасывается System.NullReferenceException.
      • В противном случае, если поле является только для чтения и ссылочный доступ происходит за пределами конструктора экземпляра класса, в котором объявлено поле, результатом является значение этого поля I в объекте, на который ссылается E.
      • В противном случае результатом является переменная, а именно поле I в объекте, на который ссылается E.
    • Если T является struct_type и I определяет поле экземпляра этого struct_type:
      • Если E является значением, или если поле только для чтения и ссылка возникает вне конструктора экземпляра структуры, в которой объявлено поле, то результатом является значение, а именно значение поля I в экземпляре структуры, заданном E.
      • В противном случае результатом является переменная, а именно поле I в экземпляре структуры, заданном E.
    • Если I идентифицирует событие экземпляра:
      • Если ссылка возникает в классе или структуре, где объявлено событие, и событие объявлено без event_accessor_declarations (§15.8.1), а ссылка не появляется в качестве левой части оператора a += или -=, то E.I обрабатывается точно так же, как если бы I было полем экземпляра.
      • В противном случае результатом является доступ к событиям с соответствующим выражением экземпляра E.
  • В противном случае производится попытка обработать E.I как вызов метода расширения (§12.8.10.3). Если это не удается, E.I является недопустимой ссылкой на член, и возникает ошибка времени привязки.

12.8.7.2 Идентичные простые имена и имена типов

В случае доступа к члену формы E.I, если E является одним идентификатором, и если значение E в качестве простого имени (§12.8.4) является константой, полем, свойством, локальной переменной или параметром с таким же типом, как значение E в качестве имени типа (§7.8.1), разрешены оба возможных значения E. Поиск элементов E.I никогда не является неоднозначным, так как I обязательно должен быть членом типа E в обоих случаях. Другими словами, правило просто разрешает доступ к статическим членам и вложенным типам E, где в противном случае возникла бы ошибка во время компиляции.

пример:

struct Color
{
    public static readonly Color White = new Color(...);
    public static readonly Color Black = new Color(...);
    public Color Complement() => new Color(...);
}

class A
{
    public «Color» Color;              // Field Color of type Color

    void F()
    {
        Color = «Color».Black;         // Refers to Color.Black static member
        Color = Color.Complement();  // Invokes Complement() on Color field
    }

    static void G()
    {
        «Color» c = «Color».White;       // Refers to Color.White static member
    }
}

Только в рамках класса A те вхождения идентификатора Color, которые ссылаются на тип Color, разделяются с помощью «...», а те, которые ссылаются на поле Color, не разделяются.

конечный пример

Доступ к условному члену 12.8.8

null_conditional_member_access — это условная версия member_access (§12.8.7), и это ошибка на этапе привязки, если тип результата void. Для условного выражения null, где тип результата может быть void см. (§12.8.11).

null_conditional_member_access состоит из primary_expression, за которыми следуют два маркера "?" и ".", а затем идентификатор с необязательным type_argument_list, за которым следует ноль или более dependent_access, причем любой из них может быть предварительно отмечен null_forgiving_operator.

null_conditional_member_access
    : primary_expression '?' '.' identifier type_argument_list?
      (null_forgiving_operator? dependent_access)*
    ;
    
dependent_access
    : '.' identifier type_argument_list?    // member access
    | '[' argument_list ']'                 // element access
    | '(' argument_list? ')'                // invocation
    ;

null_conditional_projection_initializer
    : primary_expression '?' '.' identifier type_argument_list?
    ;

Выражение null_conditional_member_accessE является в форме P?.A. Значение E определяется следующим образом:

  • Если тип P является типом значения, допускающего значение NULL:

    Позвольте T быть типом P.Value.A.

    • Если T является параметром типа, который не называется ссылочным типом или типом значения, не допускающим значение NULL, возникает ошибка во время компиляции.

    • Если T является типом ненулевого значения, то тип ET?, а значение E совпадает со значением:

      ((object)P == null) ? (T?)null : P.Value.A
      

      За исключением того, что P оценивается только один раз.

    • В противном случае тип E — это T, а значение E совпадает со значением:

      ((object)P == null) ? (T)null : P.Value.A
      

      За исключением того, что P оценивается только один раз.

  • Иначе:

    Позвольте T быть типом выражения P.A.

    • Если T является параметром типа, который не называется ссылочным типом или типом значения, не допускающим значение NULL, возникает ошибка во время компиляции.

    • Если T является типом ненулевого значения, то тип E является T?, а значение E то же, что и значение:

      ((object)P == null) ? (T?)null : P.A
      

      За исключением того, что P оценивается только один раз.

    • В противном случае тип E - это T, а значение E совпадает со значением:

      ((object)P == null) ? (T)null : P.A
      

      За исключением того, что P оценивается только один раз.

Примечание: В выражении следующего вида:

P?.A₀?.A₁

Затем, если P выражается в null, ни A₀, ни A₁ не будут вычислены. То же самое верно, если выражение представляет собой последовательность операций null_conditional_member_access или null_conditional_element_access§12.8.13.

конечная заметка

null_conditional_projection_initializer представляет собой ограничение для null_conditional_member_access и имеет одинаковую семантику. Это используется только как инициализатор проекции в выражении создания анонимного объекта (§12.8.17.7).

12.8.9 Выражения, игнорирующие нулевые значения

12.8.9.1 Общие

Значение выражения, тип, классификация (§12.2) и безопасное контекст (§16.4.12) — это значение, тип, классификация и безопасный контекст primary_expression.

null_forgiving_expression
    : primary_expression null_forgiving_operator
    ;

null_forgiving_operator
    : '!'
    ;

Примечание: постфиксные null-игнорирующие операторы и префиксные операторы логического отрицания (§12.9.4), хотя и обозначены одним и тем же лексическим токеном (!), являются разными. Только последнее может быть переопределено (§15.10), определение оператора, допускающего значение NULL, исправлено. конечная сноска

Это ошибка времени компиляции применять оператор null-forgiving более одного раза к тому же выражению, вне зависимости от наличия скобок.

Пример: следующее является недопустимым:

var p = q!!;            // error: applying null_forgiving_operator more than once
var s = ( ( m(t) ! ) )! // error: null_forgiving_operator applied twice to m(t)

конечный пример

Оставшаяся часть этого подклауза и сопутствующие подклаузы являются условно нормативными.

Компилятор, который выполняет статический анализ состояния NULL (§8.9.5) должен соответствовать следующей спецификации.

Оператор,допускающий значение NULL, — это псевдо-операция времени компиляции, которая используется для информирования статического анализа состояния null компилятора. Он используется в двух случаях: для переопределения определения компилятора, что выражение может быть null; и для переопределения предупреждений компилятора, связанных с нулевыми значениями.

Применение оператора, допускающего значение NULL, к выражению, для которого статический анализ состояния null компилятора не выдает никаких предупреждений, не является ошибкой.

12.8.9.2 Переопределение определения "может быть null"

В некоторых случаях статический анализ состояния null компилятора может определить, что выражение имеет состояние NULL, может быть null и выдает предупреждение диагностики, когда другие сведения указывают, что выражение не может быть null. Применение оператора null-forgiving к такому выражению сообщает статическому анализу null-состояния компилятора, что состояние null находится в , а не в; что предотвращает выдачу диагностического предупреждения и может повлиять на текущий анализ.

пример: рассмотрим следующее:

#nullable enable
public static void M()
{
    Person? p = Find("John");                  // returns Person?
    if (IsValid(p))
    {
       Console.WriteLine($"Found {p!.Name}");  // p can't be null
    }
}

public static bool IsValid(Person? person) =>
    person != null && person.Name != null;

Если IsValid возвращает true, p можно безопасно разыменовать для доступа к свойству Name, а предупреждение о "разыменовании возможного значения NULL" можно подавить с помощью !.

конечный пример

пример: оператор с опрощающим значением null следует использовать с осторожностью, рассмотрите следующее:

#nullable enable
int B(int? x)
{
    int y = (int)x!; // quash warning, throw at runtime if x is null
    return y;
}

Здесь оператор null-forgiving применяется к типу значения и отменяет любое предупреждение в x. Однако, если x станет null во время выполнения, будет вызвано исключение, так как null нельзя привести к int.

конечный пример

12.8.9.3 Переопределение других предупреждений анализа null

Помимо переопределения , возможно, определения null, как описано выше, могут возникнуть другие обстоятельства, когда требуется переопределить статическое определение состояния null компилятора, что выражение требует одного или нескольких предупреждений. Применение обнуляемого оператора к такому выражению требует от компилятора не выдавать предупреждений для выражения. В ответ компилятор может не выдавать предупреждений, а также изменять дальнейший анализ.

пример: рассмотрим следующее:

#nullable enable
public static void Assign(out string? lv, string? rv) { lv = rv; }

public string M(string? t)
{
    string s;
    Assign(out s!, t ?? "«argument was null»");
    return s;
}

Типы параметров метода Assign, lv & rv, string?, при этом lv является выходным параметром и выполняется простое задание.

Метод M передает переменную sтипа stringв качестве выходного параметра Assign. Компилятор выдает предупреждение, так как s не является переменной, допускающей значение NULL. Учитывая, что второй аргумент Assignне может иметь значение NULL, используется оператор подавления NULL, чтобы убрать предупреждение.

конечный пример

конец условно нормативного текста.

Выражения вызова 12.8.10

12.8.10.1 General

Для вызова метода используется invocation_expression.

invocation_expression
    : primary_expression '(' argument_list? ')'
    ;

primary_expression может быть null_forgiving_expression, если она имеет delegate_type.

выражение_вызова связано динамически (§12.3.3), если выполнено хотя бы одно из следующих условий:

  • primary_expression имеет тип времени компиляции dynamic.
  • По крайней мере один аргумент необязательного argument_list имеет тип времени компиляции dynamic.

В этом случае компилятор классифицирует invocation_expression как значение типа dynamic. Приведенные ниже правила для установления значения invocation_expression затем применяются во время выполнения, используя тип времени выполнения вместо типа времени компиляции первичных выражений и аргументов, имеющих тип времени компиляции dynamic. Если primary_expression не имеет типа времени компиляции dynamic, вызов метода проходит ограниченную проверку во время компиляции, как описано в §12.6.5.

primary_expression в выражении вызова invocation_expression должна быть группой методов или значением типа делегата delegate_type. Если primary_expression является группой методов, invocation_expression является вызовом метода (§12.8.10.2). Если primary_expression является значением delegate_type, то invocation_expression является вызовом делегата (§12.8.10.4). Если primary_expression не является ни группой методов, ни значением delegate_type, возникает ошибка во время привязки.

Необязательный argument_list (§12.6.2) предоставляет значения или ссылки на переменные для параметров метода.

Результат оценки invocation_expression классифицируется следующим образом:

  • Если invocation_expression вызывает метод, который не возвращает значение (§15.6.1), или делегат, не возвращающий значение, результат отсутствует. Выражение, которое классифицируется как ничего, допускается только в контексте statement_expression (§13.7) или как текст lambda_expression (§12.19). В противном случае возникает ошибка при времени связывания.
  • В противном случае, если invocation_expression вызывает метод или делегат с возвращением по ссылке (§15.6.1), результатом является переменная с типом, соответствующим возвращаемому типу метода или делегата. Если вызов является методом экземпляра, а приемник имеет тип класса T, связанный тип выбирается из первого объявления или переопределения метода, найденного при запуске с T и поиске по его базовым классам.
  • В противном случае invocation_expression вызывает метод, возвращающий значение (§15.6.1) или делегат, возвращающий значение, и результатом является значение, тип которого определяется возвращаемым типом метода или делегата. Если вызов является методом экземпляра, а приемник является экземпляром класса T, связанный тип выбирается из первого объявления или переопределения метода, найденного начиная с T и поиска по его базовым классам.

Вызовы методов 12.8.10.2

Для вызова метода primary_expressioninvocation_expression должен быть группой методов. Группа методов определяет один метод для вызова или набора перегруженных методов, из которых следует выбрать конкретный метод для вызова. В последнем случае определение конкретного метода вызова основано на контексте, предоставленном типами аргументов в argument_list.

Обработка вызова метода M(A)на этапе времени привязки, где M является группой методов (возможно, включая список аргументов типа ), и A является необязательным списком аргументов , состоит из следующих шагов:

  • Создается набор методов-кандидатов для вызова метода. Для каждого метода F, связанного с группой методов M:
    • Если F не является универсальным, F является кандидатом, когда:
      • M не имеет списка аргументов типа и
      • F применимо к A (§12.6.4.2).
    • Если F является универсальным и M не имеет списка аргументов типа, F является кандидатом, если:
      • Вывод типа (§12.6.3) успешно выполняется, и при этом определяется список аргументов типа для вызова.
      • После замены аргументов типа на соответствующие параметры типа метода все созданные типы в списке параметров F удовлетворяют их ограничениям (§8.4.5), а список параметров F применим к A (§12.6.4.2)
    • Если F является универсальным и M включает список аргументов типа, F является кандидатом при:
      • F имеет то же количество параметров типа метода, что и в списке аргументов типа, и
      • После замены аргументов типа соответствующими параметрами типа метода все созданные типы в списке параметров F удовлетворяют их ограничениям (§8.4.5), а список параметров F применим к A (§12.6.4.2).
  • Набор методов кандидатов сводится к тому, чтобы содержать только методы из наиболее производных типов: для каждого метода C.F в наборе, где C является типом, в котором объявлен метод F, все методы, объявленные в базовом типе C, удаляются из набора. Кроме того, если C является типом класса, отличным от object, все методы, объявленные в типе интерфейса, удаляются из набора.

    Примечание. Это последнее правило действует только в том случае, если группа методов была результатом поиска элемента в параметре типа с эффективным базовым классом, отличным от object и непустым набором эффективных интерфейсов. конечная сноска

  • Если результирующий набор методов-кандидатов пуст, то дальнейшая обработка по следующим шагам прекращается. Вместо этого предпринимается попытка обработать вызов как вызов метода расширения (§12.8.10.3). Если это не удается, то применимые методы не существуют, и возникает ошибка во время привязки.
  • Лучший метод из набора кандидатов выбирается с помощью правил разрешения перегрузки §12.6.4. Если не удается определить один лучший метод, вызов метода является неоднозначным, и возникает ошибка во время привязки. При разрешении перегрузки параметры универсального метода учитываются после подстановки аргументов типа (предоставленных или выводимых) на место соответствующих параметров типа метода.

После выбора и проверки метода во время привязки на указанных выше шагах фактический вызов во время выполнения обрабатывается в соответствии с правилами вызова члена функции, описанного в §12.6.6.

Примечание. Интуитивно понятный эффект правил разрешения, описанных выше, выглядит следующим образом: чтобы найти конкретный метод, вызываемый вызовом метода, начните с типа, указанного вызовом метода, и продолжайте цепочку наследования до тех пор, пока не будет найдено хотя бы одно применимое, доступное объявление метода без переопределения. Затем выполните вывод типов и разрешите перегрузки для набора применимых, доступных, непереопределённых методов, объявленных в этом типе, и вызовите выбранный таким образом метод. Если метод не найден, попробуйте обработать вызов как вызов метода расширения. конечная заметка

Вызовы метода расширения 12.8.10.3

В вызове метода (§12.6.6.2) одной из форм

«expr» . «identifier» ( )  
«expr» . «identifier» ( «args» )  
«expr» . «identifier» < «typeargs» > ( )  
«expr» . «identifier» < «typeargs» > ( «args» )

Если при обычной обработке вызова не находятся применимые методы, предпринимается попытка обработать конструкцию как вызов метода расширения. Если "expr" или любой из "args" имеет тип времени компиляции dynamic, методы расширения не будут применяться.

Цель состоит в том, чтобы найти лучший type_nameC, чтобы для этого мог быть вызван соответствующий статический метод:

C . «identifier» ( «expr» )  
C . «identifier» ( «expr» , «args» )  
C . «identifier» < «typeargs» > ( «expr» )  
C . «identifier» < «typeargs» > ( «expr» , «args» )

Метод расширения Cᵢ.Mₑявляется допустимым, если:

  • Cᵢ — это необобщенный, невложенный класс
  • Имя Mₑ — это идентификатор
  • Mₑ доступно и применимо при применении к аргументам в качестве статического метода, как показано выше.
  • Неявное приведение идентичности, ссылки или упаковочное преобразование существует из экспр к типу первого параметра в Mₑ.

Поиск C выполняется следующим образом:

  • Начиная с ближайшего объявления пространства имен, переходя к каждому последующему объявлению включающего пространства имен и заканчивая содержащим блоком компиляции, осуществляются последовательные попытки найти набор методов расширения.
    • Если заданное пространство имен или единица компиляции напрямую содержит объявления типов-неуниверсалов Cᵢ с соответствующими методами расширения Mₑ, то набор этих методов расширения является кандидатным набором.
    • Если пространства имен импортируются с помощью директив пространства имен в заданном пространстве имен или блоке компиляции и напрямую содержат объявления не-обобщенных типов Cᵢ с соответствующими методами расширения Mₑ, то набор этих методов расширения является кандидатным набором.
  • Если ни один набор кандидатов не найден в любом объявлении пространства имен или единице компиляции, возникает ошибка во время компиляции.
  • В противном случае разрешение перегрузки применяется к набору кандидатов, как описано в §12.6.4. Если ни один лучший метод не найден, возникает ошибка во время компиляции.
  • C — это тип, в котором лучший метод объявляется как метод расширения.

При использовании C в качестве целевого объекта вызов метода обрабатывается как вызов статического метода (§12.6.6).

Примечание. В отличие от вызова метода экземпляра, исключение не возникает, если expr оценивается как пустая ссылка. Вместо этого это значение null передается методу расширения так же, как это делалось бы при обычном вызове статического метода. От реализации метода расширения зависит, как реагировать на такой вызов. концевая заметка

Приведенные выше правила означают, что методы экземпляра имеют приоритет над методами расширения, которые доступны в объявлениях внутреннего пространства имен, имеют приоритет над методами расширения, доступными в объявлениях внешнего пространства имен, и что методы расширения, объявленные непосредственно в пространстве имен, имеют приоритет над методами расширения, импортированными в то же пространство имен с помощью директивы пространства имен.

пример:

public static class E
{
    public static void F(this object obj, int i) { }
    public static void F(this object obj, string s) { }
}

class A { }

class B
{
    public void F(int i) { }
}

class C
{
    public void F(object obj) { }
}

class X
{
    static void Test(A a, B b, C c)
    {
        a.F(1);            // E.F(object, int)
        a.F("hello");      // E.F(object, string)
        b.F(1);            // B.F(int)
        b.F("hello");      // E.F(object, string)
        c.F(1);            // C.F(object)
        c.F("hello");      // C.F(object)
    }
}

В этом примере метод Bимеет приоритет над первым методом расширения, а метод Cимеет приоритет над обоими методами расширения.

public static class C
{
    public static void F(this int i) => Console.WriteLine($"C.F({i})");
    public static void G(this int i) => Console.WriteLine($"C.G({i})");
    public static void H(this int i) => Console.WriteLine($"C.H({i})");
}

namespace N1
{
    public static class D
    {
        public static void F(this int i) => Console.WriteLine($"D.F({i})");
        public static void G(this int i) => Console.WriteLine($"D.G({i})");
    }
}

namespace N2
{
    using N1;

    public static class E
    {
        public static void F(this int i) => Console.WriteLine($"E.F({i})");
    }

    class Test
    {
        static void Main(string[] args)
        {
            1.F();
            2.G();
            3.H();
        }
    }
}

Выходные данные этого примера:

E.F(1)
D.G(2)
C.H(3)

D.G имеет приоритет над C.G, а E.F имеет приоритет и над D.F, и над C.F.

конечный пример

12.8.10.4 Делегирование вызовов

Для вызова делегата primary_expressioninvocation_expression должно быть значением delegate_type. Кроме того, если рассматривать delegate_type как членов функции с тем же списком параметров, что и delegate_type, delegate_type подлежат применению (§12.6.4.2) относительно argument_listinvocation_expression.

Обработка вызова делегата D(A)формы, где D является primary_expressiondelegate_type и A является необязательным argument_list, состоит из следующих этапов:

  • D вычисляется. Если эта оценка вызывает исключение, дальнейшие действия не выполняются.
  • Список аргументов A вычисляется. Если эта оценка вызывает исключение, дальнейшие действия не выполняются.
  • Проверяется, является ли значение D действительным. Если значение D равно null, генерируется System.NullReferenceException и дальнейшие действия не выполняются.
  • В противном случае D является ссылкой на экземпляр делегата. Вызовы членов функций (§12.6.6) выполняются для каждой из вызываемых сущностей в списке вызовов делегата. Для вызываемых сущностей, состоящих из экземпляра и метода экземпляра, экземпляром для вызова является тот экземпляр, который содержится в вызываемой сущности.

Дополнительные сведения о нескольких списках вызовов без параметров см. в §20.6.

Выражение условного вызова null 12.8.11

null_conditional_invocation_expression является синтаксически либо null_conditional_member_access (§12.8.8), либо null_conditional_element_access (§12.8.13), где окончательным dependent_access является это выражением вызова (§12.8.10).

null_conditional_invocation_expression возникает в контексте statement_expression (§13.7), anonymous_function_body (§12.19.1) или method_body (§15.6.1).

В отличие от синтаксически эквивалентных null_conditional_member_access или null_conditional_element_access, null_conditional_invocation_expression может считаться ничем.

null_conditional_invocation_expression
    : null_conditional_member_access null_forgiving_operator? '(' argument_list? ')'
    | null_conditional_element_access null_forgiving_operator? '(' argument_list? ')'
    ;

Необязательный null_forgiving_operator может быть включён только в том случае, если null_conditional_member_access или null_conditional_element_access есть delegate_type.

Выражение null_conditional_invocation_expressionEP?Aформы ; где A является оставшейся частью синтаксического эквивалента null_conditional_member_access или null_conditional_element_access, A поэтому начинается с . или [. Позвольте PA обозначить объединение P и A.

Если E происходит как statement_expression значение E совпадает со значением инструкции:

if ((object)P != null) PA

за исключением того, что P оценивается только один раз.

Если E возникает в виде anonymous_function_body или method_body, смысл E зависит от его классификации:

  • Если E классифицируется как ничего, то его значение совпадает со значением блока :

    { if ((object)P != null) PA; }
    

    за исключением того, что P оценивается только один раз.

  • В противном случае значение E совпадает со значением блока :

    { return E; }
    

    и, в свою очередь, значение этого блока зависит от того, является ли E синтаксически эквивалентен null_conditional_member_access (§12.8.8) или null_conditional_element_access (§12.8.13).

12.8.12 Доступ к элементу

12.8.12.1 General

element_access состоит из primary_no_array_creation_expression, затем следует маркер «[», затем argument_listи маркер «]». argument_list состоит из одного или нескольких аргументов , разделенных запятыми.

element_access
    : primary_no_array_creation_expression '[' argument_list ']'
    ;

Не допускается, чтобы argument_list в element_access содержал out или ref аргументы.

element_access динамически привязан (§12.3.3), если выполняется хотя бы одно из следующих условий:

  • Тип primary_no_array_creation_expression определяется на этапе компиляции как dynamic.
  • По крайней мере одно выражение argument_list имеет тип времени компиляции dynamic, а primary_no_array_creation_expression не имеет типа массива.

В этом случае компилятор классифицирует element_access как значение типа dynamic. Приведенные ниже правила для определения смысла element_access затем применяются во время выполнения, используя тип времени выполнения вместо типа времени компиляции для выражений primary_no_array_creation_expression и argument_list, которые имеют тип времени компиляции dynamic. Если primary_no_array_creation_expression не имеет типа компиляции dynamic, доступ к элементу проходит ограниченную проверку времени компиляции, как описано в §12.6.5.

Если primary_no_array_creation_expression для доступа к element_access является значением типа array_type, то element_access представляет собой доступ к массиву (§12.8.12.2). В противном случае primary_no_array_creation_expression должен быть либо переменной, либо значением класса, структуры или интерфейсного типа, имеющего один или несколько индексаторов, в таком случае element_access является обращением к индексатору (§12.8.12.3).

12.8.12.2 Доступ к массиву

Для обращения к элементу массива выражение_без_создания_массива в доступе_к_элементу должно быть значением типа массив. Кроме того, argument_list доступа к массиву не допускается содержать именованные аргументы. Число выражений в argument_list должно совпадать с рангом array_type, и каждое выражение должно быть типом int, uint, longили ulong, или неявно преобразовано в один или несколько этих типов.

Результатом оценки доступа к массиву является переменная типа элемента массива, а именно элемент массива, выбранный значениями выражений в argument_list.

Обработка доступа к массиву формы P[A], где P — это primary_no_array_creation_expression типа array_type, а A — это argument_list, включает следующие этапы:

  • P оценивается. Если эта оценка вызывает исключение, дальнейшие действия не выполняются.
  • Выражения индекса argument_list вычисляются по порядку слева направо. После оценки каждого выражения индекса выполняется неявное преобразование (§10.2) в один из следующих типов: int, uint, longulong. Первый тип в этом списке, для которого существует неявное преобразование, выбирается. Например, если выражение индекса имеет тип short, то выполняется неявное преобразование в int, так как неявные преобразования из short в int и из short в long возможны. Если оценка выражения индекса или последующего неявного преобразования вызывает исключение, то дальнейшие выражения индекса не оцениваются и дальнейшие шаги не выполняются.
  • Проверяется корректность значения P. Если значение P равно null, выбрасывается System.NullReferenceException и дальнейшие действия не выполняются.
  • Значение каждого выражения в argument_list проверяется на фактические границы каждого измерения экземпляра массива, на который ссылается P. Если одно или несколько значений не находятся в диапазоне, создается System.IndexOutOfRangeException, и дальнейшие шаги не выполняются.
  • Расположение элемента массива, заданного выражениями индекса, вычисляется, и это расположение становится результатом доступа к массиву.

Доступ индексатора 12.8.12.3

Для доступа индексатора primary_no_array_creation_expressionelement_access должна быть переменной или значением класса, структуры или типа интерфейса, и этот тип должен реализовывать один или несколько индексаторов, применимых к argument_listelement_access.

Обработка доступа индексатора к P[A]формы, где P является primary_no_array_creation_expression класса, структуры или типа интерфейса T, а A — это argument_list, состоит из следующих этапов:

  • Создается набор индексаторов, предоставляемых T. Набор состоит из всех индексаторов, объявленных в T или базовом типе T, которые не являются декларациями переопределения и доступны в текущем контексте (§7.5).
  • Набор уменьшается до тех индексаторов, которые применимы и не скрыты другими индексаторами. Следующие правила применяются к каждому индексатору S.I в наборе, где S является типом, в котором объявлен индексатор I:
    • Если I не применимо к A (§12.6.4.2), I удаляется из набора.
    • Если I применимо к A (§12.6.4.2), все индексаторы, объявленные в базовом типе S, удаляются из набора.
    • Если I применимо к A (§12.6.4.2) и S — это тип класса, отличный от object, все индексаторы, объявленные в интерфейсе, удаляются из набора.
  • Если результирующий набор кандидатов индексаторов пуст, то применимые индексаторы не существуют, и возникает ошибка во время привязки.
  • Лучший индексатор из набора индексаторов-кандидатов определяется с помощью правил разрешения перегрузки §12.6.4. Если не удается определить один лучший индексатор, доступ индексатора является неоднозначным, и возникает ошибка во время привязки.
  • Выражения индекса argument_list вычисляются по порядку слева направо. Результатом обработки доступа индексатора является выражение, классифицируемое как доступ индексатора. Выражение доступа индексатора ссылается на индексатор, определенный на предыдущем шаге, и имеет связанное с ним выражение экземпляра P, список аргументов A, а также тип, который совпадает с типом этого индексатора. Если T является типом класса, связанный тип выбирается из первого объявления или переопределения индексатора, найденного при запуске с T и поиске по базовым классам.

В зависимости от контекста, в котором он используется, доступ к индексатору вызывает либо get аксессор, либо set аксессор индексатора. Если доступ индексатора является целью присвоения, сеттер вызывается для назначения нового значения (§12.21.2). Во всех других случаях метод доступа вызывается для получения текущего значения (§12.2.2).

Доступ к условному элементу NULL 12.8.13

null_conditional_element_access состоит из primary_no_array_creation_expression, за которыми следует два маркера "?" и "[", а затем argument_list, а затем маркер]", а затем ноль или более dependent_accesses любой из которых может предшествовать null_forgiving_operator.

null_conditional_element_access
    : primary_no_array_creation_expression '?' '[' argument_list ']'
      (null_forgiving_operator? dependent_access)*
    ;

null_conditional_element_access — это условная версия element_access (§12.8.12) и это ошибка времени привязки, если тип результата void. Для условного выражения null, где тип результата может быть void см. (§12.8.11).

Выражение null_conditional_element_accessE имеет форму P?[A]B; где B — это dependent_access, если таковые присутствуют. Значение E определяется следующим образом:

  • Если тип P является типом значения, который может принимать значение NULL:

    Позвольте T быть типом выражения P.Value[A]B.

    • Если T является параметром типа, который не называется ссылочным типом или типом значения, не допускающим значение NULL, возникает ошибка во время компиляции.

    • Если T является типом, который не допускает значения null, то тип ET?, а значение E совпадает со значением:

      ((object)P == null) ? (T?)null : P.Value[A]B
      

      За исключением того, что P оценивается только один раз.

    • В противном случае тип E является T, а значение E совпадает со значением:

      ((object)P == null) ? null : P.Value[A]B
      

      За исключением того, что P оценивается только один раз.

  • Иначе:

    Позвольте T быть типом выражения P[A]B.

    • Если T является параметром типа, который не называется ссылочным типом или типом значения, не допускающим значение NULL, возникает ошибка во время компиляции.

    • Если T является типом, не допускающим значение null, то тип ET?, а значение E равно значению:

      ((object)P == null) ? (T?)null : P[A]B
      

      За исключением того, что P оценивается только один раз.

    • В противном случае тип E равен T, а значение E совпадает со значением:

      ((object)P == null) ? null : P[A]B
      

      За исключением того, что P оценивается только один раз.

Примечание: в выражении вида:

P?[A₀]?[A₁]

Если P приводит к null, то ни A₀, ни A₁ не оцениваются. То же самое верно, если выражение представляет собой последовательность операций null_conditional_element_access или null_conditional_member_access§12.8.8.

конечная сноска

12.8.14 Этот доступ

this_access состоит из ключевого слова this.

this_access
    : 'this'
    ;

this_access разрешено только в блоке конструктора экземпляра, метода экземпляра, аксессора экземпляра (§12.2.1) или средства завершения. Он имеет одно из следующих значений:

  • Если this используется в primary_expression в конструкторе экземпляра класса, он классифицируется как значение. Тип значения — это тип экземпляра (§15.3.2) класса, в котором происходит использование, и значение является ссылкой на созданный объект.
  • Если this используется в primary_expression в методе экземпляра или методе доступа к экземпляру класса, он классифицируется как значение. Тип значения — это тип экземпляра (§15.3.2) класса, в котором происходит использование, и значение является ссылкой на объект, для которого был вызван метод или метод доступа.
  • Если this используется в primary_expression в конструкторе экземпляра структуры, он классифицируется как переменная. Тип переменной — это тип экземпляра (§15.3.2) структуры, в которой происходит использование, и переменная представляет структуру, созданную.
    • Если объявление конструктора не имеет инициализатора конструктора, переменная this ведет себя точно так же, как выходной параметр типа структуры. В частности, это означает, что переменная должна быть определенно назначена в каждом пути выполнения конструктора экземпляра.
    • В противном случае переменная this ведет себя точно так же, как и параметр ref типа структуры. В частности, это означает, что переменная считается первоначально назначенной.
  • Если this используется в основной_выражении в методе экземпляра или экземплярном аксессоре структуры, он классифицируется как переменная. Тип переменной — это тип экземпляра (§15.3.2) структуры, в которой происходит использование.
    • Если метод или метод доступа не является итератором (§15.14) или асинхронной функцией (§15.15), переменная this представляет структуру, для которой был вызван метод или метод доступа.
      • Если структурой является readonly struct, переменная this ведет себя точно так же, как входной параметр типа структуры.
      • В противном случае переменная this работает точно так же, как и параметр ref типа структуры.
    • Если метод или метод доступа является итератором или асинхронной функцией, переменная this представляет копию структуры, для которой был вызван метод или метод доступа, и ведет себя точно так же, как и значение параметра типа структуры.

Использование this в primary_expression в контексте, отличном от перечисленных выше, является ошибкой во время компиляции. В частности, нельзя ссылаться на this в статическом методе, аксессоре статического свойства или в инициализации переменной в объявлении поля.

Доступ на базу 12.8.15

base_access состоит из базы ключевых слов, за которой следует маркер.и идентификатор и необязательный type_argument_list или argument_list, заключенные в квадратные скобки:

base_access
    : 'base' '.' identifier type_argument_list?
    | 'base' '[' argument_list ']'
    ;

base_access используется для доступа к элементам базового класса, скрытым элементами с аналогичными именами в текущем классе или структуре. base_access разрешается только в теле конструктора экземпляра, метода экземпляра, метода доступа к экземплярам (§12.2.1) или финализатора. Когда base.I появляется в классе или структуре, это будет обозначать член базового класса или структуры. Аналогичным образом, когда base[E] происходит в классе, применимый индексатор должен существовать в базовом классе.

Во время привязки base_access выражения формы base.I и base[E] вычисляются точно так же, как если бы они были написаны ((B)this).I и ((B)this)[E], где B является базовым классом класса или структурой, в которой происходит конструкция. Таким образом, base.I и base[E] соответствуют this.I и this[E], за исключением this, который рассматривается как экземпляр базового класса.

Когда base_access ссылается на элемент виртуальной функции (метод, свойство или индексатор), определение элемента функции, вызываемого во время выполнения (§12.6.6) изменяется. Вызываемый член функции определяется путем поиска наиболее производной реализации (§15.6.4) члена функции относительно B (вместо относительно типа времени выполнения this, как это обычно бывает в небазовом доступе). Таким образом, в переопределении элемента виртуальной функции можно использовать base_access для вызова унаследованной реализации элемента функции. Если элемент функции, на который ссылается base_access, является абстрактным, возникает ошибка времени привязки.

примечание: В отличие от this, base не является выражением. Это ключевое слово, используемое только в контексте base_access или constructor_initializer (§15.11.2). сноска

12.8.16 Постфиксные операторы инкремента и декремента

post_increment_expression
    : primary_expression '++'
    ;

post_decrement_expression
    : primary_expression '--'
    ;

Операнды операции увеличения или уменьшения постфикса должны быть выражением, классифицируемым как переменная, доступ к свойству или доступ индексатора. Результат операции — это значение того же типа, что и операнды.

Если primary_expression имеет тип времени компиляции dynamic, оператор динамически привязан (§12.3.3), post_increment_expression или post_decrement_expression имеет тип времени компиляции dynamic, а следующие правила применяются во время выполнения с помощью типа времени выполнения primary_expression.

Если операнд постфиксной операции инкремента или декремента является свойством или доступом к индексатору, данное свойство или индексатор должны иметь как метод доступа get, так и метод доступа set. Если это не так, возникает ошибка во время привязки.

Разрешение перегрузки унарного оператора (§12.4.4) применяется для выбора конкретной реализации оператора. Стандартные операторы ++ и -- существуют для следующих типов: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimalи любого типа перечисления. Предопределенные операторы ++ возвращают значение, созданное путем добавления 1 в операнду, а предопределенные операторы -- возвращают значение, созданное путем вычитания 1 из операнда. В проверяемом контексте, если результат этого добавления или вычитания находится за пределами диапазона типа результата, а тип результата является целым типом или типом перечисления, создается System.OverflowException.

Должно происходить неявное преобразование из возвращаемого типа выбранного унарного оператора в тип primary_expression, иначе возникает ошибка во время компиляции.

Обработка постфикса инкремента или декремента для операций типа x++ или x-- состоит из следующих этапов:

  • Если x классифицируется как переменная:
    • x вычисляется для получения значения переменной.
    • Значение x сохраняется.
    • Сохраненное значение x преобразуется в тип операнда выбранного оператора, а оператор вызывается с этим значением в качестве аргумента.
    • Значение, возвращаемое оператором, преобразуется в тип x и хранится в расположении, заданном более ранней оценкой x.
    • Сохраненное значение x становится результатом операции.
  • Если x классифицируется как свойство или доступ индексатора:
    • Выражение экземпляра (если x не static) и список аргументов (если x является доступом индексатора), которые связаны с x, вычисляются, и результаты используются в последующих вызовах аксессоров get и set.
    • Вызывается метод доступа x, а возвращаемое значение сохраняется.
    • Сохраненное значение x преобразуется в тип операнда выбранного оператора, а оператор вызывается с этим значением в качестве аргумента.
    • Значение, возвращаемое оператором, преобразуется в тип x, а set-ассессор x вызывается с этим значением в качестве аргумента.
    • Сохраненное значение x становится результатом операции.

Операторы ++ и -- также поддерживают нотацию префикса (§12.9.6). Результатом x++ или x-- является значение xдо операции, а результатом ++x или --x является значение xпосле операции. В любом случае x имеет то же значение после операции.

Реализацию оператора ++ или -- можно вызвать в постфиксной или префиксной нотации. Невозможно иметь отдельные реализации операторов для двух нотаций.

12.8.17 Новый оператор

12.8.17.1 General

Оператор new используется для создания новых экземпляров типов.

Существует три формы новых выражений:

  • Выражения создания объектов и выражения анонимного создания объектов используются для создания новых экземпляров типов классов и типов значений.
  • Выражения создания массива используются для создания новых экземпляров типов массивов.
  • Выражения создания делегатов используются для получения экземпляров типов делегатов.

Оператор new подразумевает создание экземпляра типа, но не обязательно подразумевает выделение памяти. В частности, экземпляры типов значений не требуют дополнительной памяти за пределами переменных, в которых они находятся, и выделение не происходит, когда new используется для создания экземпляров типов значений.

Примечание: Выражения создания делегатов не всегда создают новые экземпляры. При обработке выражения таким же образом, как преобразование группы методов (§10.8) или преобразование анонимной функции (§10.7), это может привести к повторному использованию существующего экземпляра делегата. конечная сноска

Выражения создания объектов 12.8.17.2

выражение создания объекта используется для создания нового экземпляра типа класса или типа значения.

object_creation_expression
    : 'new' type '(' argument_list? ')' object_or_collection_initializer?
    | 'new' type object_or_collection_initializer
    ;

object_or_collection_initializer
    : object_initializer
    | collection_initializer
    ;

Тип выражение_создания_объекта должен быть класс_типа, значение_типаили тип_параметра. Тип не может быть tuple_type или абстрактным либо статическим class_type.

Необязательный argument_list (§12.6.2) разрешен только в том случае, если тип является class_type или struct_type.

Выражение создания объекта может опустить список аргументов конструктора и круглые скобки, если оно включает инициализатор объекта или инициализатор коллекции. Опущение списка аргументов конструктора и заключения круглых скобок эквивалентно указанию пустого списка аргументов.

Обработка выражения создания объекта, включающего инициализатор объектов или инициализатор коллекции, состоит из первой обработки конструктора экземпляра, а затем обработки инициализаций элементов, указанных инициализатором объектов (§12.8.17.3) или инициализатором коллекции (§12.8.17.4).

Если какой-либо из аргументов в необязательном списке_аргументов имеет компиляционный тип dynamic, то при создании объекта выражение_создания_объекта динамически связывается (§12.3.3), и в процессе выполнения применяются следующие правила, используя тип времени выполнения для тех аргументов из списка_аргументов, которые имеют компиляционный тип dynamic. Однако создание объекта проходит ограниченную проверку во время компиляции, как описано в §12.6.5.

Обработка объектного выражения создания вида new T(A), где T является типом класса или типом значения , а A является необязательным списком аргументов , включает в себя следующие шаги:

  • Если T является типом значения, а A отсутствует:
    • object_creation_expression — это вызов конструктора по умолчанию. Результатом object_creation_expression является значение типа T, а именно значение по умолчанию для T, как определено в §8.3.3.
  • В противном случае если T является типом параметра и A отсутствует:
    • Если для Tне задано ограничение типа значения или ограничение конструктора (§15.2.5), возникает ошибка во время привязки.
    • Результатом object_creation_expression является значение типа времени выполнения, к которому привязан параметр типа, а именно результат вызова конструктора по умолчанию этого типа. Тип времени выполнения может быть ссылочным или типом значения.
  • В противном случае, если T — это class_type или struct_type:
    • Если T является абстрактной или статической class_type, возникает ошибка во время компиляции.
    • Конструктор экземпляра для вызова определяется с помощью правил разрешения перегрузки §12.6.4. Набор конструкторов экземпляров-кандидатов состоит из всех конструкторов доступных экземпляров, объявленных в T, которые применимы к A (§12.6.4.2). Если набор конструкторов экземпляров кандидатов пуст или если не удается определить один лучший конструктор экземпляра, возникает ошибка во время привязки.
    • Результатом object_creation_expression является значение типа T, а именно значение, созданное путем вызова конструктора экземпляра, определенного на шаге выше.
    • В противном случае object_creation_expression недопустим, и возникает ошибка во время привязки.

Даже если object_creation_expression динамически привязан, тип времени компиляции по-прежнему T.

Обработка во время выполнения object_creation_expression в форме new T(A), где Tclass_type или struct_type, и A является необязательным argument_list, состоит из следующих шагов:

  • Если T является типом класса:
    • Выделяется новый экземпляр класса T. Если для выделения нового экземпляра недостаточно памяти, создается System.OutOfMemoryException, и дальнейшие действия не выполняются.
    • Все поля нового экземпляра инициализированы до значений по умолчанию (§9.3).
    • Конструктор экземпляра вызывается в соответствии с правилами вызова элемента функции (§12.6.6). Ссылка на недавно выделенный экземпляр автоматически передается конструктору экземпляра, а экземпляр можно получить из этого конструктора.
  • Если T является struct_type:
    • Экземпляр типа T создается путем выделения временной локальной переменной. Так как конструктор экземпляра struct_type требуется для определенного назначения значения каждому полю создаваемого экземпляра, инициализация временной переменной не требуется.
    • Конструктор экземпляра вызывается в соответствии с правилами вызова элемента функции (§12.6.6). Ссылка на недавно выделенный экземпляр автоматически передается конструктору экземпляра, и этот экземпляр может быть доступен в этом конструкторе с помощью this.

Инициализаторы объектов 12.8.17.3

Инициализатор объектов указывает значения для нуля или нескольких полей, свойств или индексированных элементов объекта.

object_initializer
    : '{' member_initializer_list? '}'
    | '{' member_initializer_list ',' '}'
    ;

member_initializer_list
    : member_initializer (',' member_initializer)*
    ;

member_initializer
    : initializer_target '=' initializer_value
    ;

initializer_target
    : identifier
    | '[' argument_list ']'
    ;

initializer_value
    : expression
    | object_or_collection_initializer
    ;

Инициализатор объектов состоит из последовательности инициализаторов членов, заключённых в маркеры { и } и разделённых запятыми. Каждый member_initializer должен назначать целевой объект для инициализации. Идентификатор должен указывать доступное поле или свойство инициализируемого объекта, в то время как список_аргументов, заключенный в квадратные скобки, должен определять аргументы для доступного индексатора на объекте, который инициализируется. Это ошибка для инициализатора объекта, включающего несколько инициализаторов элементов для одного поля или свойства.

Примечание. В то время как инициализатор объектов не допускается задать одно поле или свойство более одного раза, такие ограничения для индексаторов отсутствуют. Инициализатор объектов может содержать несколько целевых объектов инициализатора, ссылающихся на индексаторы, и даже использовать один и тот же аргумент индексатора несколько раз. конец сноски

За каждым initializer_target следует знак равенства и либо выражение, либо инициализатор объектов, либо инициализатор коллекции. Для выражений в инициализаторе объектов невозможно ссылаться на созданный объект, который он инициализирует.

Инициализатор члена, указывающий выражение после знака равенства, обрабатывается таким же образом, как присваивание (§12.21.2) целевому объекту.

Инициализатор элементов, указывающий инициализатор объектов после знака равенства, является инициализатором вложенных объектов , т. е. инициализацией внедренного объекта. Вместо назначения нового значения полю или свойству назначения в инициализаторе вложенных объектов обрабатываются как назначения членам поля или свойства. Инициализаторы вложенных объектов нельзя применять к свойствам с типом значения или к полям только для чтения с типом значения.

Инициализатор элементов, указывающий инициализатор коллекции после знака равенства, является инициализацией внедренной коллекции. Вместо назначения новой коллекции целевому полю, свойству или индексатору элементы, заданные в инициализаторе, добавляются в коллекцию, на которую ссылается целевой объект. Целевой объект должен иметь тип коллекции, удовлетворяющий требованиям, указанным в §12.8.17.4.

Когда целевой объект инициализатора ссылается на индексатор, аргументы индексатора всегда будут оцениваться ровно один раз. Таким образом, даже если аргументы никогда не используются (например, из-за пустого вложенного инициализатора), они оцениваются из-за их побочных эффектов.

пример: следующий класс представляет точку с двумя координатами:

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

Экземпляр Point можно создать и инициализировать следующим образом:

Point a = new Point { X = 0, Y = 1 };

Это имеет тот же эффект, что и

Point __a = new Point();
__a.X = 0;
__a.Y = 1;
Point a = __a;

где __a является невидимой и недоступной временной переменной.

В следующем классе показан прямоугольник, созданный из двух точек, и создание и инициализация экземпляра Rectangle:

public class Rectangle
{
    public Point P1 { get; set; }
    public Point P2 { get; set; }
}

Экземпляр 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выделяет два внедренных экземпляра Point, их можно использовать для инициализации внедренных Point экземпляров вместо назначения новых экземпляров:

public class Rectangle
{
    public Point P1 { get; } = new Point();
    public Point P2 { get; } = new Point();
}

Следующую конструкцию можно использовать для инициализации встроенных экземпляров 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;

конечный пример

Инициализаторы коллекции 12.8.17.4

Инициализатор коллекции задает элементы коллекции.

collection_initializer
    : '{' element_initializer_list '}'
    | '{' element_initializer_list ',' '}'
    ;

element_initializer_list
    : element_initializer (',' element_initializer)*
    ;

element_initializer
    : non_assignment_expression
    | '{' expression_list '}'
    ;

expression_list
    : expression
    | expression_list ',' expression
    ;

Инициализатор коллекции состоит из последовательности инициализаторов элементов, заключенных в маркеры { и } и разделенных запятыми. Каждый инициализатор элементов определяет элемент, добавляемый в объект коллекции, и состоит из списка выражений, заключенных в токены { и } и разделенных запятыми. Инициализатор одного выражения может быть написан без фигурных скобок, но затем не может быть выражением назначения, чтобы избежать неоднозначности с инициализаторами элементов. Производство non_assignment_expression определяется в §12.22.

пример. Ниже приведен пример выражения создания объекта, включающего инициализатор коллекции:

List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

конечный пример

Объект коллекции, к которому применяется инициализатор коллекции, должен иметь тип, реализующий System.Collections.IEnumerable или возникает ошибка во время компиляции. Для каждого указанного элемента в порядке от левого к правому производится обычный поиск члена для нахождения элемента с именем Add. Если результат поиска элемента не является группой методов, возникает ошибка во время компиляции. В противном случае разрешение перегрузки применяется к списку выражений инициализатора элементов в качестве списка аргументов, а инициализатор коллекции вызывает результирующий метод. Таким образом, объект коллекции должен содержать применимый экземпляр или метод расширения с именем Add для каждого инициализатора элементов.

пример:Ниже показан класс, представляющий контакт с именем и списком номеров телефонов, а также создание и инициализацию List<Contact>:

public class Contact
{
    public string Name { get; set; }
    public List<string> PhoneNumbers { get; } = new List<string>();
}

class A
{
    static void M()
    {
        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 являются временными переменными, которые в противном случае невидимы и недоступны.

конечный пример

Выражения создания массива 12.8.17.5

Для создания нового экземпляра типа массива используется выражение создания массива .

array_creation_expression
    : 'new' non_array_type '[' expression_list ']' rank_specifier*
      array_initializer?
    | 'new' array_type array_initializer
    | 'new' rank_specifier array_initializer
    ;

Выражение создания массива первой формы выделяет экземпляр массива типа, который приводит к удалению каждого из отдельных выражений из списка выражений.

пример: выражение создания массива new int[10,20] создает экземпляр массива типа int[,], а выражение создания массива, новое int[10][,] создает экземпляр массива типа int[][,]. конечный пример

Каждое выражение в списке выражений должно иметь тип int, uint, longили ulongили неявно преобразуемый в один или несколько этих типов. Значение каждого выражения определяет длину соответствующего измерения в недавно выделенном экземпляре массива. Поскольку длина измерения массива должна быть неотрицательной, наличие постоянного выражения с отрицательным значением в списке выражений является ошибкой времени компиляции.

За исключением случаев, когда контекст является небезопасным (§23.2), расположение массивов не определено.

Если выражение создания массива первой формы включает инициализатор массива, каждое выражение в списке выражений должно быть константой, а длина ранга и измерения, указанная списком выражений, должна соответствовать значениям инициализатора массива.

В выражении создания массива второй или третьей формы ранг указанного типа массива или описателя ранга должен соответствовать значению инициализатора массива. Длины каждого измерения определяются по числу элементов на каждом из соответствующих уровней вложенности в инициализаторе массива. Таким образом, выражение инициализатора в следующем объявлении

var a = new int[,] {{0, 1}, {2, 3}, {4, 5}};

точно соответствует

var a = new int[3, 2] {{0, 1}, {2, 3}, {4, 5}};

Выражение создания массива третьей формы называется неявным типизированным выражением создания массива . Он похож на вторую форму, за исключением того, что тип элемента массива не задан явно, но определяется как лучший распространенный тип (§12.6.3.15) набора выражений в инициализаторе массива. Для многомерного массива, т. е., где rank_specifier содержит по крайней мере одну запятую, этот набор содержит все выражения, найденные в вложенных array_initializers.

Инициализаторы массивов описаны далее в §17.7.

Результат оценки выражения создания массива классифицируется как значение, а именно ссылка на недавно выделенный экземпляр массива. Обработка выражения создания массива во время выполнения состоит из следующих шагов:

  • Выражения длины измерения expression_list вычисляются по порядку слева направо. После оценки каждого выражения выполняется неявное преобразование (§10.2) в один из следующих типов: int, uint, long, ulong. Первый тип в этом списке, для которого существует неявное преобразование, выбирается. Если оценка выражения или последующего неявного преобразования вызывает исключение, то дальнейшие выражения не оцениваются и дальнейшие шаги не выполняются.
  • Вычисляемые значения для длины измерения проверяются следующим образом: если одно или несколько значений меньше нуля, создается System.OverflowException, а дальнейшие действия не выполняются.
  • Выделяется экземпляр массива с заданными длинами измерений. Если для выделения нового экземпляра недостаточно памяти, создается System.OutOfMemoryException, и дальнейшие действия не выполняются.
  • Все элементы нового экземпляра массива инициализированы по умолчанию (§9.3).
  • Если выражение создания массива содержит инициализатор массива, то каждое выражение в инициализаторе массива вычисляется и назначается соответствующему элементу массива. Оценки и назначения выполняются в порядке записи выражений в инициализаторе массива. Другими словами, элементы инициализируются в порядке увеличения индексов, при этом сначала увеличивается размерность самого правого индекса. Если оценка заданного выражения или последующего назначения соответствующему элементу массива вызывает исключение, дальнейшие элементы не инициализированы (и остальные элементы таким образом будут иметь значения по умолчанию).

Выражение создания массива позволяет создать экземпляр массива с элементами типа массива, но элементы такого массива должны быть инициализированы вручную.

пример: утверждение

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[,] a = 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[]. Кроме того, один из элементов можно привести к общему базовому типу, который затем станет типом выводимых элементов.

конечный пример

Неявно типизированные выражения создания массива можно объединить с анонимными инициализаторами объектов (§12.8.17.7) для создания анонимных структур данных.

пример:

var contacts = new[]
{
    new
    {
        Name = "Chris Smith",
        PhoneNumbers = new[] { "206-555-0101", "425-882-8080" }
    },
    new 
    {
        Name = "Bob Harris",
       PhoneNumbers = new[] { "650-555-0199" }
    }
};

конечный пример

12.8.17.6 Выражения создания делегатов

Для получения экземпляра delegate_typeиспользуется delegate_creation_expression.

delegate_creation_expression
    : 'new' delegate_type '(' expression ')'
    ;

Аргумент выражения создания делегата должен быть группой методов, анонимной функцией или значением типа времени компиляции dynamic или delegate_type. Если аргумент является группой методов, он определяет метод и, для метода экземпляра, объект, для которого создается делегат. Если аргумент является анонимной функцией, она непосредственно определяет параметры и текст метода целевого объекта делегата. Если аргумент является значением, он определяет экземпляр делегата, из которого необходимо создать копию.

Если выражение имеет тип времени компиляции dynamic, delegate_creation_expression динамически привязан (§12.8.17.6), а приведенные ниже правила применяются во время выполнения с помощью типа времени выполнения выражения . В противном случае правила применяются во время компиляции.

Обработка delegate_creation_expression формы новой D(E)во время привязки, где D является delegate_type и E является выражением , состоит из следующих этапов:

  • Если E является группой методов, операция создания делегата обрабатывается аналогично преобразованию группы методов (§10.8) от E до D.

  • Если E является анонимной функцией, выражение создания делегата обрабатывается так же, как и анонимное преобразование функций (§10.7) от E до D.

  • Если E является значением, E должна быть совместима (§20.2) с D, а результатом является ссылка на только что созданный делегат со списком вызовов с одной записью, который вызывает E.

Время выполнения delegate_creation_expression в форме new D(E), где D является delegate_type и E является выражением , включает следующие этапы:

  • Если E является группой методов, выражение создания делегата рассматривается как преобразование группы методов (§10.8) от E к D.
  • Если E является анонимной функцией, создание делегата оценивается как анонимное преобразование функций из E в D (§10,7).
  • Если E является значением delegate_type:
    • E оценивается. Если эта оценка вызывает исключение, дальнейшие действия не выполняются.
    • Если значение E равно null, выдается System.NullReferenceException, и дальнейшие действия не выполняются.
    • Выделяется новый экземпляр типа делегата D. Если для выделения нового экземпляра недостаточно памяти, создается System.OutOfMemoryException, и дальнейшие действия не выполняются.
    • Новый экземпляр делегата инициализируется с помощью списка вызовов с одной записью, который вызывает E.

Список вызовов делегата определяется при создании экземпляра делегата, а затем остается постоянным в течение всего времени существования делегата. Другими словами, невозможно изменить целевые вызываемые сущности делегата после его создания.

Примечание: Помните, что если два делегата объединяются или один удаляется из другого, получается новый делегат; существующий делегат не изменяет свое содержимое. конечная заметка

Невозможно создать делегат, ссылающийся на свойство, индексатор, оператор, определяемый пользователем, конструктор экземпляра, финализатор или статический конструктор.

пример. Как описано выше, при создании делегата из группы методов, список параметров и тип возврата делегата определяют, какой из перегруженных методов следует выбрать. В примере

delegate double DoubleFunc(double x);

class A
{
    DoubleFunc f = new DoubleFunc(Square);

    static float Square(float x) => x * x;
    static double Square(double x) => x * x;
}

Поле A.f инициализируется делегатом, который ссылается на второй метод Square, так как этот метод точно соответствует списку параметров и типу возвращаемого значения DoubleFunc. Если второй метод Square отсутствует, произошла ошибка во время компиляции.

конечный пример

Выражения создания анонимного объекта 12.8.17.7

Для создания объекта анонимного типа используется anonymous_object_creation_expression.

anonymous_object_creation_expression
    : 'new' anonymous_object_initializer
    ;

anonymous_object_initializer
    : '{' member_declarator_list? '}'
    | '{' member_declarator_list ',' '}'
    ;

member_declarator_list
    : member_declarator (',' member_declarator)*
    ;

member_declarator
    : simple_name
    | member_access
    | null_conditional_projection_initializer
    | base_access
    | identifier '=' expression
    ;

Анонимный инициализатор объектов объявляет анонимный тип и возвращает экземпляр этого типа. Анонимный тип — это класс без имени, который наследуется непосредственно от object. Члены анонимного типа — это последовательность свойств только для чтения, выводимых из инициализатора анонимного объекта, используемого для создания экземпляра типа. В частности, инициализатор анонимного объекта формы

new { p₁=e₁,p₂=e₂, ... pv=ev}

объявляет анонимный тип формы

class __Anonymous1
{
    private readonly «T1» «f1»;
    private readonly «T2» «f2»;
    ...
    private readonly «Tn» «fn»;

    public __Anonymous1(«T1» «a1», «T2» «a2»,..., «Tn» «an»)
    {
        «f1» = «a1»;
        «f2» = «a2»;
        ...
        «fn» = «an»;
    }

    public «T1» «p1» { get { return «f1»; } }
    public «T2» «p2» { get { return «f2»; } }
    ...
    public «Tn» «pn» { get { return «fn»; } }
    public override bool Equals(object __o) { ... }
    public override int GetHashCode() { ... }
}

где каждый "Tx" является типом соответствующего выражения "ex". Выражение, используемое в member_declarator, должно иметь тип. Таким образом, это ошибка во время компиляции выражения в member_declarator для null или анонимной функции.

Имена анонимного типа и параметра для метода Equals автоматически создаются компилятором и не могут ссылаться в тексте программы.

В одной программе два анонимных инициализатора объектов, которые указывают последовательность свойств одинаковых имен и типов времени компиляции в том же порядке, будут создавать экземпляры одного и того же анонимного типа.

Пример: в примере

var p1 = new { Name = "Lawnmower", Price = 495.00 };
var p2 = new { Name = "Shovel", Price = 26.95 };
p1 = p2;

Назначение в последней строке разрешено, так как p1 и p2 имеют одинаковый анонимный тип.

конечный пример

Методы Equals и GetHashcode анонимных типов переопределяют методы, унаследованные от object, и определяются с точки зрения Equals и GetHashcode свойств, чтобы два экземпляра одного и того же анонимного типа были равными, только если все их свойства равны.

Декларация члена может быть сокращена до простого имени (§12.8.4), доступа к члену (§12.8.7), инициализатора null-условной проекции §12.8.8 или базового доступа (§12.8.15). Это называется инициализатором проекции и является сокращенным для объявления и назначения свойству с тем же именем. В частности, декларации членов форм

«identifier», «expr» . «identifier» и «expr» ? . «identifier»

точно эквивалентны следующим, соответственно:

«identifer» = «identifier», «identifier» = «expr» . «identifier» и «identifier» = «expr» ? . «identifier»

Таким образом, в инициализаторе проекции идентификатор выбирает как значение, так и поле или свойство, которому присваивается значение. Интуитивно понятно, что инициализатор проекции проецирует не только значение, но и его имя.

12.8.18 Оператор typeof

Оператор typeof используется для получения объекта System.Type для типа.

typeof_expression
    : 'typeof' '(' type ')'
    | 'typeof' '(' unbound_type_name ')'
    | 'typeof' '(' 'void' ')'
    ;

unbound_type_name
    : identifier generic_dimension_specifier?
    | identifier '::' identifier generic_dimension_specifier?
    | unbound_type_name '.' identifier generic_dimension_specifier?
    ;

generic_dimension_specifier
    : '<' comma* '>'
    ;

comma
    : ','
    ;

Первая форма typeof_expression состоит из ключевого слова typeof, после которого указывается тип в скобках. Результатом выражения этой формы является объект System.Type для указанного типа. Существует только один объект System.Type для любого заданного типа. Это означает, что для типа Ttypeof(T) == typeof(T) всегда имеет значение true. Тип не может быть dynamic.

Вторая форма typeof_expression состоит из ключевого слова typeof, за которым следует круглые скобки unbound_type_name.

Примечание: unbound_type_name очень похож на type_name (§7.8), за исключением того, что unbound_type_name содержит generic_dimension_specifier, где type_name содержит type_argument_lists. конечная заметка

Если операнд typeof_expression представляет собой последовательность токенов, удовлетворяющих грамматикам как unbound_type_name, так и type_name, а именно, если она не содержит ни generic_dimension_specifier, ни type_argument_list, то последовательность токенов считается type_name. Значение unbound_type_name определяется следующим образом:

  • Преобразуйте последовательность маркеров в type_name, заменив каждую generic_dimension_specifier на type_argument_list с одинаковым числом запятых и ключевым словом object, как и каждый type_argument.
  • Оцените результирующий type_name, игнорируя все ограничения параметров типа.
  • unbound_type_name сопоставляется с несвязанным обобщённым типом, который ассоциирован с результирующим сконструированным типом (§8.4).

Недопустимо, чтобы имя типа было ссылочным типом, допускающим значение NULL.

Результатом выражения typeof_expression является объект System.Type для итогового независимого общего типа.

Третья форма typeof_expression состоит из ключевого слова typeof, за которым следует ключевое слово void в скобках. Результатом выражения этой формы является объект System.Type, представляющий отсутствие типа. Объект типа, возвращаемый typeof(void), отличается от объекта типа, возвращаемого для любого типа.

Примечание. Этот специальный объект System.Type полезен в библиотеках классов, которые позволяют отражать методы на языке, где эти методы хотят представить возвращаемый тип любого метода, включая методы void, с экземпляром System.Type. конец примечания

Оператор typeof можно использовать в параметре типа. Это ошибка времени компиляции, если имя типа, как известно, является ссылочным типом, допускаемым значением NULL. Результатом является объект System.Type для типа времени выполнения, привязанного к параметру типа. Если тип времени выполнения является ссылочным типом, допускаемым значением NULL, результатом является соответствующий ненулевой ссылочный тип. Оператор typeof также можно использовать для созданного типа или несвязанного универсального типа (§8.4.4). Объект System.Type для несвязанного универсального типа не совпадает с объектом System.Type типа экземпляра (§15.3.2). Тип экземпляра всегда является закрытым созданным типом во время выполнения, поэтому его System.Type объект зависит от используемых аргументов типа выполнения. Несвязанный универсальный тип, с другой стороны, не имеет аргументов типа и дает один и тот же объект System.Type независимо от аргументов типа среды выполнения.

пример: пример

class X<T>
{
    public static void PrintTypes()
    {
        Type[] t =
        {
            typeof(int),
            typeof(System.Int32),
            typeof(string),
            typeof(double[]),
            typeof(void),
            typeof(T),
            typeof(X<T>),
            typeof(X<X<T>>),
            typeof(X<>)
        };
        for (int i = 0; i < t.Length; i++)
        {
            Console.WriteLine(t[i]);
        }
    }
}

class Test
{
    static void Main()
    {
        X<int>.PrintTypes();
    }
}

создает следующие выходные данные:

System.Int32
System.Int32
System.String
System.Double[]
System.Void
System.Int32
X`1[System.Int32]
X`1[X`1[System.Int32]]
X`1[T]

Обратите внимание, что int и System.Int32 одинаковы. Результат typeof(X<>) не зависит от аргумента типа, но результат typeof(X<T>) зависит.

конечный пример

12.8.19 Оператор sizeof

Оператор sizeof возвращает количество 8-разрядных байтов, занятых переменной заданного типа. Тип, указанный в качестве операнда для sizeof, должен быть unmanaged_type (§8.8).

sizeof_expression
    : 'sizeof' '(' unmanaged_type ')'
    ;

Для определенных стандартных типов оператор sizeof возвращает постоянное int значение, как показано в таблице ниже:

выражение результатов
sizeof(sbyte) 1
sizeof(byte) 1
sizeof(short) 2
sizeof(ushort) 2
sizeof(int) 4
sizeof(uint) 4
sizeof(long) 8
sizeof(ulong) 8
sizeof(char) 2
sizeof(float) 4
sizeof(double) 8
sizeof(bool) 1
sizeof(decimal) 16

Для типа перечисления Tрезультат выражения sizeof(T) является константным значением, равным размеру базового типа, как показано выше. Для всех других типов операндов оператор sizeof указывается в §23.6.9.

12.8.20 Проверенные и непроверенные операторы

Операторы checked и unchecked используются для управления контекстом проверки переполнения для арифметических операций и преобразований целого типа.

checked_expression
    : 'checked' '(' expression ')'
    ;

unchecked_expression
    : 'unchecked' '(' expression ')'
    ;

Оператор checked вычисляет содержащееся выражение в проверяемом контексте, а оператор unchecked оценивает содержащееся выражение в неконтролируемом контексте. checked_expression или unchecked_expression точно соответствует parenthesized_expression (§12.8.5), за исключением того, что содержащееся выражение вычисляется в заданном контексте проверки переполнения.

Контекст проверки переполнения также можно контролировать с помощью инструкций checked и unchecked (§13.12).

Следующие операции затрагиваются контекстом проверки переполнения, установленным операторами и конструкциями checked и unchecked:

  • Предопределенные операторы ++ и -- (§12.8.16 и §12.9.6), когда операнд имеет целочисленный или перечисленный тип.
  • Предопределённый - унарный оператор (§12.9.3), когда операнд является целого типа.
  • Предопределенные +, -, *и / двоичные операторы (§12.10), когда оба операнда являются целочисленными типами или типом перечисления.
  • Явные числовые преобразования (§10.3.2) от одного целочисленного или перечисленного типа к другому целочисленному или перечисленному типу, или из float или double в целочисленный или перечисленный тип.

Если одна из указанных выше операций создает результат, который слишком велик для представления в целевом типе, контекст, в котором выполняется операция, управляет результирующей поведением:

  • В контексте checked, если операция является константным выражением (§12.23), возникает ошибка во время компиляции. В противном случае при выполнении операции создается System.OverflowException.
  • В контексте unchecked результат усечен путем отмены любых битов высокого порядка, которые не соответствуют типу назначения.

Для неконстантных выражений (§12.23), которые оцениваются во время выполнения и не заключены в операторы или инструкции checked или unchecked, контекст проверки переполнения по умолчанию считается непроверенным, если только внешние факторы (например, параметры компилятора и конфигурация среды выполнения) не требуют проверенной оценки.

Для константных выражений (§12.23) (выражения, которые можно полностью оценить во время компиляции), всегда проверяется контекст проверки переполнения по умолчанию. Если константное выражение явно не помещается в контекст unchecked, переполнение, возникающее во время компиляции выражения, всегда приводит к ошибкам во время компиляции.

Тело анонимной функции не зависит от контекстов checked или unchecked, в которых она встречается.

Пример: в следующем коде

class Test
{
    static readonly int x = 1000000;
    static readonly int y = 1000000;

    static int F() => checked(x * y);    // Throws OverflowException
    static int G() => unchecked(x * y);  // Returns -727379968
    static int H() => x * y;             // Depends on default
}

Ошибки во время компиляции не сообщаются, так как ни из выражений не могут оцениваться во время компиляции. Во время выполнения программы метод F вызывает System.OverflowException, а метод G возвращает –727379968 (нижние 32 бита результата вне диапазона). Поведение метода H зависит от контекста проверки переполнения по умолчанию для компиляции, но это либо то же, что F или то же, что и G.

конечный пример

Пример: в следующем коде

class Test
{
    const int x = 1000000;
    const int y = 1000000;

    static int F() => checked(x * y);    // Compile-time error, overflow
    static int G() => unchecked(x * y);  // Returns -727379968
    static int H() => x * y;             // Compile-time error, overflow
}

Переполнение, возникающее при оценке константных выражений в F и H приводит к возникновению ошибок во время компиляции, так как выражения оцениваются в контексте checked. Переполнение также происходит при вычислении константного выражения в G, но, поскольку вычисление выполняется в контексте unchecked, переполнение не сообщается.

конечный пример

Операторы checked и unchecked влияют только на контекст проверки переполнения операций, которые текстуально содержатся в маркерах "(" и ")". Операторы не оказывают влияния на элементы функции, которые вызываются в результате вычисления содержащегося выражения.

Пример: в следующем коде

class Test
{
    static int Multiply(int x, int y) => x * y;

    static int F() => checked(Multiply(1000000, 1000000));
}

Использование checked в F не влияет на оценку x * y в Multiply, поэтому x * y вычисляется в контексте проверки переполнения по умолчанию.

конечный пример

Оператор unchecked удобно при написании констант подписанных целочисленных типов в шестнадцатеричной нотации.

пример:

class Test
{
    public const int AllBits = unchecked((int)0xFFFFFFFF);
    public const int HighBit = unchecked((int)0x80000000);
}

Обе шестнадцатеричные константы выше относятся к типу uint. Поскольку константы находятся за пределами диапазона int, без оператора unchecked приведения к int вызывали бы ошибки при компиляции.

конечный пример

Примечание: операторы checked и инструкции unchecked позволяют программистам контролировать некоторые аспекты числовых вычислений. Однако поведение некоторых числовых операторов зависит от типов данных операндов. Например, умножение двух десятичных чисел всегда вызывает исключение при переполнении даже в явно непроверенной конструкции. Аналогичным образом умножение двух чисел с плавающей запятой никогда не приводит к исключению при переполнении даже в явно проверенном блоке. Кроме того, другие операторы никогда не влияют на режим проверки, будь то по умолчанию или явным. конечная сноска

Выражения значений по умолчанию 12.8.21

Выражение значения по умолчанию используется для получения значения по умолчанию (§9.3) типа.

default_value_expression
    : explictly_typed_default
    | default_literal
    ;

explictly_typed_default
    : 'default' '(' type ')'
    ;

default_literal
    : 'default'
    ;

default_literal представляет значение по умолчанию (§9.3). Он не имеет типа, но его можно преобразовать в любой тип с помощью литерального преобразования по умолчанию (§10.2.16).

Результатом default_value_expression является значение по умолчанию (§9.3) явного типа в explictly_typed_defaultили целевой тип преобразования для default_value_expression.

default_value_expression — это константное выражение (§12.23), если тип является одним из следующих:

  • ссылочный тип
  • параметр типа, который, как известно, является ссылочным типом (§8.2);
  • один из следующих типов значений: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool,; или
  • любой тип перечисления.

12.8.22 Выделение стека

Выражение распределения памяти выделяет блок памяти из стека выполнения. Стек выполнения — это область памяти, в которой хранятся локальные переменные. Стек выполнения не входит в управляемую кучу. Память, используемая для локального хранилища переменных, автоматически восстанавливается при возврате текущей функции.

Правила безопасного контекста для выражения выделения стека описаны в §16.4.12.7.

stackalloc_expression
    : 'stackalloc' unmanaged_type '[' expression ']'
    | 'stackalloc' unmanaged_type? '[' constant_expression? ']' stackalloc_initializer
    ;

stackalloc_initializer
     : '{' stackalloc_initializer_element_list '}'
     ;

stackalloc_initializer_element_list
     : stackalloc_element_initializer (',' stackalloc_element_initializer)* ','?
     ;
    
stackalloc_element_initializer
    : expression
    ;

unmanaged_type (§8.8) указывает тип элементов, которые будут храниться в недавно выделенной области памяти, а выражение указывает количество этих элементов. Вместе они указывают требуемый размер выделения. Тип выражения неявно преобразуется в тип int.

Поскольку размер выделения стека не может быть отрицательным, это ошибка компиляции указать количество элементов в виде constant_expression, которое оценивается как отрицательное значение.

Во время выполнения, если число выделенных элементов является отрицательным значением, поведение не определено. Если оно равно нулю, то выделение не выполняется, а возвращаемое значение определяется реализацией. Если недостаточно памяти для выделения элементов, выбрасывается исключение System.StackOverflowException.

Когда присутствует stackalloc_initializer:

  • Если unmanaged_type опущен, он определяется в соответствии с правилами для наилучшего общего типа (§12.6.3.15) для набора stackalloc_element_initializer.
  • Если constant_expression опущено, его значение определяется как число stackalloc_element_initializer.
  • Если присутствует constant_expression, то оно должно равняться количеству stackalloc_element_initializer.

Каждый stackalloc_element_initializer должен иметь неявное преобразование в unmanaged_type (§10.2). Элементы stackalloc_element_initializerинициализируют в выделенной памяти в порядке возрастания, начиная с элемента нулевого индекса. В отсутствие stackalloc_initializerсодержимое только что выделенной памяти не определено.

Если stackalloc_expression происходит непосредственно как выражение инициализации local_variable_declaration (§13.6.2), где local_variable_type либо является типом указателя (§23.3) или выводится (var), то результатом stackalloc_expression является указатель типа T* (§23.9). В этом случае выражение stackalloc_expression должно появляться в небезопасном коде. В противном случае результатом stackalloc_expression является экземпляр типа Span<T>, где T является неуправляемым_типом.

  • Span<T> (§C.3) — это тип структуры ref (§16.2.3), который представляет блок памяти, в данном случае блок, выделенный с помощью *stackalloc_expression*, как индексируемую коллекцию типизированных (T) элементов.
  • Свойство Length результата возвращает количество выделенных элементов.
  • Индексатор результата (§15.9) возвращает ссылку на переменную (§9.5) к элементу выделенного блока и проходит проверку диапазона.

Инициализаторы выделения стека запрещены в блоках catch или finally (§13.11).

примечание. Нет способа явно освободить память, выделенную с помощью stackalloc. конечная сноска

Все блоки памяти, выделенные стека, созданные во время выполнения элемента функции, автоматически удаляются при возврате этого элемента функции.

За исключением оператора stackalloc, C# не предоставляет предопределенных конструкций для управления памятью, не собираемой сборщиком мусора. Такие службы обычно предоставляются вспомогательными библиотеками классов или импортируются непосредственно из базовой операционной системы.

пример:

// Memory uninitialized
Span<int> span1 = stackalloc int[3];
// Memory initialized
Span<int> span2 = stackalloc int[3] { -10, -15, -30 };
// Type int is inferred
Span<int> span3 = stackalloc[] { 11, 12, 13 };
// Error; result is int*, not allowed in a safe context
var span4 = stackalloc[] { 11, 12, 13 };
// Error; no conversion from Span<int> to Span<long>
Span<long> span5 = stackalloc[] { 11, 12, 13 };
// Converts 11 and 13, and returns Span<long> 
Span<long> span6 = stackalloc[] { 11, 12L, 13 };
// Converts all and returns Span<long>
Span<long> span7 = stackalloc long[] { 11, 12, 13 };
// Implicit conversion of Span<T>
ReadOnlySpan<int> span8 = stackalloc int[] { 10, 22, 30 };
// Implicit conversion of Span<T>
Widget<double> span9 = stackalloc double[] { 1.2, 5.6 };

public class Widget<T>
{
    public static implicit operator Widget<T>(Span<double> sp) { return null; }
}

В случае span8stackalloc приводит к Span<int>, который преобразуется неявным оператором в ReadOnlySpan<int>. Аналогичным образом для span9результирующий Span<double> преобразуется в определяемый пользователем тип Widget<double> с помощью преобразования, как показано ниже. конечный пример

12.8.23 Оператор "nameof"

nameof_expression используется для получения имени сущности программы в виде постоянной строки.

nameof_expression
    : 'nameof' '(' named_entity ')'
    ;
    
named_entity
    : named_entity_target ('.' identifier type_argument_list?)*
    ;
    
named_entity_target
    : simple_name
    | 'this'
    | 'base'
    | predefined_type 
    | qualified_alias_member
    ;

Так как nameof не является ключевым словом, nameof_expression всегда синтаксически неоднозначно с вызовом простого имени nameof. По соображениям совместимости, если поиск имени (§12.8.4) nameof успешно завершён, выражение рассматривается как вызов выражения , независимо от того, действителен ли сам вызов. В противном случае это nameof_expression.

Простые операции доступа к именам и членам производятся для named_entity во время компиляции в соответствии с правилами, описанными в §12.8.4 и §12.8.7. Однако, когда поиск, описанный в §12.8.4, и §12.8.7 приводит к ошибке, так как член экземпляра найден в статическом контексте, nameof_expression не приводит к такой ошибке.

Это ошибка во время компиляции для named_entity назначения группы методов для type_argument_list. Ошибка времени компиляции возникает, если named_entity_target имеет тип dynamic.

nameof_expression — это константное выражение типа stringи не оказывает влияния на время выполнения. В частности, его named_entity не оценивается и игнорируется в рамках анализа определенного присваивания (§9.4.4.22). Его значение является последним идентификатором named_entity перед необязательным окончательным type_argument_list, преобразованным следующим образом:

  • Префикс "@", если используется, удаляется.
  • Каждый unicode_escape_sequence преобразуется в соответствующий символ Юникода.
  • Любые форматирующие символы удаляются.

Эти же преобразования применяются в §6.4.3 при тестировании равенства между идентификаторами.

пример. Ниже показаны результаты различных выражений nameof, предполагая, что универсальный тип List<T> объявлен в пространстве имен System.Collections.Generic:

using TestAlias = System.String;

class Program
{
    static void Main()
    {
        var point = (x: 3, y: 4);

        string n1 = nameof(System);                      // "System"
        string n2 = nameof(System.Collections.Generic);  // "Generic"
        string n3 = nameof(point);                       // "point"
        string n4 = nameof(point.x);                     // "x"
        string n5 = nameof(Program);                     // "Program"
        string n6 = nameof(System.Int32);                // "Int32"
        string n7 = nameof(TestAlias);                   // "TestAlias"
        string n8 = nameof(List<int>);                   // "List"
        string n9 = nameof(Program.InstanceMethod);      // "InstanceMethod"
        string n10 = nameof(Program.GenericMethod);      // "GenericMethod"
        string n11 = nameof(Program.NestedClass);        // "NestedClass"

        // Invalid
        // string x1 = nameof(List<>);            // Empty type argument list
        // string x2 = nameof(List<T>);           // T is not in scope
        // string x3 = nameof(GenericMethod<>);   // Empty type argument list
        // string x4 = nameof(GenericMethod<T>);  // T is not in scope
        // string x5 = nameof(int);               // Keywords not permitted
        // Type arguments not permitted for method group
        // string x6 = nameof(GenericMethod<Program>);
    }

    void InstanceMethod() { }

    void GenericMethod<T>()
    {
        string n1 = nameof(List<T>); // "List"
        string n2 = nameof(T);       // "T"
    }

    class NestedClass { }
}

Потенциально удивительной частью этого примера является разрешение nameof(System.Collections.Generic) только на "Generic", вместо полного пространства имен, и nameof(TestAlias) на "TestAlias", а не "String". конечный пример

Выражения анонимного метода 12.8.24

anonymous_method_expression является одним из двух способов определения анонимной функции. Они описаны далее в §12.19.

12.9 Унарные операторы

12.9.1 Общие положения

+, -, ! (логическое отрицание §12.9.4 только), ~, ++, --, await и приведение типов называются унарными операторами.

Примечание: оператор postfix null-forgiving (§12.8.9), !, из-за своего характера, который проявляется только на этапе компиляции, и невозможность перегрузки, исключается из приведенного выше списка. концевой примечание

unary_expression
    : primary_expression
    | '+' unary_expression
    | '-' unary_expression
    | logical_negation_operator unary_expression
    | '~' unary_expression
    | pre_increment_expression
    | pre_decrement_expression
    | cast_expression
    | await_expression
    | pointer_indirection_expression    // unsafe code support
    | addressof_expression              // unsafe code support
    ;

pointer_indirection_expression (§23.6.2) и addressof_expression (§23.6.5) доступны только в небезопасном коде (§23).

Если операнд unary_expression имеет тип времени компиляции dynamic, он динамически привязан (§12.3.3). В этом случае тип времени компиляции unary_expressiondynamic, а разрешение, описанное ниже, будет выполняться во время выполнения с помощью типа времени выполнения операнда.

Оператор 12.9.2 Unary plus

Для операции формы +xосуществляется разрешение перегрузки унарного оператора (§12.4.4) для выбора определённой реализации оператора. Операнд преобразуется в тип параметра выбранного оператора, а тип результата — возвращаемый тип оператора. Предопределенные унарные операторы сложения:

int operator +(int x);
uint operator +(uint x);
long operator +(long x);
ulong operator +(ulong x);
float operator +(float x);
double operator +(double x);
decimal operator +(decimal x);

Для каждого из этих операторов результат — это просто значение операнда.

Снятые (§12.4.8) формы незавернутых предопределенных унарных операторов плюс, определенных выше, также предопределяются.

12.9.3 Оператор унарного минуса

Для операции в форме –xприменяется разрешение перегрузки унарного оператора (§12.4.4) с целью выбора конкретной реализации оператора. Операнд преобразуется в тип параметра выбранного оператора, а тип результата — возвращаемый тип оператора. Стандартные унарные операторы отрицания:

  • Целочисленное отрицание:

    int operator –(int x);
    long operator –(long x);
    

    Результат вычисляется путем вычитания X от нуля. Если значение X является наименьшим представимым значением типа операнда (-2³¹ для int или –2⁶³ для long), то математическая инверсия X не представима в типе операнда. Если это происходит в контексте checked, создается System.OverflowException; если это происходит в контексте unchecked, результатом является значение операнда, а переполнение не сообщается.

    Если операнд оператора отрицания имеет тип uint, он преобразуется в тип long, а тип результата — long. Исключением является правило, позволяющее −2147483648 значения int (−2) записываться в виде десятичного целочисленного литерала (§6.4.5.3).

    Если операнд оператора отрицания имеет тип ulong, возникает ошибка во время компиляции. Исключением является правило, позволяющее записывать значение long−9223372036854775808 (-2⁶³) в виде десятичного целого литерала (§6.4.5.3)

  • Отрицание с плавающей запятой:

    float operator –(float x);
    double operator –(double x);
    

    Результатом является значение X с измененным знаком. Если x равен NaN, то результат также NaN.

  • Десятичное отрицание:

    decimal operator –(decimal x);
    

    Результат вычисляется путем вычитания X от нуля. Десятичное отрицание эквивалентно использованию унарного оператора минуса типа System.Decimal.

Поднятые (§12.4.8) формы неподнятых предопределенных унарных операторов минуса, определенных выше, также предопределены.

Оператор логического отрицания 12.9.4

Для операции формы !xприменяется разрешение перегрузки оператора для унарных операций (§12.4.4) для выбора конкретной реализации оператора. Операнд преобразуется в тип параметра выбранного оператора, а тип результата — возвращаемый тип оператора. Существует только один предопределенный логический оператор отрицания:

bool operator !(bool x);

Этот оператор вычисляет логическое отрицание операнда: если операнд равен true, результат будет false. Если операнд false, результатом будет true.

Снятые (§12.4.8) формы предопределенного предопределенного оператора логического отрицания, определенного выше, также предопределяются.

Примечание: операторы префиксного логического отрицания и постфиксного null-допуска (§12.8.9), представленные тем же лексическим токеном (!), различаются. конечная сноска

Оператор дополнения 12.9.5 Bitwise

Для операции формы ~xприменяется разрешение перегрузки унарного оператора (§12.4.4), чтобы выбрать конкретную реализацию оператора. Операнд преобразуется в тип параметра выбранного оператора, а тип результата — возвращаемый тип оператора. Стандартные операторы битового дополнения:

int operator ~(int x);
uint operator ~(uint x);
long operator ~(long x);
ulong operator ~(ulong x);

Для каждого из этих операторов результатом операции является побитовое дополнение x.

Каждый тип перечисления E неявно предоставляет следующий побитовый оператор дополнения:

E operator ~(E x);

Результат вычисления ~x, где X является выражением типа перечисления E с базовым типом U, точно такой же, как результат вычисления (E)(~(U)x), за исключением того, что преобразование в E всегда выполняется будто в контексте unchecked (§12.8.20).

Поднятые (§12.4.8) формы неоподнятых предопределенных операторов побитового дополнения, определенных выше, также предопределены.

12.9.6 Префиксные операторы инкремента и декремента

pre_increment_expression
    : '++' unary_expression
    ;

pre_decrement_expression
    : '--' unary_expression
    ;

Операнд операции увеличения или уменьшения префикса должен быть выражением, классифицируемым как переменная, доступ к свойству или доступ индексатора. Результат операции — это значение того же типа, что и операнды.

Если операнд префиксного инкремента или декремента является свойством или доступом индексатора, то свойство или индексатор должны иметь как метод получения, так и метод установки. Если это не так, возникает ошибка во время привязки.

Разрешение перегрузки унарного оператора (§12.4.4) применяется для выбора конкретной реализации оператора. Стандартные операторы ++ и -- существуют для следующих типов: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimalи любого типа перечисления. Предопределенные операторы ++ возвращают значение, созданное путем добавления 1 в операнду, а предопределенные операторы -- возвращают значение, созданное путем вычитания 1 из операнда. В контексте checked, если результат этого добавления или вычитания находится за пределами диапазона типа результата, а тип результата является целочисленным типом или типом перечисления, выбрасывается System.OverflowException.

Требуется неявное преобразование из возвращаемого типа выбранного унарного оператора в тип unary_expression, в противном случае возникает ошибка во время компиляции.

Время выполнения обработки префикса инкремента или декремента формы ++x или --x состоит из следующих этапов:

  • Если x классифицируется как переменная:
    • x вычисляется для получения переменной.
    • Значение x преобразуется в тип операнда выбранного оператора, а оператор вызывается с этим значением в качестве аргумента.
    • Значение, возвращаемое оператором, преобразуется в тип x. Полученное значение хранится в расположении, заданном вычислением x.
    • и становится результатом операции.
  • Если x классифицируется как свойство или доступ индексатора:
    • Выражение экземпляра (если x не static) и список параметров (если x является доступом индексатора), связанные с x, вычисляются, а результаты используются в последующих обращениях к методам получения и установки доступа.
    • Вызывается метод доступа x.
    • Значение, возвращаемое методом доступа get, преобразуется в тип операнда выбранного оператора и оператор вызывается с этим значением в качестве аргумента.
    • Значение, возвращаемое оператором, преобразуется в тип x. Сеттер x вызывается с этим значением в качестве аргумента.
    • Это значение также становится результатом операции.

Операторы ++ и -- также поддерживают нотацию постфикса (§12.8.16). Результатом x++ или x-- является значение x перед операцией, а результатом ++x или --x является значение x после операции. В любом случае x имеет то же значение после операции.

Вызов реализации оператора ++ или оператора -- можно произвести с использованием как постфиксной, так и префиксной нотации. Невозможно реализовать операторы отдельно для двух нотаций.

Поднятые (§12.4.8) формы неподнятых предопределенных префиксных операторов инкремента и декремента, определенные выше, также предопределены.

Выражения приведения 12.9.7

cast_expression используется именно для явного преобразования выражения в заданный тип.

cast_expression
    : '(' type ')' unary_expression
    ;

cast_expression формы (T)E, где T является типом и E является unary_expression, выполняет явное преобразование (§10,3) значения E для типа T. Если явное преобразование не существует из E в T, возникает ошибка во время привязки. В противном случае результатом является значение, созданное явным преобразованием. Результат всегда классифицируется как значение, даже если E обозначает переменную.

Грамматика для cast_expression приводит к определенной синтаксической неоднозначности.

пример. Выражение (x)–y может быть истолковано как cast_expression (приведение –y к типу x) или как additive_expression в комбинации с parenthesized_expression (который вычисляет значение x – y). конечный пример

Чтобы устранить неоднозначность cast_expression, существует следующее правило: последовательность одного или нескольких маркеров (§6.4), заключенная в скобки, считается началом cast_expression только в том случае, если по крайней мере одно из следующих утверждений истинно:

  • Последовательность токенов соответствует грамматике для типа, но не для выражения.
  • Последовательность маркеров является правильной грамматикой для типа, и маркер сразу после закрывающей скобки является маркером "~", токен "!", токен "(", идентификатор (§6.4.3), литерал (§6.4.5) или любое ключевое слово (§6.4.4), кроме as и is.

Термин "правильная грамматика", приведенный выше, означает только то, что последовательность элементов должна соответствовать определенной грамматической конструкции. В частности, он не учитывает фактическое значение каких-либо составляющих идентификаторов.

пример. Если x и y являются идентификаторами, то x.y является правильной грамматикой для типа, даже если x.y фактически не обозначает тип. конечный пример

Примечание. Из правила дизамбигуации следует, что, если x и y являются идентификаторами, (x)y, (x)(y)и (x)(-y) являются cast_expression, но (x)-y нет, даже если x идентифицирует тип. Однако если x является ключевым словом, определяющим предопределенный тип (например, int), все четыре формы cast_expression(так как такое ключевое слово не может быть выражением само по себе). конечная сноска

Выражения 12.9.8 Await

12.9.8.1 Общие

Оператор await используется для приостановки выполнения охватывающей асинхронной функции до завершения асинхронной операции, представленной операндом.

await_expression
    : 'await' unary_expression
    ;

await_expression допускается только в тексте асинхронной функции (§15.15). В ближайшей асинхронной функции await_expression не должно встречаться в следующих местах:

  • Внутри вложенной (не асинхронной) анонимной функции
  • Внутри блока lock_statement
  • При анонимном преобразовании функции в тип дерева выражений (§10.7.3)
  • В небезопасном контексте

примечание: await_expression не может использоваться в большинстве случаев в query_expression, так как они синтаксически преобразуются для использования асинхронных лямбда-выражений. конечная сноска

В асинхронной функции await не следует использовать в качестве available_identifier хотя можно использовать подробный идентификатор @await. Поэтому нет синтактической неоднозначности между await_expressionи различными выражениями, включающими идентификаторы. Вне асинхронных функций await выступает в качестве обычного идентификатора.

Операнд await_expression называется задачей . Это представляет собой асинхронную операцию, которая может быть завершена или не завершена к моменту, когда вычисляется await_expression. Назначение оператора await заключается в приостановке выполнения заключающей асинхронной функции до завершения ожидаемой задачи, а затем получить его результат.

12.9.8.2 Ожидаемые выражения

Задача await_expression должна быть ожидаемой. Выражение t может быть ожидаемым, если выполняется одно из следующих условий:

  • t является типом времени компиляции dynamic
  • t имеет доступный экземпляр или метод расширения, называемый GetAwaiter, без параметров и типов, и возвращаемый тип A, для которого выполняются все следующие условия:
    • A реализует интерфейс System.Runtime.CompilerServices.INotifyCompletion (далее называется INotifyCompletion для краткости)
    • A имеет доступное для чтения свойство экземпляра IsCompleted типа bool
    • A имеет доступный метод экземпляра GetResult без параметров и без параметров типа

Цель метода GetAwaiter — получить ожидатель для задачи. Тип A называется типом awaiter для выражения await.

Цель свойства IsCompleted — определить, завершена ли задача. В этом случае не требуется приостановить оценку.

Цель метода INotifyCompletion.OnCompleted заключается в регистрации «продолжения» задачи, т.е. делегата (типа System.Action), который вызовется, как только задача будет завершена.

Целью метода GetResult является получение результата задачи после его завершения. Этот результат может быть успешным завершением, возможно, со значением результата или может быть исключением, которое создается методом GetResult.

12.9.8.3 Классификация выражений ожидания

Выражение await t классифицируется так же, как и выражение (t).GetAwaiter().GetResult(). Таким образом, если тип возвращаемого значения GetResultvoid, await_expression классифицируется как ничего. Если он имеет тип возврата, отличный отvoid, T, await_expression классифицируется как значение типа T.

12.9.8.4 Оценка выражений await во время выполнения

Во время выполнения выражение await t оценивается следующим образом:

  • Выполняется получение объекта ожидания a путем вычисления выражения (t).GetAwaiter().
  • bool b получается путем оценки выражения (a).IsCompleted.
  • Если bfalse, то оценка зависит от того, реализует ли a интерфейс System.Runtime.CompilerServices.ICriticalNotifyCompletion (далее называется ICriticalNotifyCompletion для краткости). Эта проверка выполняется во время привязки; т. е. во время выполнения, если a имеет тип времени компиляции dynamic, а в противном случае — во время компиляции. Пусть r обозначает делегат возобновления (§15.15):
    • Если a не реализует ICriticalNotifyCompletion, тогда вычисляется выражение ((a) as INotifyCompletion).OnCompleted(r).
    • Если a реализует ICriticalNotifyCompletion, вычисляется выражение ((a) as ICriticalNotifyCompletion).UnsafeOnCompleted(r).
    • Затем оценка приостанавливается, и управление возвращается текущему вызывающему функцию асинхронной функции.
  • Либо сразу после (если b был true), либо во время последующего вызова делегата возобновления (если b был false), выражение (a).GetResult() вычисляется. Если он возвращает значение, это значение является результатом await_expression. В противном случае результат не имеет значения.

Реализация методов интерфейса, выполняемая ожидателем, INotifyCompletion.OnCompleted и ICriticalNotifyCompletion.UnsafeOnCompleted, должна привести к вызову делегата r не более одного раза. В противном случае поведение заключенной асинхронной функции не определено.

12.10 Арифметические операторы

12.10.1 General

Операторы *, /, %, +и - называются арифметическими операторами.

multiplicative_expression
    : unary_expression
    | multiplicative_expression '*' unary_expression
    | multiplicative_expression '/' unary_expression
    | multiplicative_expression '%' unary_expression
    ;

additive_expression
    : multiplicative_expression
    | additive_expression '+' multiplicative_expression
    | additive_expression '-' multiplicative_expression
    ;

Если операнды арифметического оператора имеют тип времени компиляции dynamic, выражение динамически привязано (§12.3.3). В этом случае тип выражения определяется во время компиляции как dynamic, а разрешение на выполнение, описанное ниже, будет происходить во время выполнения с использованием типа этих операндов, определяемого во время компиляции как dynamic.

Оператор умножения 12.10.2

Для операции формы x * yприменяется разрешение перегрузки двоичных операторов (§12.4.5) для выбора конкретной реализации оператора. Операнды преобразуются в типы параметров выбранного оператора, а тип результата — возвращаемый тип оператора.

Ниже перечислены предопределенные операторы умножения. Все операторы вычисляют произведение между x и y.

  • Целочисленное умножение:

    int operator *(int x, int y);
    uint operator *(uint x, uint y);
    long operator *(long x, long y);
    ulong operator *(ulong x, ulong y);
    

    В контексте checked, если результат выходит за пределы диапазона типа результата, генерируется System.OverflowException. В контексте unchecked переполнения не сообщаются, а любые значительные биты высокого порядка вне диапазона результирующего типа отбрасываются.

  • Умножение с плавающей запятой:

    float operator *(float x, float y);
    double operator *(double x, double y);
    

    Продукт вычисляется в соответствии с правилами арифметики IEC 60559. В следующей таблице перечислены результаты всех возможных сочетаний ненулевого конечного значения, нули, бесконечности и naNs. В таблице x и y являются положительными конечными значениями. z является результатом x * y, округлённым до ближайшего представимого значения. Если величина результата слишком велика для целевого типа, z становится бесконечностью. Из-за округления z может быть нулевым, хотя ни x, ни y не равно нулю.

    +y -y +0 -0 +∞ -∞ NaN
    +x +z -z +0 -0 +∞ -∞ NaN
    -x -z +z -0 +0 -∞ +∞ NaN
    +0 +0 -0 +0 -0 NaN NaN NaN
    -0 -0 +0 -0 +0 NaN NaN NaN
    +∞ +∞ -∞ NaN NaN +∞ -∞ NaN
    -∞ -∞ +∞ NaN NaN -∞ +∞ NaN
    NaN NaN NaN NaN NaN NaN NaN NaN

    (Кроме того, в таблицах с плавающей запятой в §12.10.2§12.10.6 использование "+" означает, что значение положительно; использование "-" означает, что значение отрицательное; и отсутствие знака означает, что значение может быть положительным или отрицательным или не имеет знака (NaN).)

  • Десятичное умножение:

    decimal operator *(decimal x, decimal y);
    

    Если величина результирующего значения слишком велика, чтобы представить в десятичном формате, выдается System.OverflowException. Из-за округления результат может быть нулевым, даже если ни один операнд не равен нулю. Масштаб результата перед округлением — это сумма шкал двух операндов. Десятичное умножение эквивалентно использованию оператора умножения типа System.Decimal.

Поднятые (§12.4.8) формы незавершенных предопределенных операторов умножения, определенных выше, также являются предопределенными.

12.10.3 Оператор деления

Для операции формы x / yприменяется разрешение перегрузки двоичных операторов (§12.4.5) с целью выбора конкретной реализации оператора. Операнды преобразуются в типы параметров выбранного оператора, а тип результата — возвращаемый тип оператора.

Ниже перечислены предопределенные операторы деления. Все операторы вычисляют частное x по y.

  • Целочисленное деление:

    int operator /(int x, int y);
    uint operator /(uint x, uint y);
    long operator /(long x, long y);
    ulong operator /(ulong x, ulong y);
    

    Если значение правого операнда равно нулю, выбрасывается System.DivideByZeroException.

    Деление округляет результат к нулю. Таким образом, абсолютное значение результата является наибольшим возможным целым числом, которое меньше или равно абсолютному значению частного от деления двух операндов. Результат равен нулю или положительным, если два операнда имеют одинаковый знак и ноль или отрицательный, когда два операнда имеют противоположные знаки.

    Если левый операнд представляет собой наименьшее допустимое значение int или long, а правый операнд - –1, возникает переполнение. В контексте checked это приводит к выбрасыванию System.ArithmeticException (или одного из его подклассов). В контексте unchecked определяется реализацией, возбуждается ли исключение System.ArithmeticException (или его подкласс), или переполнение остается незамеченным, и результирующее значение является значением левого операнда.

  • Деление с плавающей запятой:

    float operator /(float x, float y);
    double operator /(double x, double y);
    

    Частное вычисляется в соответствии с правилами арифметики по стандарту IEC 60559. В следующей таблице перечислены результаты всех возможных сочетаний ненулевого конечного значения, нули, бесконечности и naNs. В таблице x и y являются положительными конечными значениями. z — это результат округления x / yдо ближайшего представимого значения.

    +y -y +0 -0 +∞ -∞ NaN
    +x +z -z +∞ -∞ +0 -0 NaN
    -x -z +z -∞ +∞ -0 +0 NaN
    +0 +0 -0 NaN NaN +0 -0 NaN
    -0 -0 +0 NaN NaN -0 +0 NaN
    +∞ +∞ -∞ +∞ -∞ NaN NaN NaN
    -∞ -∞ +∞ -∞ +∞ NaN NaN NaN
    NaN NaN NaN NaN NaN NaN NaN NaN
  • Десятичное деление:

    decimal operator /(decimal x, decimal y);
    

    Если значение правого операнда равно нулю, выбрасывается System.DivideByZeroException. Если величина результирующего значения слишком велика, чтобы представить в десятичном формате, выбрасывается System.OverflowException. Из-за округления результат может быть нулевым, хотя первый операнд не равен нулю. Масштаб результата перед округлением является ближайшим к предпочтительному масштабу, который сохранит результат, равный точному результату. Предпочтительный масштаб — это масштаб x меньше масштаба y.

    Десятичное деление эквивалентно использованию оператора деления типа System.Decimal.

Преобразованные (§12.4.8) формы нелокализованных предопределенных операторов деления, определенных выше, также являются предопределёнными.

Оператор остатка 12.10.4

Для операции формы x % yприменяется разрешение перегрузки бинарных операторов (§12.4.5) для выбора конкретной реализации оператора. Операнды преобразуются в типы параметров выбранного оператора, а тип результата — возвращаемый тип оператора.

Ниже перечислены предопределённые операторы остатка. Все операторы вычисляют оставшуюся часть деления между x и y.

  • Остаток целочисленного числа:

    int operator %(int x, int y);
    uint operator %(uint x, uint y);
    long operator %(long x, long y);
    ulong operator %(ulong x, ulong y);
    

    Результатом x % y является значение, созданное x – (x / y) * y. Если y равно нулю, выбрасывается System.DivideByZeroException.

    Если левый операнд является наименьшим из значений int или long, и правый операнд равен –1, то System.OverflowException выбрасывается, если и только если x / y вызывает исключение.

  • Оставшаяся часть с плавающей запятой:

    float operator %(float x, float y);
    double operator %(double x, double y);
    

    В следующей таблице перечислены результаты всех возможных сочетаний ненулевого конечного значения, нули, бесконечности и naNs. В таблице x и y являются положительными конечными значениями. z является результатом x % y и вычисляется как x – n * y, где n является самым большим возможным целым числом, которое меньше или равно x / y. Этот метод вычисления оставшейся части аналогиен тому, что используется для целых операндов, но отличается от определения IEC 60559 (в котором n является целым числом, ближайшим к x / y).

    +y -y +0 -0 +∞ -∞ NaN
    +x +z +z NaN NaN +x +x NaN
    -x -z -z NaN NaN -x -x NaN
    +0 +0 +0 NaN NaN +0 +0 NaN
    -0 -0 -0 NaN NaN -0 -0 NaN
    +∞ NaN NaN NaN NaN NaN NaN NaN
    -∞ NaN NaN NaN NaN NaN NaN NaN
    NaN NaN NaN NaN NaN NaN NaN NaN
  • Десятичный остаток:

    decimal operator %(decimal x, decimal y);
    

    Если значение правого операнда равно нулю, выбрасывается System.DivideByZeroException. Определяется реализацией, когда выбрасывается System.ArithmeticException (или его подкласс). Соответствующая реализация не должна вызывать исключение для x % y в любом случае, если x / y не создает исключение. Масштаб результата, до округления, является наибольшим из масштабов двух операндов, а знак результата, если ненулевой, совпадает со знаком x.

    Десятичный остаток эквивалентен использованию оператора остатка от деления типа System.Decimal.

    Примечание. Эти правила гарантируют, что для всех типов результат никогда не имеет противоположного знака левого операнда. конец примечания

Повышенные (§12.4.8) формы неповышенных предопределенных операторов остаточных, которые определены выше, также предопределены.

Оператор сложения 12.10.5

Для операции формы x + y(разрешение на перегрузку двоичных операторов§12.4.5) применяется для выбора конкретной реализации оператора. Операнды преобразуются в типы параметров выбранного оператора, а тип результата — возвращаемый тип оператора.

Ниже перечислены предопределенные операторы сложения. Для типов числовых и перечислений предопределенные операторы сложения вычисляют сумму двух операндов. Если один или оба операнда имеют тип string, предопределенные операторы сложения объединяют строковое представление операндов.

  • Добавление целых чисел:

    int operator +(int x, int y);
    uint operator +(uint x, uint y);
    long operator +(long x, long y);
    ulong operator +(ulong x, ulong y
    

    В контексте checked, если сумма выходит за пределы диапазона типа результата, выбрасывается System.OverflowException. В контексте unchecked переполнения не фиксируются, и любые значимые биты высокого порядка за пределами диапазона результирующего типа отбрасываются.

  • Добавление с плавающей запятой:

    float operator +(float x, float y);
    double operator +(double x, double y);
    

    Сумма вычисляется в соответствии с правилами арифметики IEC 60559. В следующей таблице перечислены результаты всех возможных сочетаний ненулевого конечного значения, нули, бесконечности и naNs. В таблице x и y являются ненулевыми конечными значениями, а z — результатом x + y. Если x и y имеют одинаковые величины, но противоположные признаки, z положительный ноль. Если x + y слишком велик для представления в целевом типе, то z является бесконечностью с тем же знаком, как и у x + y.

    y +0 -0 +∞ -∞ NaN
    x z x x +∞ -∞ NaN
    +0 y +0 +0 +∞ –∞ NaN
    -0 y +0 -0 +∞ -∞ NaN
    +∞ +∞ +∞ +∞ +∞ NaN NaN
    -∞ -∞ -∞ -∞ NaN -∞ NaN
    NaN NaN NaN NaN NaN NaN NaN
  • Десятичное добавление:

    decimal operator +(decimal x, decimal y);
    

    Если величина результирующего значения слишком велика, чтобы представить ее в десятичном формате, вызывается исключение System.OverflowException. Масштаб результата, прежде чем любое округление, больше масштабов двух операндов.

    Десятичное добавление эквивалентно использованию оператора добавления типа System.Decimal.

  • Добавление перечисления. Каждый тип перечисления неявно предоставляет следующие предопределенные операторы, где E — это тип перечисления, а U — это базовый тип для E.

    E operator +(E x, U y);
    E operator +(U x, E y);
    

    Во время выполнения эти операторы оцениваются точно так же, как (E)((U)x + (U)y).

  • Объединение строк:

    string operator +(string x, string y);
    string operator +(string x, object y);
    string operator +(object x, string y);
    

    Эти перегрузки двоичного оператора + выполняют объединение строк. Если операнд объединения строк равен null, он заменяется на пустую строку. В противном случае любой операнд, отличный отstring, преобразуется в строковое представление путем вызова метода виртуального ToString, унаследованного от типа object. Если ToString возвращает null, пустая строка будет заменена.

    пример:

    class Test
    {
        static void Main()
        {
            string s = null;
            Console.WriteLine("s = >" + s + "<");  // Displays s = ><
    
            int i = 1;
            Console.WriteLine("i = " + i);         // Displays i = 1
    
            float f = 1.2300E+15F;
            Console.WriteLine("f = " + f);         // Displays f = 1.23E+15
    
            decimal d = 2.900m;
            Console.WriteLine("d = " + d);         // Displays d = 2.900
       }
    }
    

    Выходные данные, показанные в комментариях, являются типичным результатом в системе US-English. Точные выходные данные могут зависеть от региональных параметров среды выполнения. Оператор объединения строк ведет себя одинаково в каждом случае, но методы ToString, неявно вызываемые во время выполнения, могут быть затронуты региональными настройками.

    конечный пример

    Результатом оператора объединения строк является string, состоящий из символов левого операнда, за которым следуют символы правого операнда. Оператор объединения строк никогда не возвращает значение null. System.OutOfMemoryException может возникать, если недостаточно памяти для выделения результирующей строки.

  • Сочетание делегатов. Каждый тип делегата неявно предоставляет следующий предопределенный оператор, где D является типом делегата:

    D operator +(D x, D y);
    

    Если первый операнд null, результатом операции является значение второго операнда (даже если это также null). В противном случае, если второй операнд равен null, то результат операции — это значение первого операнда. В противном случае результатом операции является новый экземпляр делегата, список вызовов которого состоит сначала из элементов списка вызовов первого операнда, а затем из элементов списка вызовов второго операнда. То есть список вызовов результирующего делегата — объединение списков вызовов двух операндов.

    Примечание. Примеры сочетания делегатов см. в разделе §12.10.6 и §20.6. Так как System.Delegate не является типом делегата, оператор + не определен для него. в конце

Поднятые (§12.4.8) формы неподнятых предопределенных операторов сложения, определенные выше, также предопределены.

12.10.6 Оператор вычитания

Для операции формы x – yприменяется разрешение на перегрузку двоичного оператора (§12.4.5) для выбора конкретной реализации оператора. Операнды преобразуются в типы параметров выбранного оператора, а тип результата — возвращаемый тип оператора.

Ниже перечислены предопределенные операторы вычитания. Все операторы вычитают y из x.

  • Вычитание целочисленного числа:

    int operator –(int x, int y);
    uint operator –(uint x, uint y);
    long operator –(long x, long y);
    ulong operator –(ulong x, ulong y
    

    В контексте checked, если разница выходит за пределы диапазона типа результата, выбрасывается System.OverflowException. В контексте unchecked переполнения не учитываются, и любые значимые старшие биты вне диапазона результирующего типа отбрасываются.

  • Вычитание с плавающей запятой:

    float operator –(float x, float y);
    double operator –(double x, double y);
    

    Разница вычисляется в соответствии с правилами арифметики IEC 60559. В следующей таблице перечислены результаты всех возможных сочетаний ненулевого конечного значения, нули, бесконечности и naNs. В таблице x и y являются ненулевыми конечными значениями, а z — результатом x – y. Если x и y равны, z является положительным нулем. Если x – y слишком велик для представления в целевом типе, z это бесконечность с тем же знаком, что и x – y.

    y +0 -0 +∞ -∞ NaN
    x z x x -∞ +∞ NaN
    +0 -y +0 +0 -∞ +∞ NaN
    -0 -y -0 +0 -∞ +∞ NaN
    +∞ +∞ +∞ +∞ NaN +∞ NaN
    -∞ -∞ -∞ -∞ -∞ NaN NaN
    NaN NaN NaN NaN NaN NaN NaN

    (В приведенной выше таблице записи -y указывают отрицаниеy, а не отрицательное значение.)

  • Десятичное вычитание:

    decimal operator –(decimal x, decimal y);
    

    Если величина результирующего значения слишком велика, чтобы представить в десятичном формате, выдается System.OverflowException. Масштаб результата, прежде чем любое округление, больше масштабов двух операндов.

    Десятичное вычитание эквивалентно использованию оператора вычитания типа System.Decimal.

  • Вычитание элементов перечисления. Каждый тип перечисления неявно предоставляет следующий предопределенный оператор, где E является типом перечисления, и U является базовым типом E:

    U operator –(E x, E y);
    

    Этот оператор вычисляется точно так же, как (U)((U)x – (U)y). Другими словами, оператор вычисляет разницу между порядковых значений x и y, а тип результата — базовым типом перечисления.

    E operator –(E x, U y);
    

    Этот оператор вычисляется точно так же, как (E)((U)x – y). Другими словами, оператор вычитает значение из базового типа перечисления, что дает значение перечисления.

  • Удаление делегата. Каждый тип делегата неявно предоставляет следующий предопределенный оператор, где D является типом делегата:

    D operator –(D x, D y);
    

    Семантика выглядит следующим образом:

    • Если первый операнд равен null, то результат операции равен null.
    • В противном случае если второй операнд null, результат операции — значение первого операнда.
    • В противном случае оба операнда представляют непустые списки вызовов (§20.2).
      • Если списки равны, как это определено оператором равенства делегатов (§12.12.9), то результат операции — это null.
      • В противном случае результатом операции является новый список вызовов, состоящий из списка первого операнда со записями второго операнда, удаленными из него, если список второго операнда является под списком первого операнда. (Чтобы определить равенство подсписка, соответствующие записи сравниваются так же, как для оператора равенства делегата.) Если список второго операнда совпадает с несколькими подсписками смежных записей в списке первого операнда, то последний совпадающий подсписок смежных записей удаляется.
      • В противном случае результатом операции является значение левого операнда.

    Ни один из списков операндов (если таковой) не изменяется в процессе.

    пример:

    delegate void D(int x);
    
    class C
    {
        public static void M1(int i) { ... }
        public static void M2(int i) { ... }
    }
    
    class Test
    {
        static void Main()
        {
            D cd1 = new D(C.M1);
            D cd2 = new D(C.M2);
            D list = null;
    
            list = null - cd1;                             // null
            list = (cd1 + cd2 + cd2 + cd1) - null;         // M1 + M2 + M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - cd1;          // M1 + M2 + M2
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2);  // M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd2);  // M1 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd1);  // M1 + M2
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd1);  // M1 + M2 + M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2 + cd2 + cd1);  // null
        }
    }
    

    конечный пример

Поднятые (§12.4.8) формы неподнятых предопределенных операторов вычитания, определенных выше, также предопределены.

Операторы сдвига 12.11

Операторы << и >> используются для выполнения операций с перемещением битов.

shift_expression
    : additive_expression
    | shift_expression '<<' additive_expression
    | shift_expression right_shift additive_expression
    ;

Если операнд shift_expression имеет тип времени компиляции dynamic, выражение динамически привязано (§12.3.3). В этом случае тип времени компиляции выражения dynamic, и разрешение, описанное ниже, будет выполняться во время выполнения с помощью типа времени выполнения этих операндов, имеющих тип времени компиляции dynamic.

Для операции формы x << count или x >> countиспользуется разрешение перегрузки двоичных операторов (§12.4.5) для выбора конкретной реализации оператора. Операнды преобразуются в типы параметров выбранного оператора, а тип результата — возвращаемый тип оператора.

При объявлении перегруженного оператора shift тип первого операнда всегда должен быть классом или структурой, содержащей объявление оператора, и тип второго операнда всегда должен быть int.

Ниже перечислены предопределенные операторы смены.

  • Сдвиг влево

    int operator <<(int x, int count);
    uint operator <<(uint x, int count);
    long operator <<(long x, int count);
    ulong operator <<(ulong x, int count);
    

    Оператор << сдвигает x влево на количество битов, вычисляемое, как описано ниже.

    Биты высокого порядка за пределами диапазона типа результатов x удаляются, остальные биты сдвигаются влево, а пустые позиции пустых битов низкого порядка равны нулю.

  • Сдвиг вправо:

    int operator >>(int x, int count);
    uint operator >>(uint x, int count);
    long operator >>(long x, int count);
    ulong operator >>(ulong x, int count);
    

    Оператор >> сдвигает x вправо на несколько битов, вычисляемых, как описано ниже.

    Если x имеет тип int или long, то младшие биты x удаляются, оставшиеся биты сдвигаются вправо, и позиции старших пустых битов устанавливаются в нуль, если x не является отрицательным, и устанавливаются в единицу, если x отрицательный.

    Если x имеет тип uint или ulong, то низшие биты x отбрасываются, оставшиеся биты сдвигаются вправо, а позиции старших битов заполняются нулями.

Для предопределенных операторов число битов для смены вычисляется следующим образом:

  • Если тип x, int или uint, число сдвигов определяется младшими пятью битами count. Другими словами, количество сдвигов вычисляется из count & 0x1F.
  • Если тип xlong или ulong, то число сдвигов определяется младшими шестью битами count. Другими словами, количество сдвигов вычисляется из count & 0x3F.

Если результирующее число сдвигов равно нулю, операторы сдвига просто возвращают значение x.

Операции сдвига никогда не вызывают переполнений и создают те же результаты в проверяемых и непроверяемых контекстах.

Когда левый операнд оператора >> имеет подписанный целочисленный тип, оператор выполняет арифметический арифметический сдвиг вправо, где значение наиболее значительного бита (бит знака) операнда распространяется на пустые позиции большого порядка. Если левый операнд оператора >> имеет неподписанный целочисленный тип, оператор выполняет логический сдвиг вправо, где пустые битовые позиции высокого порядка всегда равны нулю. Для выполнения противоположной операции, выводимой из типа операнда, можно использовать явные приведения.

Пример: Если x является переменной типа int, операция unchecked ((int)((uint)x >> y)) выполняет логический сдвиг вправо из x. конечный пример

Поднятые (§12.4.8) формы нескорректированных предопределенных операторов сдвига, упомянутых выше, также заранее определены.

Операторы реляционного и типового тестирования 12.12

12.12.1 Общие

Операторы ==, !=, <, >, <=, >=, isи as называются операторами реляционного и типового тестирования.

relational_expression
    : shift_expression
    | relational_expression '<' shift_expression
    | relational_expression '>' shift_expression
    | relational_expression '<=' shift_expression
    | relational_expression '>=' shift_expression
    | relational_expression 'is' type
    | relational_expression 'is' pattern
    | relational_expression 'as' type
    ;

equality_expression
    : relational_expression
    | equality_expression '==' relational_expression
    | equality_expression '!=' relational_expression
    ;

Примечание: Поиск правого операнда оператора is должен сначала проверяться как тип , затем как выражение , которое может включать несколько токенов. В случае, если операнд является экспрезионом, выражение шаблона должно иметь приоритет по крайней мере как shift_expression. конечная заметка

Оператор is описан в §12.12.12, а оператор as описан в §12.12.13.

Операторы , , , , и являются операторами сравнения.

Если default_literal (§12.8.21) используется в качестве операнда <, >, <=или оператора >=, возникает ошибка во время компиляции. Если default_literal используется в качестве обоих операндов оператора == или !=, возникает ошибка во время компиляции. Если default_literal используется в качестве левого операнда оператора is или as, возникает ошибка во время компиляции.

Если операнды оператора сравнения имеют тип времени компиляции dynamic, выражение динамически привязано (§12.3.3). В этом случае тип выражения во время компиляции — dynamic, и процесс разрешения, описанный ниже, будет происходить во время выполнения, используя тип времени выполнения для тех операндов, которые имеют тип времени компиляции dynamic.

Для операции формы x «op» y, где "op" является оператором сравнения, разрешение перегрузки (§12.4.5) применяется для выбора конкретной реализации оператора. Операнды преобразуются в типы параметров выбранного оператора, а тип результата — возвращаемый тип оператора. Если оба операнда equality_expression являются литералами null, разрешение перегрузки не выполняется, и выражение оценивается в константное значение true или false в зависимости от того, == или !=оператор используется.

Предопределенные операторы сравнения описаны в следующих подклаузах. Все предопределенные операторы сравнения возвращают результат типа bool, как описано в следующей таблице.

операции результат
x == y true, если x равно y, false в противном случае
x != y true, если x не равно y, false в противном случае
x < y true, если x меньше y, false в противном случае
x > y true, если x больше y, false в противном случае
x <= y true, если x меньше или равно y, false в противном случае
x >= y true, если x больше или равно y, false в противном случае

Операторы сравнения целых чисел 12.12.2

Стандартные операторы сравнения целых чисел:

bool operator ==(int x, int y);
bool operator ==(uint x, uint y);
bool operator ==(long x, long y);
bool operator ==(ulong x, ulong y);

bool operator !=(int x, int y);
bool operator !=(uint x, uint y);
bool operator !=(long x, long y);
bool operator !=(ulong x, ulong y);

bool operator <(int x, int y);
bool operator <(uint x, uint y);
bool operator <(long x, long y);
bool operator <(ulong x, ulong y);

bool operator >(int x, int y);
bool operator >(uint x, uint y);
bool operator >(long x, long y);
bool operator >(ulong x, ulong y);

bool operator <=(int x, int y);
bool operator <=(uint x, uint y);
bool operator <=(long x, long y);
bool operator <=(ulong x, ulong y);

bool operator >=(int x, int y);
bool operator >=(uint x, uint y);
bool operator >=(long x, long y);
bool operator >=(ulong x, ulong y);

Каждый из этих операторов сравнивает числовые значения двух целых операндов и возвращает значение bool, указывающее, является ли конкретное отношение true или false.

Поднятые (§12.4.8) формы неподнятых предопределенных операторов сравнения целых чисел, определенных выше, также предопределены.

Операторы сравнения с плавающей запятой 12.12.3

Стандартные операторы сравнения с плавающей запятой:

bool operator ==(float x, float y);
bool operator ==(double x, double y);

bool operator !=(float x, float y);
bool operator !=(double x, double y);

bool operator <(float x, float y);
bool operator <(double x, double y);

bool operator >(float x, float y);
bool operator >(double x, double y);

bool operator <=(float x, float y);
bool operator <=(double x, double y);

bool operator >=(float x, float y);
bool operator >=(double x, double y);

Операторы сравнивают операнды в соответствии с правилами стандарта IEC 60559:

Если какой-либо операнд является NaN, результат false для всех операторов, кроме !=, для которого результат true. Для всех двух операндов x != y всегда выдает тот же результат, что и !(x == y). Однако если один или оба операнда являются NaN, то <, >, <=и >= операторы не создают те же результаты, что и логический отрицание противоположного оператора.

пример. Если один из x или y является NaN, тогда x < y является false, но !(x >= y) является true. конечный пример

Если ни один из операндов не является NaN, операторы сравнивают значения двух чисел с плавающей запятой в соответствии с порядком.

–∞ < –max < ... < –min < –0.0 == +0.0 < +min < ... < +max < +∞

где min и max являются наименьшими и крупнейшими положительными конечными значениями, которые можно представить в заданном формате с плавающей запятой. Важные эффекты этого упорядочения:

  • Отрицательные и положительные нули считаются равными.
  • Отрицательная бесконечность считается меньше всех остальных значений, но равна другой отрицательной бесконечности.
  • Положительная бесконечность считается больше всех остальных значений, но равна другой положительной бесконечности.

Поднятые (§12.4.8) формы незавержденных предопределенных операторов сравнения с плавающей запятой, определенные выше, также предопределяются.

Операторы сравнения десятичных разрядов 12.12.4

Стандартные операторы сравнения десятичных разрядов:

bool operator ==(decimal x, decimal y);
bool operator !=(decimal x, decimal y);
bool operator <(decimal x, decimal y);
bool operator >(decimal x, decimal y);
bool operator <=(decimal x, decimal y);
bool operator >=(decimal x, decimal y);

Каждый из этих операторов сравнивает числовые значения двух десятичных операндов и возвращает значение bool, указывающее, является ли конкретное отношение true или false. Каждое десятичное сравнение эквивалентно использованию соответствующего оператора реляционного или равенства типа System.Decimal.

Поднятые (§12.4.8) формы неподнятых предопределенных десятичных операторов сравнения, определенных выше, также предопределены.

12.12.5 Логические операторы равенства

Стандартные логические операторы равенства:

bool operator ==(bool x, bool y);
bool operator !=(bool x, bool y);

Результат == — это true, если и x, и y равны true, или если и x, и y равны false. В противном случае результат false.

Результат != является false, если x и y являются true, или если x и y являются false. В противном случае результат true. Если операнды имеют тип bool, оператор != выдает тот же результат, что и оператор ^.

Поднятые (§12.4.8) формы неподнятых предопределенных булевых операторов равенства, определенных выше, также предопределены.

12.12.6 Операторы сравнения перечисления

Каждый тип перечисления неявно предоставляет следующие предопределенные операторы сравнения

bool operator ==(E x, E y);
bool operator !=(E x, E y);

bool operator <(E x, E y);
bool operator >(E x, E y);
bool operator <=(E x, E y);
bool operator >=(E x, E y);

Результат вычисления x «op» y, где x и y являются выражениями типа перечисления E с базовым типом U, а "op" является одним из операторов сравнения, точно так же, как и при оценке ((U)x) «op» ((U)y). Другими словами, операторы сравнения типов перечисления просто сравнивают базовые целочисленные значения двух операндов.

Снятые (§12.4.8) формы незавержденных предопределенных операторов сравнения перечисления, определенные выше, также предопределяются.

Операторы равенства ссылочных типов 12.12.7

Каждый тип класса C неявно предоставляет следующие предопределенные операторы равенства ссылочных типов:

bool operator ==(C x, C y);
bool operator !=(C x, C y);

Если стандартные операторы равенства для C не определены иначе (например, когда C равен string или System.Delegate).

Операторы возвращают результат сравнения двух ссылок на равенство или не равенство. operator == возвращает true, если и только если x и y ссылаются на один и тот же экземпляр или оба являются null, а operator != возвращает true, если и только если operator == с теми же операндами возвращает false.

Помимо правил нормальной применимости (§12.6.4.2), предопределенные операторы равенства ссылочного типа требуют одного из следующих условий для применения:

  • Оба операнда — это значение типа, известного как reference_type или литерал null. Кроме того, тождественное или явное преобразование ссылки (§10.3.5) существует от каждого операнда к типу другого операнда.
  • Один операнд является литеральным null, а другой операнд — значение типа T, где T является type_parameter, который не считается типом значения, а также не имеет ограничения на тип значения.
    • Если во время выполнения T является типом ненулевого значения, результатом == является false, а результат !=true.
    • Если во время выполнения T является типом значения, допускающего значение NULL, результат вычисляется из свойства HasValue операнда, как описано в разделе (§12.12.10).
    • Если во время выполнения T является ссылочным типом, результат — это true, если операнд null, иначе — false.

Если ни одно из этих условий не истинно, возникает ошибка времени привязки.

примечание. Важные последствия этих правил:

  • Это ошибка времени связывания использовать предопределенные операторы равенства ссылочных типов для сравнения двух ссылок, которые заведомо различны на момент связывания. Например, если типы времени привязки операндов являются двумя типами классов, и если ни один из них не является производным от другого, то для двух операндов невозможно ссылаться на один и тот же объект. Таким образом, операция считается ошибкой времени связывания.
  • Предопределенные операторы равенства ссылочного типа не позволяют сравнивать операнды типов значений (за исключением случаев, когда параметры типа сравниваются с null, который обрабатывается специально).
  • Операнды предопределенных операторов ссылочного типа равенства никогда не упаковываются. Выполнение таких операций по упаковке было бы бессмысленным, так как ссылки на только что выделенные упакованные экземпляры обязательно будут отличаться от всех остальных ссылок.

Для операции формы x == y или x != y, если существуют какие-либо применимые пользовательские operator == или operator !=, правила разрешения перегрузки оператора (§12.4.5) выберут этот оператор вместо встроенного оператора равенства ссылочного типа. Всегда можно выбрать предопределенный оператор равенства ссылочного типа путем явного приведения одного или обоих операндов к типу object.

заметка концовая

пример. В следующем примере проверяется, является ли аргумент типа параметра без ограничений null.

class C<T>
{
   void F(T x)
   {
      if (x == null)
      {
          throw new ArgumentNullException();
      }
      ...
   }
}

Конструкция x == null допускается, даже если T может представлять тип значения, не допускающего значения NULL, и результат определяется как false, если T является типом значений, не допускающих значение NULL.

конечный пример

Для операции формы x == y или x != y, если существуют какие-либо применимые operator == или operator !=, процедура разрешения перегрузки оператора (§12.4.5) будет выбирать соответствующий оператор вместо предопределенного оператора равенства для ссылочного типа.

Примечание. Всегда можно выбрать предопределенный оператор равенства ссылочного типа путем явного приведения обоих операндов к типу object. конечная заметка

пример: пример

class Test
{
    static void Main()
    {
        string s = "Test";
        string t = string.Copy(s);
        Console.WriteLine(s == t);
        Console.WriteLine((object)s == t);
        Console.WriteLine(s == (object)t);
        Console.WriteLine((object)s == (object)t);
    }
}

создает выходные данные

True
False
False
False

Переменные s и t относятся к двум отдельным экземплярам строк, содержащим одинаковые символы. Первое сравнение выводит True, потому что предопределенный оператор равенства строк (§12.12.8) выбирается, когда оба операнда имеют тип string. Остальные сравнения дают результат False, так как перегрузка operator == в типе string неприменима, если хотя бы у одного из операндов тип времени привязки object.

Обратите внимание, что приведенный выше метод не имеет значения для типов значений. Пример

class Test
{
    static void Main()
    {
        int i = 123;
        int j = 123;
        Console.WriteLine((object)i == (object)j);
    }
}

выводит результат False, поскольку приведение типов создает ссылки на два отдельных экземпляра упакованных значений int.

конечный пример

Операторы равенства строк 12.12.8

Стандартные операторы равенства строк:

bool operator ==(string x, string y);
bool operator !=(string x, string y);

Два значения string считаются равными, если выполняется одно из следующих условий:

  • Оба значения null.
  • Оба значения не являютсяnull ссылками на строковые экземпляры, имеющие одинаковую длину и идентичные символы на каждой позиции.

Операторы равенства строк сравнивают строковые значения, а не строковые ссылки. Если два отдельных экземпляра строки содержат ту же последовательность символов, значения строк равны, но ссылки отличаются.

примечание. Как описано в §12.12.7, операторы равенства ссылочного типа можно использовать для сравнения строковых ссылок вместо строковых значений. концевая сноска

Операторы равенства делегатов 12.12.9

Стандартные операторы равенства делегатов:

bool operator ==(System.Delegate x, System.Delegate y);
bool operator !=(System.Delegate x, System.Delegate y);

Два экземпляра делегата считаются равными следующим образом:

  • Если один из экземпляров делегата null, они равны только в том случае, если оба экземпляра null.
  • Если делегаты имеют другой тип времени выполнения, они никогда не равны.
  • Если оба экземпляра делегата имеют список вызовов (§20.2), тогда эти экземпляры равны в том и только в том случае, если длина их списков вызовов одинакова, и каждая запись в списке вызовов одного в точности соответствует соответствующей записи в том же порядке в списке вызовов другого (как определено ниже).

Следующие правила определяют равенство записей списка вызовов:

  • Если две записи списка вызовов относятся к одному и тому же статическому методу, то записи равны.
  • Если две записи списка вызовов относятся к одному и тому же нестатическому методу на одном целевом объекте (как определено операторами ссылочного равенства), то записи равны.
  • Записи списка вызовов, созданные при оценке семантически идентичных анонимных функций (§12.19) с тем же (возможно пустым) набором захваченных экземпляров внешних переменных, могут быть равными (но не обязаны быть таковыми).

Если разрешение перегрузки оператора сводится к оператору равенства делегатов, и типы времени привязки обоих операндов являются типами делегатов, как описано в разделе §20, а не System.Delegate, и между типами операндов времени привязки отсутствует идентификационное преобразование, возникает ошибка времени привязки.

Примечание. Это правило предотвращает сравнение, которое никогда не может считать значения, неnull, равными, поскольку они являются ссылками на экземпляры разных типов делегатов. конечная сноска

12.12.10 Операторы равенства между nullable типами значений и литералом NULL

Операторы == и != позволяют одному операнду быть значением типа значений, допускающего значение NULL, а другой — литералом null, даже если для операции не существует предопределенного или определяемого пользователем оператора (в неуправляемой или снятой форме).

Для выполнения одной из предусмотренных форм

x == null    null == x    x != null    null != x

где x является выражением типа значения, допускающего значение NULL, если разрешение перегрузки оператора (§12.4.5) не удается найти применимый оператор, результат вычисляется из свойства HasValuex. В частности, первые две формы преобразуются в !x.HasValue, а последние две формы преобразуются в x.HasValue.

Операторы равенства кортежей 12.12.11

Операторы равенства кортежа применяются попарно к элементам кортежей операндов в лексическом порядке.

Если каждый из операндов x и y оператора == или != классифицируется либо как кортеж, либо как значение с типом кортежа (§8.3.11), то оператор является оператором равенства кортежей.

Если операнд e классифицирован как кортеж, элементы e1...en должны быть результатами оценки выражений элементов кортежа. В противном случае если e является значением типа кортежа, элементы должны быть t.Item1...t.Itemn, где t является результатом вычисления e.

Операнды x и y оператора равенства кортежа должны иметь одинаковую арность, иначе возникает ошибка времени компиляции. Для каждой пары элементов xi и yiприменяется один и тот же оператор равенства, который должен дать результат типа bool, dynamic, тип, имеющий неявное преобразование в bool, или тип, определяющий операторы true и false.

Оператор равенства кортежей x == y используется следующим образом:

  • Операнд с левой стороны x вычисляется.
  • Операнд y с правой стороны вычисляется.
  • Для каждой пары элементов xi и yi в лексическом порядке:
    • Оператор xi == yi вычисляется, и результат типа bool получен следующим образом:
      • Если сравнение дало bool, это результат.
      • В противном случае, если сравнение дало dynamic, оператор false динамически вызывается на нем, а результирующее bool значение отрицается с помощью оператора логического отрицания (!).
      • В противном случае, если тип сравнения имеет неявное преобразование в bool, это преобразование применяется.
      • В противном случае, если тип сравнения имеет оператор false, вызывается этот оператор, а результирующее значение bool отрицается с помощью оператора логического отрицания (!).
    • Если результирующий bool равен false, то дальнейшая оценка не выполняется, и результат оператора равенства кортежей будет false.
  • Если все сравнения элементов дали true, результатом оператора равенства кортежа является true.

Оператор равенства кортежей x != y обрабатывается следующим образом:

  • Оценивается левый операнд x.
  • Операнд с правой стороны y вычисляется.
  • Для каждой пары элементов xi и yi в лексическом порядке:
    • Оператор xi != yi вычисляется, и следующим образом получается результат типа bool:
      • Если сравнение дало bool, это результат.
      • В противном случае, если сравнение дало dynamic, оператор true динамически вызывается на нем, и полученное значение bool будет результатом.
      • В противном случае, если тип сравнения имеет неявное преобразование в bool, это преобразование применяется.
      • В противном случае, если у типа сравнения есть оператор true, этот оператор вызывается, и результатом является полученное значение bool.
    • Если результирующий bool равен true, то дальнейшая оценка не выполняется, и результат равенства кортежей будет true.
  • Если все сравнения элементов дали false, результатом оператора равенства кортежа является false.

12.12.12 Оператор is

Существует две формы оператора is. Одним из таких является оператор типа "is" с типом справа. Другая — это оператор шаблона is, у которого шаблон с правой стороны.

12.12.12.1 Оператор is-type

Оператор is-type используется для проверки, совпадает ли тип времени выполнения объекта с заданным типом. Проверка выполняется во время работы. Результат операции E is T, где E является выражением и T является типом, отличным от dynamic, это логическое значение, указывающее, что E не является null и может быть успешно преобразован в тип T с помощью ссылочного преобразования, упаковки, распаковки, заворачивания или разворачивания.

Операция оценивается следующим образом:

  1. Если E является анонимной функцией или группой методов, возникает ошибка во время компиляции.
  2. Если E является литералом null, или если значение E равно null, результатом является false.
  3. Иначе:
  4. Пусть R будет типом выполнения для E.
  5. Пусть D будет получен из R следующим образом:
  6. Если R является типом значения, допускающего значение NULL, D является базовым типом R.
  7. В противном случае D это R.
  8. Результат зависит от D и T следующим образом:
  9. Если T является ссылочным типом, результат равен true, если:
    • Тождественное преобразование существует между D и T.
    • D является ссылочным типом, и неявное преобразование ссылки из D в T имеется или
    • Либо: D — это тип значения, а преобразование бокса из D в T существует.
      Или: D — это тип значения, а T — это тип интерфейса, реализованный D.
  10. Если T является типом значения, допускающим значение NULL, результат будет true, если D является базовым типом T.
  11. Если T является типом ненулевого значения, результат true, если D и T одинаковы.
  12. В противном случае результат false.

Определяемые пользователем преобразования не учитываются оператором is.

Примечание. Так как оператор is оценивается во время выполнения, все аргументы типа были заменены и нет открытых типов (§8.4.3) для рассмотрения. конечная сноска

примечание. Оператор is можно понять с точки зрения типов и преобразований во время компиляции следующим образом, где C является типом времени компиляции E:

  • Если тип времени компиляции e совпадает с Tили если неявное преобразование ссылок (§10.2.8), преобразование упаковки (§10.2.9), преобразование упаковки (§10.6) или явное преобразование распаковки (§10.6) существует от типа времени компиляции E к T:
    • Если C имеет тип значения, который не допускает NULL, результатом операции будет true.
    • В противном случае результат операции эквивалентен оценке E != null.
  • В противном случае, если существует явное преобразование ссылки (§10.3.5) или распаковка (§10.3.7) от C до T, или если C или T является открытым типом (§8.4.3), то проверки времени выполнения, как указано выше, должны выполняться.
  • В противном случае ни преобразование по ссылке, ни упаковка, ни разворачивание E в тип T невозможно, и результат операции — false. Компилятор может реализовать оптимизации на основе типа времени компиляции.

конечная сноска

12.12.12.2 Оператор is-pattern

Оператор является шаблоном, используется для проверки соответствия значения, вычисляемого выражением заданному шаблону (§11). Проверка выполняется во время работы. Результат оператора is-pattern имеет значение true, если значение соответствует шаблону; в противном случае значение false.

Для выражения типа E is P, где E является реляционным выражением типа T и P является шаблоном, возникает ошибка времени компиляции, если выполняется любое из следующих условий:

  • E не обозначает значение или не имеет типа.
  • Шаблон P неприменимо (§11.2) к типу T.

12.12.13 Оператор as

Оператор as используется для явного преобразования значения в заданный ссылочный тип или тип значения, допускающий значение NULL. В отличие от выражения приведения (§12.9.7), оператор as никогда не создает исключение. Вместо этого, если указанное преобразование невозможно, то результат будет null.

В операции формы E as TE должно быть выражением, и T должен быть ссылочным типом, параметр типа, известный как ссылочный тип, или тип значения, допускающий значение NULL. Кроме того, по крайней мере одно из следующих значений должно иметь значение true или в противном случае возникает ошибка во время компиляции:

  • Тождественное преобразование (§10.2.2), неявное nullable (§10.2.6), неявная ссылка (§10.2.8), упаковка (§10.2.9), явное nullable (§10.3.4), явная ссылка (§10.3.5) или обертывание (§8.3.12) существует преобразование от E к T.
  • Тип E или T является открытым типом.
  • E — это литерал null.

Если тип времени компиляции E не dynamic, операция E as T создает тот же результат, что и

E is T ? (T)(E) : (T)null

за исключением того, что E оценивается только один раз. Ожидается, что компилятор оптимизирует E as T для выполнения не более одного проверки типа среды выполнения, а не двух проверок типа среды выполнения, подразумеваемых расширением выше.

Если во время компиляции тип E является dynamic, то в отличие от оператора приведения, оператор as не связан динамически (§12.3.3). Таким образом, расширение в данном случае:

E is T ? (T)(object)(E) : (T)null

Обратите внимание, что некоторые преобразования, такие как определяемые пользователем преобразования, невозможно выполнить с помощью оператора as и должны выполняться с помощью операции приведения.

пример: в примере

class X
{
    public string F(object o)
    {
        return o as string;  // OK, string is a reference type
    }

    public T G<T>(object o)
        where T : Attribute
    {
        return o as T;       // Ok, T has a class constraint
    }

    public U H<U>(object o)
    {
        return o as U;       // Error, U is unconstrained
    }
}

Параметр типа T для G, как известно, является ссылочным типом, так как он имеет ограничение класса. Параметр типа U для H ошибочный; поэтому использование оператора as в H запрещено.

конечный пример

Логические операторы 12.13

12.13.1 Общие положения

Операторы &,^и | называются логическими операторами.

and_expression
    : equality_expression
    | and_expression '&' equality_expression
    ;

exclusive_or_expression
    : and_expression
    | exclusive_or_expression '^' and_expression
    ;

inclusive_or_expression
    : exclusive_or_expression
    | inclusive_or_expression '|' exclusive_or_expression
    ;

Если операнд логического оператора имеет тип времени компиляции dynamic, выражение динамически привязано (§12.3.3). В этом случае тип времени компиляции выражения dynamic, и разрешение, описанное ниже, будет происходить во время выполнения с использованием типа времени выполнения этих операндов, имеющих тип времени компиляции dynamic.

Для операции формы x «op» y, где "op" является одним из логических операторов, разрешение перегрузки (§12.4.5) применяется для выбора конкретной реализации оператора. Операнды преобразуются в типы параметров выбранного оператора, а тип результата — возвращаемый тип оператора.

Предопределенные логические операторы описаны в следующих подклаузах.

12.13.2 Целые логические операторы

Стандартные логические операторы целого числа:

int operator &(int x, int y);
uint operator &(uint x, uint y);
long operator &(long x, long y);
ulong operator &(ulong x, ulong y);

int operator |(int x, int y);
uint operator |(uint x, uint y);
long operator |(long x, long y);
ulong operator |(ulong x, ulong y);

int operator ^(int x, int y);
uint operator ^(uint x, uint y);
long operator ^(long x, long y);
ulong operator ^(ulong x, ulong y);

Оператор & вычисляет побитовую логическую И двух операндов, оператор | вычисляет побитовую логическую ИЛИ двух операндов, а оператор ^ вычисляет побитовую логическую исключающее ИЛИ двух операндов. Переполнения при выполнении этих операций невозможны.

Поднятые (§12.4.8) формы неподнятых предопределенных целочисленных логических операторов, определенных выше, также предопределены.

Логические операторы перечисления 12.13.3

Каждый тип перечисления E неявно предоставляет следующие предопределенные логические операторы:

E operator &(E x, E y);
E operator |(E x, E y);
E operator ^(E x, E y);

Результат оценки x «op» y, где x и y являются выражениями типа перечисления E с базовым типом U, а "op" является одним из логических операторов, точно так же, как и вычисление (E)((U)x «op» (U)y). Другими словами, логические операторы типа перечисления просто выполняют логическую операцию в базовом типе двух операндов.

Поднятые (§12.4.8) формы неподнятых предопределенных логических операторов перечисления, описанных выше, также предопределены.

12.13.4 Логические операторы

Стандартные логические операторы:

bool operator &(bool x, bool y);
bool operator |(bool x, bool y);
bool operator ^(bool x, bool y);

Результат x & ytrue, если x и y равны true. В противном случае результат false.

Результат x | y равен true, если x или y является true. В противном случае результат false.

Результатом x ^ y является true, если x равен true и y равен false, или x равен false и y равен true. В противном случае результат false. Если операнды имеют тип bool, оператор ^ вычисляет тот же результат, что и оператор !=.

12.13.5 Nullable булевский тип & и | операторы

Булевый тип Nullable bool? может представлять три значения: true, falseи null.

Как и в случае с другими двоичными операторами, поднимаемые формы логических операторов & и | (§12.13.4) также предопределяются:

bool? operator &(bool? x, bool? y);
bool? operator |(bool? x, bool? y);

Семантика снятых & и | операторов определяется следующей таблицей:

x y x & y x \| y
true true true true
true false false true
true null null true
false true false true
false false false false
false null false null
null true null true
null false false null
null null null null

Примечание. Тип bool? концептуально похож на трехзначный тип, используемый для логических выражений в SQL. Приведенная выше таблица соответствует той же семантике, что и SQL, однако применение правил §12.4.8 к операторам & и | им бы не соответствовало. Правила §12.4.8 уже предоставляют SQL-подобную семантику для оператора ^, реализованного с помощью поднятия. конечная сноска

12.14 Условные логические операторы

12.14.1 General

Операторы && и || называются условными логическими операторами. Они также называются короткозамыкающими логическими операторами.

conditional_and_expression
    : inclusive_or_expression
    | conditional_and_expression '&&' inclusive_or_expression
    ;

conditional_or_expression
    : conditional_and_expression
    | conditional_or_expression '||' conditional_and_expression
    ;

Операторы && и || являются условными версиями операторов & и |:

  • Операция x && y соответствует операции x & y, за исключением того, что y вычисляется только в том случае, если x не false.
  • Операция x || y соответствует операции x | y, кроме того, y вычисляется только в том случае, если x не true.

Примечание. Причина, по которой короткое замыкание использует условия "не true" и "не false", заключается в том, чтобы разрешить определяемым пользователем условным операторам определить, когда применяется короткое замыкание. Определяемые пользователем типы могут находиться в состоянии, где operator true возвращает false и operator false возвращает false. В таких случаях ни &&, ни || не будет коротким. конечная сноска

Если операнд условного логического оператора имеет тип времени компиляции dynamic, выражение динамически привязано (§12.3.3). В этом случае компилируемый тип выражения dynamic, и разрешение, описанное ниже, будет происходить во время выполнения с использованием типа времени выполнения тех операндов, которые имеют компилируемый тип dynamic.

Операция формы x && y или x || y обрабатывается путем применения разрешения перегрузки (§12.4.5), как если бы операция была написана x & y или x | y. Тогда

  • Если разрешение перегрузки не удается найти один лучший оператор или если разрешение перегрузки выбирает один из предопределенных логических операторов или логических операторов, допускающих значение NULL (§12.13.5), возникает ошибка во время привязки.
  • В противном случае, если выбранный оператор является одним из предопределенных логических операторов (§12.13.4), операция обрабатывается, как описано в §12.14.2.
  • В противном случае выбранный оператор является определяемым пользователем оператором, и операция обрабатывается, как описано в §12.14.3.

Невозможно напрямую перегружать условные логические операторы. Однако, поскольку условные логические операторы оцениваются с точки зрения обычных логических операторов, перегрузки обычных логических операторов также считаются перегрузками условных логических операторов. Это описано далее в §12.14.3.

12.14.2 Булевы условные логические операторы

Если операнды && или || имеют тип boolили когда операнды являются типами, которые не определяют применимые operator & или operator |, но определяют неявные преобразования в bool, операция обрабатывается следующим образом:

  • Операция x && y оценивается как x ? y : false. Другими словами, x сначала вычисляется и преобразуется в тип bool. Затем, если xtrue, y вычисляется и преобразуется в тип bool, и это становится результатом операции. В противном случае результат операции будет равен false.
  • Операция x || y оценивается как x ? true : y. Другими словами, x сначала вычисляется и преобразуется в тип bool. Затем, если x равно true, результат операции — true. В противном случае y вычисляется и преобразуется в тип bool, и это становится результатом операции.

12.14.3 Определяемые пользователем условные логические операторы

Если операнды && или || являются типами, в которых объявлены применимые пользовательские operator & или operator |, должны выполняться оба из следующих условий, где T является типом, в котором объявлен выбранный оператор:

  • Возвращаемый тип и тип каждого параметра выбранного оператора должны быть T. Другими словами, оператор должен вычислять логический И или логический ИЛИ двух операндов типа T, и должен возвращать результат типа T.
  • T должен содержать объявления operator true и operator false.

Ошибка во время привязки возникает, если одно из этих требований не удовлетворяется. В противном случае операция && или || вычисляется путем объединения определяемого пользователем operator true или operator false с выбранным пользовательским оператором:

  • Операция x && y оценивается как T.false(x) ? x : T.&(x, y), где T.false(x) является вызовом operator false, объявленного в T, и T.&(x, y) является вызовом выбранного operator &. Другими словами, x сначала вычисляется и operator false вызывается в результате, чтобы определить, является ли x определенно ложным. Затем, если x определенно false, результат операции — это значение, ранее вычисляемое для x. В противном случае вычисляется y, и выбранный operator & вызывается на значении, ранее вычисленном для x, и на значении, вычисленном для y, чтобы получить результат операции.
  • Операция x || y оценивается как T.true(x) ? x : T.|(x, y), где T.true(x) является вызовом operator true, объявленного в T, и T.|(x, y) является вызовом выбранного operator |. Другими словами, x сначала вычисляется и operator true вызывается в результате, чтобы определить, является ли x определенно истинным. Если x определенно верно, результат операции — это значение, ранее вычисляемое для x. В противном случае вычисляется y, а выбранный operator | вызывается на значении, ранее вычисленном для x, и значении, вычисленном для y, для получения результата операции.

В любой из этих операций выражение, заданное x, вычисляется только один раз, и выражение, заданное y, не вычисляется или вычисляется ровно один раз.

12.15 Оператор null-слияния

Оператор ?? называется оператором объединения null.

null_coalescing_expression
    : conditional_or_expression
    | conditional_or_expression '??' null_coalescing_expression
    | throw_expression
    ;

В выражении объединения с использованием null формы a ?? b, если a не являетсяnull, результатом будет a; в противном случае результат будет b. Операция оценивает b только в том случае, если anull.

Оператор объединения NULL является правым ассоциативным, то есть операции группируются справа налево.

пример: выражение вида a ?? b ?? c интерпретируется как ?? (b ?? c). В общих терминах выражение формы E1 ?? E2 ?? ... ?? EN возвращает первое из операндов, неnull, или null, если все операнды null. конечный пример

Тип выражения a ?? b зависит от того, какие неявные преобразования доступны на операндах. В порядке предпочтения Тип a ?? b имеет тип A₀, Aили B, где A является типом a (если a имеет тип), B является типом b(при условии, что b имеет тип), а A₀ является базовым типом A, если A является типом значений null или A в противном случае. В частности, a ?? b обрабатывается следующим образом:

  • Если A существует и не является допустимым значением NULL или ссылочным типом, возникает ошибка компиляции.
  • В противном случае, если A существует и b является динамическим выражением, тип результата dynamic. Во время выполнения a сначала вычисляется. Если a не null, a преобразуется в dynamic, и это становится результатом. В противном случае b вычисляется, и это становится результатом.
  • В противном случае, если A существует и является значением типа, допускающим NULL, и если неявное преобразование существует от b к A₀, то тип результата A₀. Во время выполнения a сначала вычисляется. Если a не null, a распаковывается в тип A₀, и это становится результатом. В противном случае b вычисляется и преобразуется в тип A₀, и это становится результатом.
  • В противном случае, если A существует и неявное преобразование существует от b до A, тип результата A. Во время выполнения сначала вычисляется a. Если a не равно NULL, то a становится результатом. В противном случае b вычисляется и преобразуется в тип A, и это становится результатом.
  • В противном случае, если A существует и является обнуляемым значимым типом, b имеет тип B, и, если существует неявное преобразование от A₀ до B, тип результата B. Во время выполнения a сначала вычисляется. Если a не null, a распакуется в тип A₀ и преобразуется в тип B, и это становится результатом. В противном случае b вычисляется и становится результатом.
  • В противном случае, если b имеет тип B и неявное преобразование существует от a до B, тип результата B. Во время выполнения сначала в первую очередь вычисляется a. Если a не null, a преобразуется в тип B, и это становится результатом. В противном случае вычисляется значение b и оно становится результатом.

В противном случае a и b несовместимы: возникает ошибка компиляции a.

12.16 Оператор выражения throw

throw_expression
    : 'throw' null_coalescing_expression
    ;

throw_expression выдает значение, созданное путем оценки null_coalescing_expression. Выражение должно неявно преобразовываться в System.Exception, а результат оценки выражения преобразуется в System.Exception перед выбрасыванием. Поведение во время выполнения вычисления выражения выброса совпадает с заданным для оператора выброса (§13.10.6).

throw_expression не имеет типа. throw_expression преобразуется в каждый тип с помощью неявного преобразования throw.

Выражение должно возникать только в следующих синтаксических контекстах:

  • В качестве второго или третьего операнда тернарного условного оператора (?:).
  • В качестве второго операнда оператора объединения null (??).
  • Как тело лямбда-тела выражения или члена.

Выражения объявления 12.17

Выражение объявления объявляет локальную переменную.

declaration_expression
    : local_variable_type identifier
    ;

local_variable_type
    : type
    | 'var'
    ;

simple_name_ также рассматривается как выражение объявления, если поиск по простому имени не нашел связанного объявления (§12.8.4). При использовании в качестве выражения объявления _ называется простым отбросом. Он семантически эквивалентен var _, но разрешён в большем количестве случаев.

Выражение объявления должно происходить только в следующих синтактических контекстах:

  • Как outargument_value в argument_list.
  • В качестве простого исключения _, составляющего левую часть простого присваивания (§12.21.2).
  • Как tuple_element в одной или нескольких рекурсивно вложенных tuple_expression, внешний из которых находится на левой стороне деконструкционного присваивания. deconstruction_expression приводит к возникновению выражений объявления в этом контексте, несмотря на то, что они не синтаксически присутствуют.

примечание: Это означает, что выражение объявления не может быть заключено в скобки. конечная сноска

Недопустимо, чтобы неявно типизированная переменная, объявленная с declaration_expression, использовалась в argument_list, где она объявлена.

Это ошибка, если переменная, объявленная с declaration_expression, упоминается в деконструирующем присваивании, где она используется.

Выражение объявления, которое является простым "отбрасыванием" или где local_variable_type является идентификатором var, классифицируется как неявно типизированная переменная. Выражение не имеет типа, а тип локальной переменной определяется на основе синтаксического контекста следующим образом:

  • В argument_list выводимый тип переменной является объявленным типом соответствующего параметра.
  • В левой части простого присваивания инференцированный тип переменной определяется типом правой стороны присваивания.
  • В tuple_expression на левой стороне простого присваивания выведенный тип переменной определяется как тип соответствующего элемента кортежа на правой стороне (после деконструкции) присваивания.

В противном случае выражение объявления классифицируется как явно типизированной переменной, и как тип выражения, так и объявленная переменная должны быть заданы local_variable_type.

Выражение объявления с идентификатором _ является отменой (§9.2.9.2), и не вводит имя переменной. Выражение объявления с идентификатором, отличном от _, вводит это имя в ближайшее заключающее пространство объявления локальной переменной (§7.3).

пример:

string M(out int i, string s, out bool b) { ... }

var s1 = M(out int i1, "One", out var b1);
Console.WriteLine($"{i1}, {b1}, {s1}");
// Error: i2 referenced within declaring argument list
var s2 = M(out var i2, M(out i2, "Two", out bool b2), out b2);
var s3 = M(out int _, "Three", out var _);

Объявление s1 отображает как явно, так и неявно типизированные выражения объявления. Предполагаемый тип b1 является bool, потому что это тип соответствующего выходного параметра в M1. Последующий WriteLine может получить доступ к i1 и b1, которые были введены в охватывающую область.

Объявление s2 указывает на попытку использовать i2 в вложенном вызове M, что запрещено, так как ссылка возникает в списке аргументов, в котором был объявлен i2. С другой стороны, допускается ссылка на b2 в последнем аргументе, так как она возникает после окончания вложенного списка аргументов, где был объявлен b2.

Объявление s3 показывает использование неявно и явно типизированных выражений объявления, которые отбрасываются. Поскольку пропуски не объявляют именованную переменную, допускаются множественные вхождения идентификатора _.

(int i1, int _, (var i2, var _), _) = (1, 2, (3, 4), 5);

В этом примере показано использование неявно и явно типизированных выражений объявления для переменных и отбрасываний в деконструкционном присваивании. simple_name_ эквивалентен var _ при отсутствии объявления _.

void M1(out int i) { ... }

void M2(string _)
{
    M1(out _);      // Error: `_` is a string
    M1(out var _);
}

В этом примере показано использование var _ для предоставления неявно типизированного отбрасывания, когда _ недоступен, так как он обозначает переменную в окружающей области.

конечный пример

12.18 Условный оператор

Оператор ?: называется условным оператором. Иногда он также называется тернарным оператором.

conditional_expression
    : null_coalescing_expression
    | null_coalescing_expression '?' expression ':' expression
    | null_coalescing_expression '?' 'ref' variable_reference ':'
      'ref' variable_reference
    ;

Выражение броска (§12.16) не допускается в условном операторе, если ref присутствует.

Условное выражение формы b ? x : y сначала вычисляет условие b. Затем, если b равно true, x вычисляется и становится результатом операции. В противном случае y вычисляется и становится результатом операции. Условное выражение никогда не вычисляет одновременно и x и y.

Условный оператор является правым ассоциативным, то есть операции группируются справа налево.

пример: выражение формы a ? b : c ? d : e оценивается как a ? b : (c ? d : e). конечный пример

Первый операнд оператора ?: должен быть выражением, которое может быть неявно преобразовано в boolили выражение типа, реализующего operator true. Если ни в чем из этих требований не выполнены, возникает ошибка во время компиляции.

Если ref присутствует:

  • Конверсия типов должна существовать между типами двух variable_reference, и тип результата может быть любым из этих типов. Если один из типов dynamic, вывод типов предпочитает dynamic (§8.7). Если какой-либо тип является типом кортежа (§8.3.11), вывод типа включает имена элементов, когда имена элементов в одном порядковом расположении совпадают в обоих кортежах.
  • Результатом является ссылка на переменную, которую можно записывать, если обе ссылки на переменные допускают запись.

примечание. Если ref присутствует, conditional_expression возвращает ссылку на переменную, которую можно назначить ссылочной переменной с помощью оператора = ref или передать в качестве параметра reference/input/output. концевая заметка

Если ref нет, то второй и третий операнды, x и y, контролируют тип условного выражения у оператора ?::

  • Если x имеет тип X и y имеет тип Y, то
    • Если существует идентичное преобразование между X и Y, результатом является наилучший общий тип для набора выражений (§12.6.3.15). Если любой из типов dynamic, вывод типов отдаёт предпочтение dynamic (§8.7). Если какой-либо тип является типом кортежа (§8.3.11), вывод типа включает имена элементов, когда имена элементов в одном порядковом расположении совпадают в обоих кортежах.
    • В противном случае, если неявное преобразование (§10.2) существует от X до Y, но не от Y до X, то Y является типом условного выражения.
    • В противном случае, если неявное преобразование перечисления (§10.2.4) существует от X до Y, то Y — это тип условного выражения.
    • В противном случае, если неявное преобразование перечисления (§10.2.4) существует от Y до X, то X — это тип условного выражения.
    • В противном случае, если неявное преобразование (§10.2) существует от Y до X, но не от X до Y, то X является типом условного выражения.
    • В противном случае тип выражения не может быть определен, и возникает ошибка во время компиляции.
  • Если только один из x и y имеет тип, а оба x и y неявно преобразуются в этот тип, то это тип условного выражения.
  • В противном случае тип выражения не может быть определен, и возникает ошибка во время компиляции.

Обработка условного выражения ref формы b ? ref x : ref y во время выполнения состоит из следующих этапов:

  • Сначала вычисляется b, затем определяются значение bool и b.
    • Если неявное преобразование типа b в bool существует, это неявное преобразование выполняется для создания значения bool.
    • В противном случае для создания значения bool вызывается operator true, определенный типом b.
  • Если значение bool, созданное на шаге выше, равно true, то вычисляется x, и результирующая ссылка на переменную становится результатом условного выражения.
  • В противном случае вычисляется y, а результирующая ссылка на переменную становится результатом условного выражения.

Обработка условного выражения формы b ? x : y во время выполнения состоит из следующих шагов:

  • Сначала вычисляется b, а bool значение элемента b определяется:
    • Если неявное преобразование типа b в bool существует, это неявное преобразование выполняется для создания значения bool.
    • В противном случае вызывается operator true, определяемый типом b, для создания значения bool.
  • Если значение bool, полученное на предыдущем шаге, равно true, то x вычисляется, преобразуется в тип условного выражения, и этот результат становится результатом условного выражения.
  • В противном случае y вычисляется и преобразуется в тип условного выражения, и это становится результатом условного выражения.

12.19 Анонимные выражения функции

12.19.1 Общие

анонимная функция — это выражение, представляющее встроенное определение метода. Анонимная функция не имеет значения или типа в себе, но преобразуется в совместимый делегат или тип дерева выражений. Оценка преобразования анонимной функции зависит от целевого типа преобразования: если это тип делегата, преобразование преобразуется в значение делегата, ссылающееся на метод, который анонимная функция определяет. Если это тип дерева выражений, преобразование оценивается в дерево выражений, представляющее структуру метода в виде структуры объекта.

Примечание: по историческим причинам существует два синтаксических варианта анонимных функций, а именно lambda_expressionи anonymous_method_expression. Почти для всех целей lambda_expressionболее краткие и выразительные, чем anonymous_method_expression, и остаются в языке для обратной совместимости. концевая сноска

lambda_expression
    : 'async'? anonymous_function_signature '=>' anonymous_function_body
    ;

anonymous_method_expression
    : 'async'? 'delegate' explicit_anonymous_function_signature? block
    ;

anonymous_function_signature
    : explicit_anonymous_function_signature
    | implicit_anonymous_function_signature
    ;

explicit_anonymous_function_signature
    : '(' explicit_anonymous_function_parameter_list? ')'
    ;

explicit_anonymous_function_parameter_list
    : explicit_anonymous_function_parameter
      (',' explicit_anonymous_function_parameter)*
    ;

explicit_anonymous_function_parameter
    : anonymous_function_parameter_modifier? type identifier
    ;

anonymous_function_parameter_modifier
    : 'ref'
    | 'out'
    | 'in'
    ;

implicit_anonymous_function_signature
    : '(' implicit_anonymous_function_parameter_list? ')'
    | implicit_anonymous_function_parameter
    ;

implicit_anonymous_function_parameter_list
    : implicit_anonymous_function_parameter
      (',' implicit_anonymous_function_parameter)*
    ;

implicit_anonymous_function_parameter
    : identifier
    ;

anonymous_function_body
    : null_conditional_invocation_expression
    | expression
    | 'ref' variable_reference
    | block
    ;

При определении anonymous_function_body, если могут быть применены как null_conditional_invocation_expression, так и выражение альтернативные варианты, выбор должен быть сделан в пользу первого.

Примечание: перекрытие и приоритет между альтернативами здесь даются исключительно для удобства описания; правила грамматики можно уточнить, чтобы устранить эти перекрытия. ANTLR и другие системы грамматики принимают то же удобство и поэтому anonymous_function_body автоматически имеет указанную семантику. конечная заметка

Примечание: Если синтаксическая форма, такая как x?.M(), рассматривается как выражение , это будет ошибкой, если тип результата Mvoid (§12.8.13). Но при обработке как null_conditional_invocation_expressionтип результата может быть void. концевая записка

Пример: Тип результата List<T>.Reverse — это void. В следующем коде тело анонимного выражения является null_conditional_invocation_expression, следовательно, это не ошибка.

Action<List<int>> a = x => x?.Reverse();

конечный пример

Оператор => имеет тот же приоритет, что и назначение (=) и является правым ассоциативным.

Анонимная функция с модификатором async является асинхронной функцией и соответствует правилам, описанным в §15.15.

Параметры анонимной функции в виде lambda_expression могут быть явно или неявно типизированными. В явно типизированном списке параметров тип каждого параметра явно указывается. В неявном типизированном списке параметров типы параметров выводятся из контекста, в котором происходит анонимная функция, в частности, когда анонимная функция преобразуется в совместимый тип делегата или тип дерева выражений, этот тип предоставляет типы параметров (§10.7).

В lambda_expression с одним неявным типизированным параметром скобки могут быть опущены из списка параметров. Другими словами, анонимная функция формы

( «param» ) => «expr»

Может быть сокращено до

«param» => «expr»

Список параметров анонимной функции в виде anonymous_method_expression необязателен. Если задано, параметры должны быть явно типизированными. В противном случае анонимная функция преобразуется в делегат с любым списком параметров, не содержащим выходные параметры.

Блок тела анонимной функции всегда доступен (§13.2).

пример: ниже приведены примеры анонимных функций:

x => x + 1                             // Implicitly typed, expression body
x => { return x + 1; }                 // Implicitly typed, block body
(int x) => x + 1                       // Explicitly typed, expression body
(int x) => { return x + 1; }           // Explicitly typed, block body
(x, y) => x * y                        // Multiple parameters
() => Console.WriteLine()              // No parameters
async (t1,t2) => await t1 + await t2   // Async
delegate (int x) { return x + 1; }     // Anonymous method expression
delegate { return 1 + 1; }             // Parameter list omitted

конечный пример

Поведение lambda_expressionи anonymous_method_expressionсовпадает, за исключением следующих моментов:

  • anonymous_method_expressionпозволяют полностью опустить список параметров, что позволяет преобразовать в типы делегатов с любым списком параметров значений.
  • lambda_expressionпозволяют типам параметров быть опущенными и выводимыми, в то время как anonymous_method_expressionтребуют, чтобы типы параметров были явно указаны.
  • Тело лямбда-выражения может быть либо выражением, либо блоком, тогда как тело анонимного метод-выражения должно быть блоком.
  • Только у lambda_expressionесть преобразования в совместимые типы дерева выражений (§8.6).

12.19.2 Анонимные подписи функций

anonymous_function_signature задает имена и, при необходимости, типы параметров анонимной функции. Область параметров анонимной функции — это anonymous_function_body (§7.7). Вместе со списком параметров (если задано) тело анонимного метода представляет собой пространство объявления (§7.3). Таким образом, это ошибка на этапе компиляции, если имя параметра анонимной функции совпадает с именем локальной переменной, локальной константы или параметра, область действия которых включает anonymous_method_expression или lambda_expression.

Если у анонимной функции есть explicit_anonymous_function_signature, набор совместимых типов делегатов и типов дерева выражений ограничен теми, которые имеют одинаковые типы параметров и модификаторы в том же порядке (§10.7). В отличие от преобразований групп методов (§10.8), контравариантность типов параметров анонимной функции не поддерживается. Если у анонимной функции нет anonymous_function_signature, набор совместимых типов делегатов и типов дерева выражений ограничен теми, у которых нет выходных параметров.

Обратите внимание, что anonymous_function_signature не может включать атрибуты или массив параметров. Тем не менее, anonymous_function_signature может быть совместима с типом делегата, список параметров которого содержит массив параметров.

Обратите внимание также, что преобразование в тип дерева выражений, даже если оно совместимо, может завершиться неудачно во время компиляции (§8.6).

12.19.3 Анонимные тела функций

Тело (выражение или блок ) анонимной функции подчиняется следующим правилам:

  • Если анонимная функция содержит подпись, параметры, указанные в сигнатуре, доступны в тексте. Если анонимная функция не имеет подписи, ее можно преобразовать в тип делегата или тип выражения с параметрами (§10.7), но параметры не могут быть доступны в тексте.
  • За исключением параметров по ссылке, указанных в сигнатуре (если она есть) наиболее близкой окружающей анонимной функции, является ошибкой компиляции пытаться обратиться к параметру по ссылке.
  • За исключением параметров, указанных в сигнатуре (если таковая имеется) ближайшей обрамляющей анонимной функции, является ошибкой компиляции, если тело обращается к параметру типа ref struct.
  • Если тип this является структурой, попытка доступа к thisприводит к ошибке компиляции. Это верно как для явного доступа (как в this.x), так и для неявного (как в x, где x является элементом экземпляра структуры). Это правило просто запрещает такой доступ и не влияет на то, приводит ли поиск элементов к члену структуры.
  • Тело имеет доступ к внешним переменным (§12.19.6) анонимной функции. Доступ к внешней переменной будет ссылаться на экземпляр переменной, который активен на момент вычисления lambda_expression или anonymous_method_expression (§12.19.7).
  • Ошибка времени компиляции возникает, если тело содержит оператор goto, оператор break или оператор continue, цель которого находится за пределами тела или внутри тела встроенной анонимной функции.
  • Оператор return в теле возвращает управление из вызова ближайшей замыкающей анонимной функции, а не из включающего элемента функции.

Явно не указано, существует ли способ выполнения блока анонимной функции кроме оценки и вызова lambda_expression или anonymous_method_expression. В частности, компилятор может реализовать анонимную функцию, синтезируя один или несколько именованных методов или типов. Имена всех синтезированных элементов должны иметь форму, зарезервированную для использования компилятором (§6.4.3).

Разрешение перегрузки 12.19.4

Анонимные функции в списке аргументов участвуют в выводе типов и разрешении перегрузки. См. §12.6.3 и §12.6.4 для точных правил.

Пример: В следующем примере показано влияние анонимных функций на разрешение перегрузок.

class ItemList<T> : List<T>
{
    public int Sum(Func<T, int> selector)
    {
        int sum = 0;
        foreach (T item in this)
        {
            sum += selector(item);
        }
        return sum;
    }

    public double Sum(Func<T, double> selector)
    {
        double sum = 0;
        foreach (T item in this)
        {
            sum += selector(item);
        }
        return sum;
    }
}

Класс ItemList<T> имеет два метода Sum. Каждый принимает аргумент selector, который извлекает значение из элемента списка для суммирования. Извлеченное значение может принимать значения int или double, а результирующая сумма аналогично может быть равна int или double.

Например, методы Sum можно использовать для вычисления сумм из списка строк с деталями в заказе.

class Detail
{
    public int UnitCount;
    public double UnitPrice;
    ...
}

class A
{
    void ComputeSums()
    {
        ItemList<Detail> orderDetails = GetOrderDetails( ... );
        int totalUnits = orderDetails.Sum(d => d.UnitCount);
        double orderTotal = orderDetails.Sum(d => d.UnitPrice * d.UnitCount);
        ...
    }

    ItemList<Detail> GetOrderDetails( ... )
    {
        ...
    }
}

При первом вызове orderDetails.Sumоба метода Sum применимы, так как анонимная функция d => d.UnitCount совместима как с Func<Detail,int>, так и с Func<Detail,double>. Однако разрешение перегрузки выбирает первый метод Sum, так как преобразование в Func<Detail,int> лучше, чем преобразование в Func<Detail,double>.

Во втором вызове orderDetails.Sumприменяется только второй метод Sum, так как анонимная функция d => d.UnitPrice * d.UnitCount создает значение типа double. Таким образом, разрешение перегрузки выбирает второй метод Sum для этого вызова.

конечный пример

12.19.5 Анонимные функции и динамическая привязка

Анонимная функция не может быть приемником, аргументом или операндом динамической операции.

12.19.6 Внешние переменные

12.19.6.1 General

Любая локальная переменная, параметр-значение или массив параметров, область которой включает lambda_expression или anonymous_method_expression, называется внешней переменной, анонимной функции. В члене функции экземпляра класса этот параметр считается параметром значения и является внешней переменной любой анонимной функции, содержащейся в члене функции.

12.19.6.2 Захваченные внешние переменные

Когда внешняя переменная ссылается на анонимную функцию, внешние переменные, как утверждается, были захвачены анонимной функцией. Обычно время существования локальной переменной ограничено выполнением блока или инструкции, с которой она связана (§9.2.9.1). Однако срок жизни захваченной внешней переменной продлевается до тех пор, пока делегат или дерево выражений, созданное из анонимной функции, не станет доступным для сборки мусора.

Пример: в примере

delegate int D();

class Test
{
    static D F()
    {
        int x = 0;
        D result = () => ++x;
        return result;
    }

    static void Main()
    {
        D d = F();
        Console.WriteLine(d());
        Console.WriteLine(d());
        Console.WriteLine(d());
    }
}

Локальная переменная x фиксируется анонимной функцией, и время существования x расширяется по крайней мере до тех пор, пока делегат не возвращается из F становится допустимым для сборки мусора. Так как каждый вызов анонимной функции работает на одном экземпляре x, результат данного примера:

1
2
3

конечный пример

Если локальная переменная или параметр значения фиксируется анонимной функцией, локальная переменная или параметр больше не считается фиксированной переменной (§23.4), но вместо этого считается перемещаемой переменной. Однако захваченные внешние переменные нельзя использовать в инструкции fixed (§23.7), поэтому адрес захваченной внешней переменной нельзя принять.

примечание. В отличие от неуправляемой переменной, захваченная локальная переменная может одновременно предоставляться нескольким потокам выполнения. конечная сноска

12.19.6.3 Инициализация локальных переменных

Локальная переменная считается создана, когда выполнение входит в область видимости переменной.

пример: например, при вызове следующего метода локальная переменная x создается и инициализируется три раза — один раз для каждой итерации цикла.

static void F()
{
    for (int i = 0; i < 3; i++)
    {
        int x = i * 2 + 1;
        ...
    }
}

Однако, если переместить объявление x за пределы цикла, это приведет к единственному созданию экземпляра x.

static void F()
{
    int x;
    for (int i = 0; i < 3; i++)
    {
        x = i * 2 + 1;
        ...
    }
}

конечный пример

Если не зафиксировано, нет способа точно наблюдать, как часто создается экземпляр локальной переменной; так как времена существования экземпляров не совпадают, каждый экземпляр может просто использовать одно и то же место хранения. Однако когда анонимная функция захватывает локальную переменную, эффекты инициализации становятся очевидными.

пример: пример

delegate void D();
class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        for (int i = 0; i < 3; i++)
        {
            int x = i * 2 + 1;
            result[i] = () => Console.WriteLine(x);
        }
        return result;
    }

    static void Main()
    {
        foreach (D d in F())
        {
            d();
        }
    }
}

создает выходные данные:

1
3
5

Однако при перемещении объявления x за пределы цикла:

delegate void D();

class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        int x;
        for (int i = 0; i < 3; i++)
        {
            x = i * 2 + 1;
            result[i] = () => Console.WriteLine(x);
        }
        return result;
   }

   static void Main()
   {
       foreach (D d in F())
       {
           d();
       }
   }
}

Выходные данные:

5
5
5

Обратите внимание, что компилятор разрешен (но не требуется) для оптимизации трех экземпляров в одном экземпляре делегата (§10.7.2).

конечный пример

Если цикл объявляет переменную итерации, то считается, что эта переменная объявлена вне цикла.

пример. Таким образом, если пример изменен для записи самой переменной итерации:

delegate void D();

class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        for (int i = 0; i < 3; i++)
        {
            result[i] = () => Console.WriteLine(i);
        }
        return result;
   }

   static void Main()
   {
       foreach (D d in F())
       {
           d();
       }
   }
}

Записывается только один экземпляр переменной итерации, который создает выходные данные:

3
3
3

конечный пример

Анонимные делегаты функций могут совместно использовать некоторые захваченные переменные, но имеют отдельные экземпляры других.

Пример: например, если F будет изменен на

static D[] F()
{
    D[] result = new D[3];
    int x = 0;
    for (int i = 0; i < 3; i++)
    {
        int y = 0;
        result[i] = () => Console.WriteLine($"{++x} {++y}");
    }
    return result;
}

Три делегата фиксируют один и тот же экземпляр x, но отдельные экземпляры y, и выходные данные следующие:

1 1
2 1
3 1

конечный пример

Отдельные анонимные функции могут захватывать тот же экземпляр внешней переменной.

пример: в примере:

delegate void Setter(int value);
delegate int Getter();

class Test
{
    static void Main()
    {
        int x = 0;
        Setter s = (int value) => x = value;
        Getter g = () => x;
        s(5);
        Console.WriteLine(g());
        s(10);
        Console.WriteLine(g());
    }
}

Две анонимные функции фиксируют один и тот же экземпляр локальной переменной x, и таким образом они могут "взаимодействовать" с этой переменной. Выходные данные примера:

5
10

конечный пример

12.19.7 Оценка анонимных выражений функций

Анонимная функция F всегда должна быть преобразована в тип делегата D или тип дерева выражений Eнапрямую или через выполнение выражения создания делегата new D(F). Это преобразование определяет результат анонимной функции, как описано в §10,7.

Пример реализации 12.19.8

Этот подпункт является информативным.

В этом подклаузе описывается возможная реализация анонимных преобразований функций с точки зрения других конструкций C#. Реализация, описанная здесь, основана на одних и том же принципах, используемых коммерческим компилятором C#, но это не является обязательной реализацией, и не является единственной возможной. Он лишь кратко упоминает преобразования в деревья выражений, так как их точная семантика находится вне области этой спецификации.

Оставшаяся часть этого подраздела содержит несколько примеров кода, содержащего анонимные функции с разными характеристиками. Для каждого примера предоставляется соответствующий перевод в код, использующий только другие конструкции C#. В примерах предполагается, что идентификатор D представляет следующий тип делегата:

public delegate void D();

Простейшая форма анонимной функции — это та, которая не захватывает переменные из внешней области видимости.

delegate void D();

class Test
{
    static void F()
    {
        D d = () => Console.WriteLine("test");
    }
}

Это можно преобразовать в экземпляр делегата, который ссылается на созданный компилятором статический метод, в котором помещается код анонимной функции:

delegate void D();

class Test
{
    static void F()
    {
        D d = new D(__Method1);
    }

    static void __Method1()
    {
        Console.WriteLine("test");
    }
}

В следующем примере анонимная функция ссылается на члены экземпляра this.

delegate void D();

class Test
{
    int x;

    void F()
    {
        D d = () => Console.WriteLine(x);
    }
}

Это можно преобразовать в созданный компилятором метод экземпляра, содержащий код анонимной функции:

delegate void D();

class Test
{
   int x;

   void F()
   {
       D d = new D(__Method1);
   }

   void __Method1()
   {
       Console.WriteLine(x);
   }
}

В этом примере анонимная функция записывает локальную переменную:

delegate void D();

class Test
{
    void F()
    {
        int y = 123;
        D d = () => Console.WriteLine(y);
    }
}

Время существования локальной переменной должно быть расширено по крайней мере до времени существования анонимного делегата функции. Это можно добиться путем поднятия локальной переменной в поле созданного компилятором класса. Создание экземпляра локальной переменной (§12.19.6.3) затем соответствует созданию экземпляра созданного компилятора класса, а доступ к локальной переменной соответствует доступу к полю в экземпляре созданного компилятора класса. Кроме того, анонимная функция становится методом экземпляра класса, созданного компилятором:

delegate void D();

class Test
{
    void F()
    {
        __Locals1 __locals1 = new __Locals1();
        __locals1.y = 123;
        D d = new D(__locals1.__Method1);
    }

    class __Locals1
    {
        public int y;

        public void __Method1()
        {
            Console.WriteLine(y);
        }
    }
}

Наконец, следующая анонимная функция записывает this, а также две локальные переменные с разными временем существования:

delegate void D();

class Test
{
   int x;

   void F()
   {
       int y = 123;
       for (int i = 0; i < 10; i++)
       {
           int z = i * 2;
           D d = () => Console.WriteLine(x + y + z);
       }
   }
}

Здесь создается класс, созданный компилятором, для каждого блока, в котором фиксируются локальные жители, так что локальные жители в разных блоках могут иметь независимое время существования. Экземпляр __Locals2, представляющий собой класс, созданный компилятором для внутреннего блока, содержит локальную переменную z и поле, которое ссылается на экземпляр __Locals1. Экземпляр __Locals1, созданный компилятором для внешнего блока, содержит локальную переменную y и поле, которое ссылается на this элемента включающей функции. С помощью этих структур данных можно достичь всех захваченных внешних переменных через экземпляр __Local2, а код анонимной функции таким образом можно реализовать как метод экземпляра этого класса.

delegate void D();

class Test
{
    int x;

    void F()
    {
        __Locals1 __locals1 = new __Locals1();
        __locals1.__this = this;
        __locals1.y = 123;
        for (int i = 0; i < 10; i++)
        {
            __Locals2 __locals2 = new __Locals2();
            __locals2.__locals1 = __locals1;
            __locals2.z = i * 2;
            D d = new D(__locals2.__Method1);
        }
    }

    class __Locals1
    {
        public Test __this;
        public int y;
    }

    class __Locals2
    {
        public __Locals1 __locals1;
        public int z;

        public void __Method1()
        {
            Console.WriteLine(__locals1.__this.x + __locals1.y + z);
        }
    }
}

Тот же метод, применяемый здесь для записи локальных переменных, также можно использовать при преобразовании анонимных функций в деревья выражений: ссылки на созданные компилятором объекты могут храниться в дереве выражений, а доступ к локальным переменным можно представить как доступ к полям на этих объектах. Преимущество этого подхода заключается в том, что он позволяет поднятым локальным переменным использоваться между делегатами и деревьями выражений.

конец информативного текста.

Выражения запросов 12.20

12.20.1 Общие

выражения запросов предоставляют синтаксис, интегрированный с языком для запросов, аналогичных реляционным и иерархическим языкам запросов, таким как SQL и XQuery.

query_expression
    : from_clause query_body
    ;

from_clause
    : 'from' type? identifier 'in' expression
    ;

query_body
    : query_body_clauses? select_or_group_clause query_continuation?
    ;

query_body_clauses
    : query_body_clause
    | query_body_clauses query_body_clause
    ;

query_body_clause
    : from_clause
    | let_clause
    | where_clause
    | join_clause
    | join_into_clause
    | orderby_clause
    ;

let_clause
    : 'let' identifier '=' expression
    ;

where_clause
    : 'where' boolean_expression
    ;

join_clause
    : 'join' type? identifier 'in' expression 'on' expression
      'equals' expression
    ;

join_into_clause
    : 'join' type? identifier 'in' expression 'on' expression
      'equals' expression 'into' identifier
    ;

orderby_clause
    : 'orderby' orderings
    ;

orderings
    : ordering (',' ordering)*
    ;

ordering
    : expression ordering_direction?
    ;

ordering_direction
    : 'ascending'
    | 'descending'
    ;

select_or_group_clause
    : select_clause
    | group_clause
    ;

select_clause
    : 'select' expression
    ;

group_clause
    : 'group' expression 'by' expression
    ;

query_continuation
    : 'into' identifier query_body
    ;

Выражение запроса начинается с предложения from и заканчивается предложением select или group. За начальным предложением from может следовать ноль или более from, let, where, join или orderby предложений. Каждое предложение from — это генератор, представляющий переменную диапазона , которая задаётся по элементам последовательности . Каждое предложение let представляет переменную диапазона, представляющую значение, вычисляемое с помощью предыдущих переменных диапазона. Каждое предложение where — это фильтр, который исключает элементы из результата. Каждое предложение join сравнивает указанные ключи исходной последовательности с ключами другой последовательности, что приводит к сопоставлению пар. Каждое предложение orderby переупорядочивает элементы в соответствии с указанными критериями. Последнее предложение select или group указывает форму результата в терминах переменных диапазона. Наконец, предложение into можно использовать для «объединения» запросов, обрабатывая результаты одного запроса как генератор в последующем запросе.

12.20.2 Неоднозначность в выражениях запросов

Выражения запросов используют ряд контекстных ключевых слов (§6.4.4): ascending, by, descending, equals, from, group, into, join, let, on, orderby, select и where.

Чтобы избежать неоднозначности, которые могут возникнуть из использования этих идентификаторов как ключевых слов, так и простых имен эти идентификаторы считаются ключевыми словами в любом месте выражения запроса, если только они не префиксированы с "@" (§6.4.4) в этом случае они считаются идентификаторами. Для этого выражение запроса — это любое выражение, начинающееся с "fromидентификатора", за которым следует любой маркер, кроме ";", "=" или ",".

Трансляция выражений запросов 12.20.3

12.20.3.1 Общие

Язык C# не указывает семантику выполнения выражений запросов. Вместо этого выражения запросов превратятся в вызовы методов, которые соответствуют шаблону выражения запроса (§12.20.4). В частности, выражения запросов преобразуются в вызовы методов с именем Where, Select, SelectMany, Join, GroupJoin, OrderBy, OrderByDescending, ThenBy, ThenByDescending, GroupByи Cast. Эти методы должны иметь определенные подписи и типы возвращаемых данных, как описано в §12.20.4. Эти методы могут быть методами экземпляра запрашиваемого объекта или методов расширения, которые являются внешними для объекта. Эти методы реализуют фактическое выполнение запроса.

Перевод выражений запросов на вызовы методов — это синтаксическое сопоставление, которое происходит до выполнения любой привязки типа или разрешения перегрузки. После перевода выражений запросов вызовы результирующего метода обрабатываются как регулярные вызовы методов, и это может в свою очередь выявить ошибки времени компиляции. К этим условиям ошибок относятся, но не ограничены, методы, которые не существуют, аргументы неправильных типов и универсальные методы, в которых вывод типов завершается сбоем.

Выражение запроса обрабатывается повторно, применяя следующие переводы, пока дальнейшие сокращения не будут возможны. Переводы перечислены в порядке выполнения: в каждом разделе предполагается, что переводы в предыдущих разделах были выполнены исчерпывающим образом, и если исчерпан, раздел в обработке одного и того же выражения запроса не будет пересматриваться впоследствии.

Это ошибка на этапе компиляции, если выражение запроса включает присваивание переменной диапазона или использование переменной диапазона в качестве аргумента для ссылочного или выходного параметра.

Некоторые переводы внедряют переменные диапазона с прозрачными идентификаторами , обозначенными *. Они описаны далее в §12.20.3.8.

12.20.3.2 Запросы с продолжением выражений

Выражение запроса с продолжением после его текста запроса

from «x1» in «e1» «b1» into «x2» «b2»

переводится на

from «x2» in ( from «x1» in «e1» «b1» ) «b2»

Переводы в следующих разделах предполагают, что запросы не имеют продолжения.

пример: пример:

from c in customers
group c by c.Country into g
select new { Country = g.Key, CustCount = g.Count() }

преобразуется в:

from g in
   (from c in customers
   group c by c.Country)
select new { Country = g.Key, CustCount = g.Count() }

окончательный перевод которого:

customers.
GroupBy(c => c.Country).
Select(g => new { Country = g.Key, CustCount = g.Count() })

конечный пример

12.20.3.3 Явные типы переменных диапазона

Предложение from, которое явно указывает тип диапазонной переменной

from «T» «x» in «e»

переводится на

from «x» in ( «e» ) . Cast < «T» > ( )

Предложение join, которое явно указывает тип переменной диапазона значений

join «T» «x» in «e» on «k1» equals «k2»

преобразуется в

join «x» in ( «e» ) . Cast < «T» > ( ) on «k1» equals «k2»

Переводы в следующих разделах предполагают, что запросы не имеют явных типов переменных диапазона.

пример: пример

from Customer c in customers
where c.City == "London"
select c

преобразуется в

from c in (customers).Cast<Customer>()
where c.City == "London"
select c

окончательный перевод которого

customers.
Cast<Customer>().
Where(c => c.City == "London")

конечный пример

Примечание. Типы переменных с явным указанием диапазона полезны для запросов коллекций, реализующих необобщённый интерфейс IEnumerable, а не обобщённый интерфейс IEnumerable<T>. В приведенном выше примере это может быть так, если клиенты были типа ArrayList. конечная сноска

12.20.3.4 Дегенерации выражений запросов

Выражение запроса формы

from «x» in «e» select «x»

переводится на

( «e» ) . Select ( «x» => «x» )

пример: пример

from c in customers
select c

переводится как

(customers).Select(c => c)

конечный пример

Вырожденное выражение запроса — это выражение, которое тривиально выбирает элементы источника.

Примечание. Последующие этапы перевода (§12.20.3.6 и §12.20.3.7) удаляют дегенерированные запросы, представленные другими шагами перевода, заменив их источником. Однако важно убедиться, что результат выражения запроса никогда не является исходным объектом. В противном случае возврат результата такого запроса может непреднамеренно предоставить конфиденциальные данные (например, массив данных) вызывающему объекту. Поэтому этот шаг защищает дегенерированные запросы, написанные непосредственно в исходном коде, явно вызывая Select в источнике. Ответственность за это лежит на тех, кто реализует Select и другие операторы запросов, чтобы гарантировать, что эти методы никогда не возвращают исходный объект. конечная сноска

12.20.3.5 Операторы from, let, where, join и orderby

Выражение запроса со вторым предложением from, за которым следует предложение select

from «x1» in «e1»  
from «x2» in «e2»  
select «v»

переводится на

( «e1» ) . SelectMany( «x1» => «e2» , ( «x1» , «x2» ) => «v» )

пример: пример

from c in customers
from o in c.Orders
select new { c.Name, o.OrderID, o.Total }

преобразуется в

(customers).
SelectMany(c => c.Orders,
(c,o) => new { c.Name, o.OrderID, o.Total }
)

конечный пример

Выражение запроса со вторым предложением from, за которым следует текст запроса Q, содержащий непустый набор предложений текста запроса:

from «x1» in «e1»
from «x2» in «e2»
Q

преобразуется в

from * in («e1») . SelectMany( «x1» => «e2» ,
                              ( «x1» , «x2» ) => new { «x1» , «x2» } )
Q

пример: пример

from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }

переводится на

from * in (customers).
   SelectMany(c => c.Orders, (c,o) => new { c, o })
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }

окончательный перевод которого

customers.
SelectMany(c => c.Orders, (c,o) => new { c, o }).
OrderByDescending(x => x.o.Total).
Select(x => new { x.c.Name, x.o.OrderID, x.o.Total })

где x — это созданный компилятором идентификатор, который в противном случае невидим и недоступен.

конечный пример

Выражение let и его предыдущая часть from:

from «x» in «e»  
let «y» = «f»  
...

переводится на

from * in ( «e» ) . Select ( «x» => new { «x» , «y» = «f» } )  
...

пример: пример

from o in orders
let t = o.Details.Sum(d => d.UnitPrice * d.Quantity)
where t >= 1000
select new { o.OrderID, Total = t }

преобразуется в

from * in (orders).Select(
    o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) })
where t >= 1000
select new { o.OrderID, Total = t }

окончательный перевод которого

orders
    .Select(o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) })
    .Where(x => x.t >= 1000)
    .Select(x => new { x.o.OrderID, Total = x.t })

где x — это созданный компилятором идентификатор, который в противном случае невидим и недоступен.

конечный пример

Выражение where с предшествующим ему положением from:

from «x» in «e»  
where «f»  
...

преобразуется в

from «x» in ( «e» ) . Where ( «x» => «f» )  
...

Предложение join, за которым сразу следует предложение select

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2»  
select «v»

переводится на

( «e1» ) . Join( «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «x2» ) => «v» )

пример: пример

from c in customersh
join o in orders on c.CustomerID equals o.CustomerID
select new { c.Name, o.OrderDate, o.Total }

преобразуется в

(customers).Join(
   orders,
   c => c.CustomerID, o => o.CustomerID,
   (c, o) => new { c.Name, o.OrderDate, o.Total })

конечный пример

Предложение join, за которым следует предложение текста запроса:

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2»  
...

переводится на

from * in ( «e1» ) . Join(  
«e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «x2» ) => new { «x1» , «x2» })  
...

Клаузула join-into непосредственно следующее за клаузулой select

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2» into «g»  
select «v»

преобразуется в

( «e1» ) . GroupJoin( «e2» , «x1» => «k1» , «x2» => «k2» ,
                     ( «x1» , «g» ) => «v» )

Предложение join into, за которым следует предложение текста запроса

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2» into *g»  
...

переводится на

from * in ( «e1» ) . GroupJoin(  
   «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «g» ) => new { «x1» , «g» })
...

пример: пример

from c in customers
join o in orders on c.CustomerID equals o.CustomerID into co
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n }

переводится на

from * in (customers).GroupJoin(
    orders,
    c => c.CustomerID,
    o => o.CustomerID,
    (c, co) => new { c, co })
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n }

который является окончательным переводом

customers
    .GroupJoin(
        orders,
        c => c.CustomerID,
        o => o.CustomerID,
        (c, co) => new { c, co })
    .Select(x => new { x, n = x.co.Count() })
    .Where(y => y.n >= 10)
    .Select(y => new { y.x.c.Name, OrderCount = y.n })

где x и y представляют собой созданные компилятором идентификаторы, которые в противном случае невидимы и недоступны.

конечный пример

Предложение orderby и предыдущее предложение from:

from «x» in «e»  
orderby «k1» , «k2» , ... , «kn»  
...

преобразуется в

from «x» in ( «e» ) .
OrderBy ( «x» => «k1» ) .
ThenBy ( «x» => «k2» ) .
... .
ThenBy ( «x» => «kn» )
...

Если оператор ordering указывает индикатор убывающего направления, вместо этого выполняется вызов OrderByDescending или ThenByDescending.

пример: пример

from o in orders
orderby o.Customer.Name, o.Total descending
select o

имеет окончательный перевод

(orders)
    .OrderBy(o => o.Customer.Name)
    .ThenByDescending(o => o.Total)

конечный пример

В следующих переводах предполагается, что в каждом выражении запроса нет предложений let, where, join или orderby, и имеется не более одного начального предложения from.

Выбор разделов 12.20.3.6

Выражение запроса формы

from «x» in «e» select «v»

переводится на

( «e» ) . Select ( «x» => «v» )

за исключением случаев, когда «v» является идентификатором «x», перевод становится простым

( «e» )

пример: пример

from c in customers.Where(c => c.City == "London")
select c

просто преобразуется в

(customers).Where(c => c.City == "London")

конечный пример

Положения группы 12.20.3.7

Пункт group

from «x» in «e» group «v» by «k»

переводится как

( «e» ) . GroupBy ( «x» => «k» , «x» => «v» )

за исключением случаев, когда «v» является идентификатором «x», перевод выполняется

( «e» ) . GroupBy ( «x» => «k» )

пример: пример

from c in customers
group c.Name by c.Country

переводится на

(customers).GroupBy(c => c.Country, c => c.Name)

конечный пример

12.20.3.8 Прозрачные идентификаторы

Некоторые переводы внедряют переменные диапазона с прозрачными идентификаторами, обозначаемыми *. Прозрачные идентификаторы существуют только в качестве промежуточного шага в процессе перевода выражений запроса.

При переводе запросов вставляет прозрачный идентификатор, дальнейшие шаги перевода распространяют прозрачный идентификатор в анонимные функции и инициализаторы анонимных объектов. В этих контекстах прозрачные идентификаторы имеют следующее поведение:

  • Когда прозрачный идентификатор используется в качестве параметра в анонимной функции, члены связанного анонимного типа автоматически доступны в теле анонимной функции.
  • Если член с прозрачным идентификатором находится в области видимости, то члены этого члена также находятся в области видимости.
  • Когда прозрачный идентификатор возникает как декларатор-член в анонимном инициализаторе объектов, он вводит элемент с прозрачным идентификатором.

В описанных выше шагах перевода прозрачные идентификаторы всегда представлены вместе с анонимными типами с намерением захвата нескольких переменных диапазона в качестве членов одного объекта. Реализация C# позволяет использовать другой механизм, отличный от анонимных типов для группировки нескольких переменных диапазона. В следующих примерах перевода предполагается, что используются анонимные типы и показаны возможные переводы прозрачных идентификаторов.

пример: пример

from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.Total }

переводится на

from * in (customers).SelectMany(c => c.Orders, (c,o) => new { c, o })
orderby o.Total descending
select new { c.Name, o.Total }

который далее переводится в

customers
    .SelectMany(c => c.Orders, (c,o) => new { c, o })
    .OrderByDescending(* => o.Total)
    .Select(\* => new { c.Name, o.Total })

что, когда прозрачные идентификаторы удаляются, эквивалентно

customers
    .SelectMany(c => c.Orders, (c,o) => new { c, o })
    .OrderByDescending(x => x.o.Total)
    .Select(x => new { x.c.Name, x.o.Total })

где x — это созданный компилятором идентификатор, который в противном случае невидим и недоступен.

Пример

from c in customers
join o in orders on c.CustomerID equals o.CustomerID
join d in details on o.OrderID equals d.OrderID
join p in products on d.ProductID equals p.ProductID
select new { c.Name, o.OrderDate, p.ProductName }

переводится на

from * in (customers).Join(
    orders,
    c => c.CustomerID,
    o => o.CustomerID,
    (c, o) => new { c, o })
join d in details on o.OrderID equals d.OrderID
join p in products on d.ProductID equals p.ProductID
select new { c.Name, o.OrderDate, p.ProductName }

что еще больше сокращается до

customers
    .Join(orders, c => c.CustomerID,
        o => o.CustomerID, (c, o) => new { c, o })
    .Join(details, * => o.OrderID, d => d.OrderID, (*, d) => new { *, d })
    .Join(products, * => d.ProductID, p => p.ProductID,
        (*, p) => new { c.Name, o.OrderDate, p.ProductName })

перевод которого является окончательным

customers
    .Join(orders, c => c.CustomerID,
        o => o.CustomerID, (c, o) => new { c, o })
    .Join(details, x => x.o.OrderID, d => d.OrderID, (x, d) => new { x, d })
    .Join(products, y => y.d.ProductID, p => p.ProductID,
        (y, p) => new { y.x.c.Name, y.x.o.OrderDate, p.ProductName })

где x и y являются идентификаторами, созданными компилятором, которые в противном случае невидимы и недоступны. конечный пример

12.20.4 Шаблон выражения запроса

Шаблон выражения запроса устанавливает шаблон методов, которые типы могут реализовать для поддержки выражений запросов.

Универсальный тип C<T> поддерживает шаблон выражения запроса, если его открытые методы-члены и общедоступные методы расширения могут быть заменены следующим определением класса. Члены и доступные методы расширения называются «структурой» универсального типа C<T>. Универсальный тип используется для иллюстрации соответствующих связей между параметрами и возвращаемыми типами, но также можно реализовать шаблон для не универсальных типов.

delegate R Func<T1,R>(T1 arg1);
delegate R Func<T1,T2,R>(T1 arg1, T2 arg2);

class C
{
    public C<T> Cast<T>() { ... }
}

class C<T> : C
{
    public C<T> Where(Func<T,bool> predicate) { ... }
    public C<U> Select<U>(Func<T,U> selector) { ... }
    public C<V> SelectMany<U,V>(Func<T,C<U>> selector,
        Func<T,U,V> resultSelector) { ... }
    public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
        Func<U,K> innerKeySelector, Func<T,U,V> resultSelector) { ... }
    public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
        Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector) { ... }
    public O<T> OrderBy<K>(Func<T,K> keySelector) { ... }
    public O<T> OrderByDescending<K>(Func<T,K> keySelector) { ... }
    public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector) { ... }
    public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector,
        Func<T,E> elementSelector) { ... }
}

class O<T> : C<T>
{
    public O<T> ThenBy<K>(Func<T,K> keySelector) { ... }
    public O<T> ThenByDescending<K>(Func<T,K> keySelector) { ... }
}

class G<K,T> : C<T>
{
    public K Key { get; }
}

Приведенные выше методы используют универсальные типы делегатов Func<T1, R> и Func<T1, T2, R>, но они могли бы в равной степени использовать другие типы делегатов или дерева выражений с теми же связями в параметрах и возвращаемых типах.

Примечание. Рекомендуемая связь между C<T> и O<T>, которая гарантирует, что методы ThenBy и ThenByDescending доступны только в результате OrderBy или OrderByDescending. конечная сноска

Примечание: рекомендуемая форма результата GroupBy— последовательность последовательностей, где каждая внутренняя последовательность имеет дополнительное свойство Key. конечная сноска

Примечание. Поскольку выражения запросов переводятся в вызовы методов с помощью синтактического сопоставления, типы имеют значительную гибкость в том, как они реализуют любой или весь шаблон выражения запроса. Например, методы шаблона могут быть реализованы как методы экземпляра или как методы расширения, так как два имеют одинаковый синтаксис вызова, а методы могут запрашивать делегаты или деревья выражений, так как анонимные функции преобразуются в оба. Типы, реализующие только некоторые из шаблонов выражений запроса, поддерживают только переводы выражений запросов, которые сопоставляют с методами, поддерживаемыми типом. конец заметки

примечание. Пространство имен System.Linq предоставляет реализацию шаблона выражения запроса для любого типа, реализующего интерфейс System.Collections.Generic.IEnumerable<T>. конечная сноска

Операторы назначения 12.21

12.21.1 Общие

Все, кроме одного из операторов назначения, присваивают новое значение переменной, свойству, событию или элементу индексатора. Исключение, = ref, назначает ссылку на переменную (§9.5) ссылочной переменной (§9,7).

assignment
    : unary_expression assignment_operator expression
    ;

assignment_operator
    : '=' 'ref'? | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<='
    | right_shift_assignment
    ;

Левый операнд операции присваивания должен быть выражением, классифицируемым как переменная или, за исключением = ref, доступом к свойству, доступом индексатора, доступом к событию или кортежем. Выражение объявления не допускается напрямую в качестве левого операнда, но может происходить как шаг в оценке деконструкционного назначения.

Оператор = называется простым оператором назначения. Она присваивает значение или значения правого операнда переменной, свойству, элементу индексатора или элементам кортежа, указанным левым операндом. Левый операнд простого оператора присваивания не должен быть доступом к событиям (за исключением случаев, описанных в §15.8.2). Простой оператор назначения описан в §12.21.2.

Оператор присваивания ссылок = ref называется оператором. Правый операнд, который должен быть variable_reference (§9,5), становится референтом переменной ссылки, назначенной левым операндом. Оператор назначения ссылок описан в §12.21.3.

Операторы назначения, которые не являются операторами = и = ref, называются составными операторами присваивания . Эти операторы выполняют указанную операцию на двух операндах, а затем присваивают результирующее значение переменной, свойству или индексатору, заданному левым операндом. Операторы составного назначения описаны в §12.21.4.

Операторы += и -= с выражением доступа к событиям в качестве левого операнда называются операторами назначения событий . Ни один другой оператор присваивания не является допустимым при использовании доступа к событию в качестве левого операнда. Операторы назначения событий описаны в §12.21.5.

Операторы присваивания являются правоассоциативными, то есть операции группируются справа налево.

пример: выражение вида a = b = c оценивается как a = (b = c). конечный пример

12.21.2 Простое назначение

Оператор = называется простым оператором присваивания.

Если левый операнд простого задания имеет форму E.P или E[Ei], где E имеет тип времени компиляции dynamic, назначение динамически привязано (§12.3.3). В этом случае тип выражения присваивания во время компиляции — это dynamic, а процедура, описанная ниже, будет выполняться во время выполнения на основе типа E. Если левый операнд имеет форму E[Ei], где хотя бы один элемент Ei имеет тип времени компиляции dynamic, а тип времени компиляции E не является массивом, результирующий доступ индексатора динамически привязан, но с ограниченной проверкой времени компиляции (§12.6.5).

Простое задание, в котором левый операнд классифицируется как кортеж, также называется деконструкция назначения. Если любой из элементов кортежа левого операнда имеет имя элемента, возникает ошибка во время компиляции. Если любой из элементов кортежа левого операнда является declaration_expression, а любой другой элемент не является declaration_expression или простой отменой, возникает ошибка во время компиляции.

Тип простого присваивания x = y — это тип присваивания для x элемента y, который определяется рекурсивно следующим образом:

  • Если x является выражением кортежа (x1, ..., xn), и y может быть деконструировано в выражение кортежа (y1, ..., yn) с элементами n (§12.7), и каждое присваивание xiyi имеет тип Ti, то присваивание будет иметь тип (T1, ..., Tn).
  • В противном случае, если x классифицируется как переменная, переменная не readonly, x имеет тип T, а y имеет неявное преобразование в T, то назначение имеет тип T.
  • В противном случае, если x классифицируется как неявно типизированная переменная (т. е. неявно типизированное выражение объявления), и при этом y имеет тип T, то выведенный тип переменной - T, а операция присваивания имеет тип T.
  • В противном случае, если x классифицируется как свойство или доступ индексатора, свойство или индексатор имеет доступный метод доступа к набору, x имеет тип T, а y имеет неявное преобразование в T, то назначение имеет тип T.
  • В противном случае присваивание недопустимо и возникает ошибка времени привязки.

Обработка простого назначения формы x = y с типом T выполняется в качестве назначения xy с типом T, которая состоит из следующих рекурсивных шагов:

  • x оценивается, если этого еще не было.
  • Если x классифицируется как переменная, y вычисляется и при необходимости преобразуется в T путем неявного преобразования (§10.2).
    • Если переменная, указанная x является элементом массива reference_type, выполняется проверка во время выполнения, чтобы убедиться, что значение, вычисленное для y, совместимо с экземпляром массива, в котором x является элементом. Проверка завершается успешно, если y равно null, или если существует неявное преобразование ссылки (§10.2.8) из типа экземпляра, на который ссылается y, в фактический тип элемента экземпляра массива, который содержит x. В противном случае выбрасывается System.ArrayTypeMismatchException.
    • Значение, полученное в результате оценки и преобразования y, сохраняется в месте, определённом оценкой x, и возвращается как результат присваивания.
  • Если x классифицируется как свойство или доступ индексатора:
    • y вычисляется и при необходимости преобразуется в T путем неявного преобразования (§10.2).
    • Устанавливающий модификатор x вызывается с аргументом, представляющим собой значение, полученное в результате вычисления и преобразования y.
    • Значение, полученное в результате оценки и преобразования y, возвращается как результат присваивания.
  • Если x классифицируется как кортеж (x1, ..., xn) с арностью n:
    • y деконструируется на элементы n в кортежное выражение e.
    • Результирующий кортеж t создаётся преобразованием e в T с использованием неявного преобразования кортежей.
    • для каждого xi слева направо выполняется назначение xit.Itemi, за исключением того, что xi не вычисляются повторно.
    • t возвращается в результате операции присваивания.

Примечание: если тип времени компиляции x соответствует dynamic и существует неявное преобразование от типа времени компиляции y к dynamic, разрешение на этапе выполнения не требуется. концевая сноска

Примечание. Правила совместного изменения массива (§17.6) позволяют значению типа массива A[] являться ссылкой на экземпляр типа массива B[], при условии, что существует неявное преобразование ссылок от B к A. Из-за этих правил назначение элементу массива reference_type требует проверки времени выполнения, чтобы убедиться, что назначенное значение совместимо с экземпляром массива. В примере

string[] sa = new string[10];
object[] oa = sa;
oa[0] = null;              // OK
oa[1] = "Hello";           // OK
oa[2] = new ArrayList();   // ArrayTypeMismatchException

Последнее назначение приводит к выбрасыванию System.ArrayTypeMismatchException, поскольку ссылка на ArrayList не может храниться в элементе string[].

конечная сноска

Если свойство или индексатор, объявленные в struct_type, является целью назначения, выражение экземпляра, связанное со свойством или доступом индексатора, должно быть классифицировано как переменная. Если выражение экземпляра классифицируется как значение, возникает ошибка во время привязки.

Примечание: из-за §12.8.7то же правило применяется к полям. концевая сноска

Пример: с учетом объявлений:

struct Point
{
   int x, y;

   public Point(int x, int y)
   {
      this.x = x;
      this.y = y;
   }

   public int X
   {
      get { return x; }
      set { x = value; }
   }

   public int Y {
      get { return y; }
      set { y = value; }
   }
}

struct Rectangle
{
    Point a, b;

    public Rectangle(Point a, Point b)
    {
        this.a = a;
        this.b = b;
    }

    public Point A
    {
        get { return a; }
        set { a = value; }
    }

    public Point B
    {
        get { return b; }
        set { b = value; }
    }
}

в примере

Point p = new Point();
p.X = 100;
p.Y = 100;
Rectangle r = new Rectangle();
r.A = new Point(10, 10);
r.B = p;

Назначения для p.X, p.Y, r.Aи r.B разрешены, так как p и r являются переменными. Однако в примере

Rectangle r = new Rectangle();
r.A.X = 10;
r.A.Y = 10;
r.B.X = 100;
r.B.Y = 100;

Присваивания являются недопустимыми, так как r.A и r.B не являются переменными.

конечный пример

Назначение ссылок 12.21.3

Оператор = ref называется оператором назначения ссылок.

Левая операнда должна быть выражением, которое привязывается к эталонной переменной (§9.7), ссылочным параметром (кроме this), выходным параметром или входным параметром. Правый операнд должен быть выражением, которое дает variable_reference, ссылающееся на значение того же типа, что и левый операнд.

Это ошибка времени компиляции, если ref-safe-context (§9.7.2) левого операнда шире, чем ref-safe-context правого операнда.

Правый операнд должен быть определенно назначен в момент операции присваивания с использованием ref.

Если левый операнд привязывается к выходному параметру, это ошибка, если выходной параметр не был определенно назначен в начале оператора назначения ссылок.

Если левый операнд является записываемой ссылкой (т. е. он обозначает что-либо, отличное от 'ref readonly' локального или входного параметра), правый операнд должен быть представлен как записываемая 'variable_reference'. Если правая переменная операнда доступна для записи, левый операнд может быть записью или ссылкой только для чтения.

Операция делает левый операнд псевдонимом правой переменной операнда. Псевдоним может быть доступен только для чтения, даже если правильная переменная операнда доступна для записи.

Оператор присваивания ссылки возвращает variable_reference назначенного типа. Это доступно для записи, если левый операнд доступен для записи.

Оператор присваивания ссылок не должен считывать местоположение памяти, которому ссылается правый операнд.

пример. Ниже приведены некоторые примеры использования = ref:

public static int M1() { ... }
public static ref int M2() { ... }
public static ref uint M2u() { ... }
public static ref readonly int M3() { ... }
public static void Test()
{
int v = 42;
ref int r1 = ref v; // OK, r1 refers to v, which has value 42
r1 = ref M1();      // Error; M1 returns a value, not a reference
r1 = ref M2();      // OK; makes an alias
r1 = ref M2u();     // Error; lhs and rhs have different types
r1 = ref M3();    // error; M3 returns a ref readonly, which r1 cannot honor
ref readonly int r2 = ref v; // OK; make readonly alias to ref
r2 = ref M2();      // OK; makes an alias, adding read-only protection
r2 = ref M3();      // OK; makes an alias and honors the read-only
r2 = ref (r1 = ref M2());  // OK; r1 is an alias to a writable variable,
              // r2 is an alias (with read-only access) to the same variable
}

конечный пример

Примечание. При чтении кода с использованием оператора = ref может возникнуть соблазн воспринимать ref часть как часть операнда. Это особенно запутано, когда операнд является условным выражением ?:. Например, при чтении ref int a = ref b ? ref x : ref y; важно понимать, что = ref — это оператор, а b ? ref x : ref y — это правый операнд: ref int a = ref (b ? ref x : ref y);. Важно, что выражение ref bне является частью этого утверждения, даже если это может показаться так на первый взгляд. конечная сноска

12.21.4 Комбинированное присваивание

Если левый операнд составного назначения имеет форму E.P или E[Ei], где E имеет тип времени компиляции dynamic, назначение динамически привязано (§12.3.3). В этом случае тип времени компиляции выражения назначения dynamic, а разрешение, описанное ниже, будет выполняться во время выполнения на основе типа времени выполнения E. Если левый операнд имеет форму E[Ei], где хотя бы один элемент Ei имеет тип времени компиляции dynamic, а тип времени компиляции E не является массивом, результирующий доступ индексатора динамически привязан, но с ограниченной проверкой времени компиляции (§12.6.5).

Операция формы x «op»= y обрабатывается путем применения разрешения перегрузки двоичного оператора (§12.4.5), так, как если бы операция записывалась x «op» y. Тогда

  • Если возвращаемый тип выбранного оператора неявно преобразуется в тип x, операция оценивается как x = x «op» y, за исключением того, что x вычисляется только один раз.
  • В противном случае, если выбранный оператор является предопределенным оператором, если тип возврата выбранного оператора явно преобразуется в тип x, а если y неявно преобразуется в тип x или оператор является оператором shift, то операция вычисляется как x = (T)(x «op» y), где T является типом x, за исключением того, что x оценивается только один раз.
  • В противном случае составная операция присваивания недопустима, и возникает ошибка времени привязки.

Термин "оценен только один раз" означает, что при оценке x «op» yрезультаты любых составляющих выражений x временно сохраняются, а затем повторно используются при выполнении задания для x.

пример: В A()[B()] += C()присваивании, где метод A возвращает int[], а B и C — методы, возвращающие int, методы вызываются только один раз, в порядке A, B, C. конечный пример

Если левый операнд составного присваивания является доступом к свойству или индексатором, то у свойства или индексатора должны быть как геттер, так и сеттер. Если это не так, возникает ошибка во время привязки.

Второе правило, приведенное выше, позволяет x «op»= y оцениваться как x = (T)(x «op» y) в определенных контекстах. Правило существует таким образом, что предопределенные операторы можно использовать в качестве составных операторов, если левый операнд имеет тип sbyte, byte, short, ushortили char. Даже если оба аргумента являются одним из этих типов, предопределенные операторы создают результат типа int, как описано в §12.4.7.3. Таким образом, без приведения невозможно назначить результат левому операнду.

Интуитивно понятный эффект правила для предопределенных операторов заключается в том, что x «op»= y разрешено, если разрешены оба x «op» y и x = y.

пример: в следующем коде

byte b = 0;
char ch = '\0';
int i = 0;
b += 1;           // OK
b += 1000;        // Error, b = 1000 not permitted
b += i;           // Error, b = i not permitted
b += (byte)i;     // OK
ch += 1;          // Error, ch = 1 not permitted
ch += (char)1;    // OK

Интуитивно понятной причиной каждой ошибки является то, что соответствующее простое назначение также было бы ошибкой.

конечный пример

Примечание. Это также означает, что сложные операции назначения поддерживают снятые операторы. Поскольку составное присваивание x «op»= y оценивается как x = x «op» y или x = (T)(x «op» y), правила оценки неявно охватывают поднятые операторы. конечная заметка

Назначение событий 12.21.5

Если левый операнд оператора a += or -= классифицируется как доступ к событиям, выражение вычисляется следующим образом:

  • Если выражение экземпляра существует, оно оценивается в контексте доступа к событию.
  • Вычисляется правый операнд оператора += или -=, а при необходимости преобразуется в тип левого операнда путем неявного преобразования (§10.2).
  • Вызывается аксессор события, со списком аргументов, состоящим из значения, вычисленного на предыдущем шаге. Если оператор был +=, вызывается метод доступа к добавлению; Если оператор был -=, вызывается средство доступа к удалению.

Выражение назначения событий не дает значения. Таким образом, выражение назначения событий допустимо только в контексте statement_expression (§13.7).

Выражение 12.22

Выражение — это выражение без присваивания или присваивание.

expression
    : non_assignment_expression
    | assignment
    ;

non_assignment_expression
    : declaration_expression
    | conditional_expression
    | lambda_expression
    | query_expression
    ;

12.23 Константные выражения

Константное выражение — это выражение, которое должно быть полностью оценено во время компиляции.

constant_expression
    : expression
    ;

Константное выражение должно иметь значение null или один из следующих типов:

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, string;
  • тип перечисления; или
  • Выражение значения по умолчанию (§12.8.21) для ссылочного типа.

В константных выражениях разрешены только следующие конструкции:

  • Литералы (включая литерал null).
  • Ссылки на const члены классов и типов структур.
  • Ссылки на элементы типов перечисления.
  • Ссылки на локальные константы.
  • Обрамленные в скобки подвыражения, которые сами по себе являются константными выражениями.
  • Выражения приведения.
  • выражения checked и unchecked.
  • выражения nameof.
  • Предопределённые унарные операторы +, -, ! (логическое отрицание) и ~.
  • Предопределенные двоичные операторы +, -, *, /, %, <<, >>, &, |, ^, &&, ||, ==, !=, <, >, <=и >=.
  • Оператор ?: условный.
  • Оператор ! null-forgiving (§12.8.9).
  • выражения sizeof, если неуправляемый тип является одним из типов, указанных в §23.6.9, для которых sizeof возвращает константное значение.
  • Выражения значений по умолчанию, если тип является одним из типов, перечисленных выше.

Следующие преобразования разрешены в константных выражениях:

  • Идентификационные преобразования
  • Числовые преобразования
  • Преобразование перечислений
  • Преобразования константных выражений
  • Неявные и явные преобразования ссылок возможны, если источник преобразований — это константное выражение, оцениваемое в значение null.

Примечание. Другие преобразования, включая боксинг, распаковку и неявные ссылочные преобразования значений, отличных отnull, не разрешены в константных выражениях. конечная сноска

Пример: в следующем коде

class C
{
    const object i = 5;         // error: boxing conversion not permitted
    const object str = "hello"; // error: implicit reference conversion
}

Инициализация i является ошибкой, так как требуется преобразование бокса. Инициализация str является ошибкой, так как требуется неявное преобразование ссылочной переменной из значения, не являющегосяnull.

конечный пример

Всякий раз, когда выражение соответствует приведенным выше требованиям, выражение вычисляется во время компиляции. Это верно, даже если выражение является вложенным выражением большего выражения, содержащего неконстантные конструкции.

При вычислении константных выражений на этапе компиляции используются те же правила, что и при оценке неконстантных выражений во время выполнения, за исключением того, что если при оценке во время выполнения возникло бы исключение, оценка на этапе компиляции приводит к ошибке времени компиляции.

Если константное выражение явно не помещается в контекст unchecked, переполнения, происходящие при выполнении арифметических операций и преобразований целочисленного типа во время компиляции выражения, всегда приводят к ошибкам времени компиляции (§12.8.20).

Выражения констант требуются в контекстах, перечисленных ниже, и это указано в грамматике с помощью constant_expression. В этих контекстах ошибка во время компиляции возникает, если выражение не может быть полностью оценено во время компиляции.

  • Объявления констант (§15.4)
  • Объявления членов перечисления (§19.4)
  • Аргументы списков параметров по умолчанию (§15.6.2)
  • case метки инструкции switch (§13.8.3).
  • инструкции goto case (§13.10.4)
  • Длины измерений в выражении создания массива (§12.8.17.5), включающего инициализатор.
  • Атрибуты (§22)
  • В constant_pattern (§11.2.3)

Неявное преобразование константного выражения (§10.2.11) позволяет преобразовать константное выражение типа int в sbyte, byte, short, ushort, uintили ulong, если значение константного выражения находится в диапазоне конечного типа.

12.24 Логические выражения

boolean_expression — это выражение, которое даёт результат типа bool, либо напрямую, либо путём применения operator true в определённых контекстах, как указано ниже.

boolean_expression
    : expression
    ;

Управляющее условное выражение if_statement (§13.8.2), while_statement (§13.9.2), do_statement (§13.9.3) или for_statement (§13.9.4) — это boolean_expression. Управляемое условное выражение оператора ?: (§12.18) следует тем же правилам, что и boolean_expression, но по соображениям приоритета оператора классифицируется как null_coalescing_expression.

Для создания значения типа boolтребуется boolean_expressionE, как показано ниже.

  • Если E неявно преобразуется в bool, то во время выполнения применяется неявное преобразование.
  • В противном случае разрешение перегрузки унарного оператора (§12.4.4) используется для поиска уникальной реализации operator true на E, и эта реализация применяется во время выполнения.
  • Если такой оператор не найден, возникает ошибка во время привязки.