Предложения 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, за которым следует предложение, отличное от select,

from x1 in e1
from x2 in e2

переводится в

from * in ( e1 ) . SelectMany( x1 => e2 , ( x1 , x2 ) => new { x1 , x2 } )

Выражение запроса с предложением let

from x in e
let y = f

переводится в

from * in ( e ) . Select ( x => new { x , y = f } )

Выражение запроса с предложением where

from x in e
where f

переводится в

from x in ( e ) . Where ( x => f )

Выражение запроса с предложением join без ключевого слова into, за которым следует предложение 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 )

Выражение запроса с предложением join без ключевого слова into, за которым следуем предложение, отличное от select

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, за которым следуем предложение, отличное от select

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 })

Выражение запроса с предложением orderby

from x in e
orderby k1 , k2 , … , kn

переводится в

from x in ( e ) .
OrderBy ( x => k1 ) .
ThenBy ( x => k2 ) .
… .
ThenBy ( x => kn )

Если в предложении упорядочения указывается индикатор направления descending, вместо него вызывается метод OrderByDescending или ThenByDescending.

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

Пример:

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 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 — идентификатор, созданный компилятором, который в других условиях является невидимым и недоступным.

Пример:

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 — идентификатор, созданный компилятором, который в других условиях является невидимым и недоступным.

Пример:

from c in customers
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 })

Пример:

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 — идентификаторы, созданные компилятором, которые в других условиях являются невидимыми и недоступными.

Пример:

from o in orders
orderby o.Customer.Name, o.Total descending
select o

имеет конечный перевод

orders.
OrderBy(o => o.Customer.Name).
ThenByDescending(o => o.Total)

Предложения select

Выражение запроса вида

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”)

Предложения groupby

Выражение запроса вида

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)

Прозрачные идентификаторы

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

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

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

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

· Когда прозрачный идентификатор оказывается в роли декларатора члена в инициализаторе анонимного объекта, он создает член с прозрачным идентификатором.

На этапах перевода, описанных выше, прозрачные идентификаторы всегда создаются вместе с анонимными типами с намерением сохранить несколько переменных диапазона в виде членов одного объекта. В реализации 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 { *, p }).
Select(* => 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, p }).
Select(z => new { z.y.x.c.Name, z.y.x.o.OrderDate, z.p.ProductName })

где x? y и z — идентификаторы, созданные компилятором, которые в других условиях являются невидимыми и недоступными.

Шаблон выражения запроса

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

Ниже представлен рекомендуемый формат универсального типа 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>.

Операторы присваивания

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

assignment:
unary-expression assignment-operator expression

assignment-operator:
=
+=
-=
*=
/=
%=
&=
|=
^=
<<=
right-shift-assignment

Левый операнд присваивания должен быть выражением с классом переменной, доступа к свойству, доступа к индексатору или доступа к событию.

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

Операторы присваивания, отличные от оператора =, называются сложными операторами присваивания. Эти операторы выполняют указанную операцию для двух операндов, а затем присваивают результирующее значение переменной, свойству или элементу индексатора, представленному левым операндом. Сложные операторы присваивания описываются в разделе §7.17.2.

Операторы += и -= с выражением доступа к событию в качестве левого операнда называются операторами присвоения события.Вместе с выражением доступа к событию в качестве левого операнда не разрешается использовать ни один другой оператор присвоения. Операторы присваивания события описываются в разделе §7.17.3.

Операторы присваивания имеют правую ассоциативность; это означает, что операции группируются справа налево. Например, выражение вида a = b = c вычисляется как a = (b = c).

Простое присваивание

Оператор = называется простым оператором присваивания.

Если левый операнд выражения простого присвоения имеет форму E.P или E[Ei], где E имеет тип времени компиляции dynamic, то операция присвоения динамически привязывается (§7.2.2). В этом случае выражение присваивания имеет тип времени компиляции dynamic, а разрешение, приведенное ниже, будет иметь место во время выполнения на основе типа E времени выполнения.

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

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

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

Во время выполнения обработка простого присваивания вида x = y включает следующие этапы.

· Если x классифицируется как переменная, то:

o x вычисляется для создания переменной.

o Вычисляется y и при необходимости преобразуется в тип x с помощью неявного преобразования (§6.1).

o Если переменная, представленная x, является элементом массива ссылочного типа, то во время выполнения проводится проверка с целью убедиться, что значение, вычисленное для y совместимо с экземпляром массива, элементом которого является x. Проверка оказывается успешной, если y равно null или если существует неявное преобразование значения ссылочного типа (§6.1.6) из фактического типа экземпляра, на который ссылается y, в фактический тип элемента экземпляра массива, содержащего x. В противном случае создается исключение System.ArrayTypeMismatchException.

o Значение, получающееся после вычисления и преобразования y, сохраняется в расположении, которое задается значением x.

· Если x классифицируется как свойство или доступ к индексатору, то:

o Вычисляются выражение экземпляра (если x не имеет тип static) и список аргументов (если x является доступом к индексатору), связанные с x, и полученные результаты используются при последующем вызове метода доступа set.

o Вычисляется y и при необходимости преобразуется в тип x с помощью неявного преобразования (§6.1).

o Вызывается метод доступа set для x со значением, вычисленным для y в качестве его аргумента value.

По правилам ковариации массива (§12.5) значение массива типа A[] может быть ссылкой на экземпляр массива типа B[], если существует неявное преобразование ссылочного типа из B в A. В соответствии с этими правилами присваивание элементу массива ссылочного типа требует проведения во время выполнения проверки с целью убедиться, что присваиваемое значение совместимо с экземпляром массива. В этом примере

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[].

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

При наличии объявлений:

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 не являются переменными.

Сложное присваивание

Если левый операнд выражения составного присвоения имеет форму E.P или E[Ei], где E имеет тип времени компиляции dynamic, то операция присвоения динамически привязывается (§7.2.2). В этом случае выражение присваивания имеет тип времени компиляции dynamic, а разрешение, приведенное ниже, будет иметь место во время выполнения на основе типа E времени выполнения.

Операция вида x op= y обрабатывается с применением разрешения перегрузки (§7.3.4), как если бы операция записывалась в виде x op y. Тогда

· Если тип возвращаемого значения выбранного оператора может быть неявно преобразован в тип x, то операция вычисляется как x = x op y, за исключением того, что x вычисляется только один раз.

· Если выбранный оператор является стандартным оператором, то если тип возвращаемого значения выбранного оператора может быть явно преобразован в тип x и если y может быть неявно преобразован в тип x или оператор является оператором сдвига, то операция вычисляется как x = (T)(x op y), где T имеет тип x, за исключением того, что x вычисляется только один раз.

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

Выражение «вычисляется только один раз» означает, что при вычислении x op y, результаты любого составляющего выражения в x временно сохраняются и затем используются повторно при присваивании для x. Например, в присвоении A()[B()] + = C(), где A является методом, возвращающим значение типа int[], а B и C являются методами, возвращающими значение типа int, эти методы вызываются только один раз в последовательности A, B, C.

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

Второе представленное выше правило позволяет в определенных контекстах вычислять x op= y как x = (T)(x op y). Существует правило, согласно которому стандартные операторы можно использовать в качестве сложных операторов, если левый операнд имеет тип sbyte, byte, short, ushort или char. Даже если оба аргумента имеют один из этих типов, стандартные операторы дают результат типа int, как описано в разделе §7.3.6.2. Таким образом, без приведения типов присвоить результат левому операнду не удастся.

Интуитивным результатом применения правила для стандартных операторов является просто то, что операция 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

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

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

int? i = 0;
i += 1; // Ok

используется оператор с нулификацией +(int?,int?).

Присваивание событий

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

· Вычисляет выражение экземпляра для доступа к событию (если имеется).

· Вычисляется правый операнд оператор += или -= и при необходимости преобразуется в тип левого операнда с помощью неявного преобразования (§6.1).

· Вызывается доступ к событию со списком аргументов, состоящим из правого операнда после вычисления и при необходимости после преобразования. Если оператор равен +=, вызывается метод доступа add; если оператор равен -=, вызывается метод доступа remove.

Выражение присваивания события не порождает значения. Таким образом, выражение присваивания события допустимо только в контексте операторного выражения (§8.6).

Выражение

Выражение является либо выражением не присваивания, либо присваиванием.

expression:
non-assignment-expression
assignment

non-assignment-expression:
conditional-expression
lambda-expression
query-expression

Константные выражения

Константное выражение — это выражение, которое можно полностью вычислить во время компиляции.

constant-expression:
expression

Константное выражение должно быть литералом null или значением одного из следующих типов: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, object, string или любого перечисляемого типа. В константных выражениях допустимы только следующие конструкции:

· Литералы (включая литерал null).

· Ссылки на члены const типов класса и структуры.

· Ссылки на члены типов перечисления.

· Ссылки на параметры const или локальные переменные.

· Вложенные выражения в скобках, которые сами являются константными выражениями.

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

· Выражения checked и unchecked.

· Выражения значения по умолчанию

· Стандартные унарные операторы +, –, ! и ~.

· Стандартные бинарные операторы +, –, *, /, %, <<, >>, &, |, ^, &&, ||, ==, !=, <, >, <= и >= при условии, что каждый операнд имеет тип, указанный в списке выше.

· Условный оператор ?:.

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

· Преобразования идентификатора

· Числовые преобразования

· Преобразования перечисления

· Преобразования константных выражений

· Явные и неявные преобразования значений ссылочного типа при условии, что источник преобразования является константным выражением, которое дает значение null.

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

class C {
const object i = 5; // error: boxing conversion not permitted
const object str = “hello”; // error: implicit reference conversion
}

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

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

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

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

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

· Объявления констант (§10.4).

· Объявления членов перечислений (§14.3).

· Аргументы по умолчанию для списков формальных параметров (§10.6.1)

· Метки case в операторе switch (§8.7.2).

· Операторы goto case (§8.9.3).

· Длины измерений в выражениях создания массивов (§7.6.10.4), которые включают инициализатор.

· Атрибуты (§17).

Неявное преобразование константного выражения (§6.1.9) допускает преобразование константного выражения типа int в тип sbyte, byte, short, ushort, uint или ulong при условии, что значение константного выражения находится в пределах диапазона целевого типа.

Логические выражения

Логическое выражение — это выражение, которое дает результат типа bool; напрямую или посредством применения оператора operator true в определенных контекстах, как указано далее.

boolean-expression:
expression

Логическим выражением является управляющее условное выражение оператора if (§8.7.1), оператора while (§8.8.1), оператора do (§8.8.2) или оператора for (§8.8.3). Для управляющего выражения оператора ?: (§7.14) действуют те же правила, что и для логического выражения, но из-за приоритетов операторов оно классифицируется как условное выражение ИЛИ.

Логическое выражение E должно обеспечивать создание значения типа bool следующим образом.

· Если E является неявно преобразуемым к типу bool, во время выполнения применяется это неявное преобразование.

· В противном случае выполняется поиск уникальной наиболее подходящей реализации оператора true в E с помощью разрешения перегрузок унарных операторов (§7.3.3), и во время выполнения применяется найденная реализация.

· Если не удается найти такой оператор, возникает ошибка времени привязки.

Тип структуры DBBool в разделе §11.4.2 является примером типа, который реализует операторы operator true и operator false.

Операторы

Язык C# содержит множество операторов. Большинство из них будут знакомы разработчикам, имеющим опыт программирования на языках C и C++.

statement:
labeled-statement
declaration-statement
embedded-statement

embedded-statement:
block
empty-statement
expression-statement
selection-statement
iteration-statement
jump-statement
try-statement
checked-statement
unchecked-statement
lock-statement
using-statement
yield-statement

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

void F(bool b) {
if (b)
int i = 44;
}

результатом будет ошибка времени компиляции, поскольку для оператора if требуется использовать внедренный оператор в ветви , а не оператор языка. Если бы такой код был разрешен, переменная i стала бы объявленной, но ее нельзя было бы использовать. Следует, однако, отметить, что помещение объявления i в блок делает пример допустимым.

Наши рекомендации