7.6.2 Unary Minus Operator

For an operation of the form -x, unary operator overload resolution (§7.2.3) is applied to select a specific operator implementation. The operand is converted to the parameter type of the selected operator, and the type of the result is the return type of the operator. The predefined negation operators are given here:

•  Integer negation:

     int operator -(int x);
     long operator -(long x);

   The result is computed by subtracting x from zero. If the value of x is the smallest representable value of the operand type (−231 for int or −263 for long), then the mathematical negation of x is not representable within the operand type. If this occurs within a checked context, a System.OverflowException is thrown; if it occurs within an unchecked con-text, the result is the value of the operand and the overflow is not reported.

If the operand of the negation operator is of type uint, it is converted to type long, and the type of the result is long. An exception is the rule that permits the int value −2147483648 (−231) to be written as a decimal integer literal (§2.4.4.2).

If the operand of the negation operator is of type ulong, a compile-time error occurs. An exception is the rule that permits the long value −9223372036854775808 (−263) to be written as a decimal integer literal (§2.4.4.2).

•  Floating point negation:

     float operator -(float x);
     double operator -(double x);

The result is the value of x with its sign inverted. If x is NaN, the result is also NaN.

•  Decimal negation:

     decimal operator -(decimal x);

The result is computed by subtracting x from zero. Decimal negation is equivalent to using the unary minus operator of type System.Decimal.

7.6.3 Logical Negation Operator

For an operation of the form !x, unary operator overload resolution (§7.2.3) is applied to select a specific operator implementation. The operand is converted to the parameter type of the selected operator, and the type of the result is the return type of the operator. Only one predefined logical negation operator exists:

     bool operator !(bool x);

This operator computes the logical negation of the operand: If the operand is true, the result is false. If the operand is false, the result is true.

7.6.4 Bitwise Complement Operator

For an operation of the form ~x, unary operator overload resolution (§7.2.3) is applied to select a specific operator implementation. The operand is converted to the parameter type of the selected operator, and the type of the result is the return type of the operator. The predefined bitwise complement operators are listed here:

     int operator ~(int x);
     uint operator ~(uint x);
     long operator ~(long x);
     ulong operator ~(ulong x);

For each of these operators, the result of the operation is the bitwise complement of x.

Every enumeration type E implicitly provides the following bitwise complement operator:

     E operator ~(E x);

The result of evaluating ~x, where x is an expression of an enumeration type E with an underlying type U, is exactly the same as evaluating (E)(~(U)x).

7.6.5 Prefix Increment and Decrement Operators

      pre-increment-expression:
             ++ unary-expression
      pre-decrement-expression:
             -- unary-expression

The operand of a prefix increment or decrement operation must be an expression classified as a variable, a property access, or an indexer access. The result of the operation is a value of the same type as the operand.

If the operand of a prefix increment or decrement operation is a property or indexer access, the property or indexer must have both a get accessor and a set accessor. If this is not the case, a compile-time error occurs.

Unary operator overload resolution (§7.2.3) is applied to select a specific operator implementation. Predefined ++ and -- operators exist for the following types: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, and any enum type. The predefined ++ operators return the value produced by adding 1 to the operand, and the predefined -- operators return the value produced by subtracting 1 from the operand. In a checked context, if the result of this addition or subtraction is outside the range of the result type and the result type is an integral type or enum type, a System.OverflowException is thrown.

The runtime processing of a prefix increment or decrement operation of the form ++x or --x consists of the following steps:

•  If x is classified as a variable:

-  x is evaluated to produce the variable.

-  The selected operator is invoked with the value of x as its argument.

-  The value returned by the operator is stored in the location given by the evalua-tion of x.

-  The value returned by the operator becomes the result of the operation.

•  If x is classified as a property or indexer access:

-  The instance expression (if x is not static) and the argument list (if x is an indexer access) associated with x are evaluated, and the results are used in the subsequent get and set accessor invocations.

-  The get accessor of x is invoked.

-  The selected operator is invoked with the value returned by the get accessor as its argument.

-  The set accessor of x is invoked with the value returned by the operator as its value argument.

-  The value returned by the operator becomes the result of the operation.

The ++ and -- operators also support postfix notation (§7.5.9). The result of x++ or x-- is the value of x before the operation, whereas the result of ++x or --x is the value stored in x after the operation. In either case, x itself has the same value after the operation.

An operator ++ or operator -- implementation can be invoked using either postfix or prefix notation. It is not possible to have separate operator implementations for the two notations.

7.6.6 Cast Expressions

A cast-expression is used to explicitly convert an expression to a given type.

        cast-expression:
               ( type ) unary-expression

A cast-expression of the form (T)E, where T is a type and E is a unary-expression, performs an explicit conversion (§6.2) of the value of E to type T. If no explicit conversion exists from E to T, a compile-time error occurs. Otherwise, the result is the value produced by the explicit conversion. The result is always classified as a value, even if E denotes a variable.

The grammar for a cast-expression leads to certain syntactic ambiguities. For example, the expression (x)-y could be interpreted either as a cast-expression (a cast of -y to type x) or as an additive-expression combined with a parenthesized-expression (which computes the value x - y).

To resolve cast-expression ambiguities, the following rule exists: A sequence of one or more tokens (§2.3.3) enclosed in parentheses is considered the start of a cast-expression only if at least one of the following criteria is met:

•  The sequence of tokens is correct grammar for a type, but not for an expression.

•  The sequence of tokens is correct grammar for a type, and the token immediately following the closing parentheses is the token “~”, the token “!”, the token “(”, an identifier2.4.1), a literal2.4.4), or any keyword2.4.3) except as or is.

The term “correct grammar” simply means that the sequence of tokens must conform to the particular grammatical production. It specifically does not consider the actual meaning of any constituent identifiers. For example, if x and y are identifiers, then x.y is correct grammar for a type, even if x.y doesn’t actually denote a type.

From the disambiguation rule it follows that, if x and y are identifiers, (x)y, (x)(y), and (x)(-y) are cast-expressions, but (x)-y is not, even if x identifies a type. However, if x is a keyword that identifies a predefined type (such as int), then all four forms are cast-expressions (because such a keyword could not possibly be an expression by itself).

7.7 Arithmetic Operators

The *, /, %, +, and -operators are called the arithmetic operators.

       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

7.7.1 Multiplication Operator

For an operation of the form x * y, binary operator overload resolution (§7.2.4) is applied to select a specific operator implementation. The operands are converted to the parameter types of the selected operator, and the type of the result is the return type of the operator.

The predefined multiplication operators are listed below. All of the operators shown here compute the product of x and y.

•  Integer multiplication:

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

In a checked context, if the product is outside the range of the result type, a System. OverflowException is thrown. In an unchecked context, overflows are not reported and any significant high-order bits outside the range of the result type are discarded.

•  Floating point multiplication:

     float operator *(float x, float y);
     double operator *(double x, double y);

The product is computed according to the rules of IEEE 754 arithmetic. The following table lists the results of all possible combinations of non-zero finite values, zeros, infini-ties, and NaNs. In the table, x and y are positive finite values, and z is the result of x * y. If the result is too large for the destination type, z is infinity. If the result is too small for the destination type, z is zero.

Image

Decimal multiplication:

     decimal operator *(decimal x, decimal y);

If the resulting value is too large to represent in the decimal format, a System. Overflow-Exception is thrown. If the result value is too small to represent in the decimal format, the result is zero. The scale of the result, before any rounding, is the sum of the scales of the two operands.

Decimal multiplication is equivalent to using the multiplication operator of type System.Decimal.

7.7.2 Division Operator

For an operation of the form x / y, binary operator overload resolution (§7.2.4) is applied to select a specific operator implementation. The operands are converted to the parameter types of the selected operator, and the type of the result is the return type of the operator.

The predefined division operators are listed below. The operators all compute the quotient of x and y.

•  Integer division:

     int operator /(int x, int y);
     uint operator /(uint x, uint y);
     long operator /(long x, long y);
     ulong operator /(ulong x, ulong y);

If the value of the right operand is zero, a System.DivideByZeroException is thrown.

The division rounds the result toward zero, and the absolute value of the result is the largest possible integer that is less than the absolute value of the quotient of the two operands. The result is zero or positive when the two operands have the same sign; it is zero or negative when the two operands have opposite signs.

If the left operand is the smallest representable int or long value and the right operand is -1, an overflow occurs. In a checked context, this overflow causes a System. Arithmetic Exception (or a subclass thereof) to be thrown. In an unchecked context, the implementation determines whether a System.ArithmeticException (or a subclass thereof) is thrown or the overflow goes unreported with the resulting value being that of the left operand.

•  Floating point division:

     float operator /(float x, float y);
     double operator /(double x, double y);

The quotient is computed according to the rules of IEEE 754 arithmetic. The following table lists the results of all possible combinations of non-zero finite values, zeros, infinities, and NaNs. In the table, x and y are positive finite values, and z is the result of x / y. If the result is too large for the destination type, z is infinity. If the result is too small for the destination type, z is zero.

Image

•  Decimal division:

     decimal operator /(decimal x, decimal y);

If the value of the right operand is zero, a System.DivideByZeroException is thrown. If the resulting value is too large to represent in the decimal format, a System.Overflow-Exception is thrown. If the result value is too small to represent in the decimal format, the result is zero. The scale of the result is the smallest scale that will preserve a result equal to the nearest representable decimal value to the true mathematical result.

Decimal division is equivalent to using the division operator of type System.Decimal.

7.7.3 Remainder Operator

For an operation of the form x % y, binary operator overload resolution (§7.2.4) is applied to select a specific operator implementation. The operands are converted to the parameter types of the selected operator, and the type of the result is the return type of the operator.

The predefined remainder operators are listed below. The operators all compute the remainder of the division between x and y.

•  Integer remainder:

     int operator %(int x, int y);
     uint operator %(uint x, uint y);
     long operator %(long x, long y);
     ulong operator %(ulong x, ulong y);

The result of x % y is the value produced by x - (x / y) * y. If y is zero, a System.Divide-ByZeroException is thrown.

If the left operand is the smallest int or long value and the right operand is -1, a System.OverflowException is thrown. In no case does x % y throw an exception where x / y would not throw an exception.

•  Floating point remainder:

     float operator %(float x, float y);
     double operator %(double x, double y);

The following table lists the results of all possible combinations of non-zero finite values, zeros, infinities, and NaNs. In the table, x and y are positive finite values, and z is the result of x % y and is computed as x - n * y where n y is the largest possible integer that is less than or equal to x / y. This method of computing the remainder is analogous to that used for integer operands, but differs from the IEEE 754 definition (in which n is the integer closest to x / y).

Image

•  Decimal remainder:

     decimal operator %(decimal x, decimal y);

If the value of the right operand is zero, a System.DivideByZeroException is thrown. The scale of the result, before any rounding, is the larger of the scales of the two operands, and the sign of the result, if non-zero, is the same as that of x.

Decimal remainder is equivalent to using the remainder operator of type System. Decimal.

7.7.4 Addition Operator

For an operation of the form x + y, binary operator overload resolution (§7.2.4) is applied to select a specific operator implementation. The operands are converted to the parameter types of the selected operator, and the type of the result is the return type of the operator.

The predefined addition operators are listed below. For numeric and enumeration types, the predefined addition operators compute the sum of the two operands. When one or both operands are of type string, the predefined addition operators concatenate the string representation of the operands.

•  Integer addition:

     int operator +(int x, int y);
     uint operator +(uint x, uint y);
     long operator +(long x, long y);
     ulong operator +(ulong x, ulong y);

In a checked context, if the sum is outside the range of the result type, a System. OverflowException is thrown. In an unchecked context, overflows are not reported and any significant high-order bits outside the range of the result type are discarded.

•  Floating point addition:

     float operator +(float x, float y);
     double operator +(double x, double y);

The sum is computed according to the rules of IEEE 754 arithmetic. The following table lists the results of all possible combinations of non-zero finite values, zeros, infinities, and NaNs. In the table, x and y are nonzero finite values, and z is the result of x + y. If x and y have the same magnitude but opposite signs, z is positive zero. If x + y is too large to represent in the destination type, z is an infinity with the same sign as x + y.

Image

•  Decimal addition:

     decimal operator +(decimal x, decimal y);

If the resulting value is too large to represent in the decimal format, a System. OverflowException is thrown. The scale of the result, before any rounding, is the larger of the scales of the two operands.

Decimal addition is equivalent to using the addition operator of type System.Decimal.

•  Enumeration addition. Every enumeration type implicitly provides the following predefined operators, where E is the enum type and U is the underlying type of E:

     E operator +(E x, U y);

     E operator +(U x, E y);

•  The operators are evaluated exactly as (E)((U)x + (U)y).

•  String concatenation:

     string operator +(string x, string y);
     string operator +(string x, object y);
     string operator +(object x, string y);

The binary + operator performs string concatenation when one or both operands are of type string. If an operand of string concatenation is null, an empty string is substituted. Otherwise, any nonstring argument is converted to its string representation by invoking the virtual ToString method inherited from type object. If T oString returns null, an empty string is substituted.

     using System;
     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
           }
    }

The result of the string concatenation operator is a string that consists of the characters of the left operand followed by the characters of the right operand. The string concatenation operator never returns a null value. A System.OutOfMemoryException may be thrown if there is not enough memory available to allocate the resulting string.

•  Delegate combination. Every delegate type implicitly provides the following predefined operator, where D is the delegate type:

     D operator +(D x, D y);

The binary + operator performs delegate combination when both operands are of some delegate type D. (If the operands have different delegate types, a compile-time error occurs.) If the first operand is null, the result of the operation is the value of the second operand (even if that is also null). Otherwise, if the second operand is null, then the result of the operation is the value of the first operand. Otherwise, the result of the operation is a new delegate instance that, when invoked, invokes the first operand and then invokes the second operand. For examples of delegate combination, see §7.7.5 and §15.4. Because System.Delegate is not a delegate type, operator + is not defined for it.

7.7.5 Subtraction Operator

For an operation of the form x - y, binary operator overload resolution (§7.2.4) is applied to select a specific operator implementation. The operands are converted to the parameter types of the selected operator, and the type of the result is the return type of the operator.

The predefined subtraction operators are listed below. The operators all subtract y from x.

•  Integer subtraction:

     int operator -(int x, int y);
     uint operator -(uint x, uint y);
     long operator -(long x, long y);
     ulong operator -(ulong x, ulong y);

In a checked context, if the difference is outside the range of the result type, a System. OverflowException is thrown. In an unchecked context, overflows are not reported and any significant high-order bits outside the range of the result type are discarded.

•  Floating point subtraction:

     float operator -(float x, float y);
     double operator -(double x, double y);

The difference is computed according to the rules of IEEE 754 arithmetic. The following table lists the results of all possible combinations of non-zero finite values, zeros, infini-ties, and NaNs. In the table, x and y are non-zero finite values, and z is the result of x y. If x and y are equal, z is positive zero. If x - y is too large to represent in the destination type, z is an infinity with the same sign as x - y.

Image

•  Decimal subtraction:

     decimal operator -(decimal x, decimal y);

If the resulting value is too large to represent in the decimal format, a System. Overflow-Exception is thrown. The scale of the result, before any rounding, is the larger of the scales of the two operands.

Decimal subtraction is equivalent to using the subtraction operator of type System. Decimal.

•  Enumeration subtraction. Every enumeration type implicitly provides the following predefined operator, where E is the enum type and U is the underlying type of E:

     U operator -(E x, E y);

This operator is evaluated exactly as (U)((U)x - (U)y). In other words, the operator computes the difference between the ordinal values of x and y, and the type of the result is the underlying type of the enumeration.

     E operator -(E x, U y);

This operator is evaluated exactly as (E)((U)x - y). In other words, the operator subtracts a value from the underlying type of the enumeration, yielding a value of the enumeration.

•  Delegate removal. Every delegate type implicitly provides the following predefined operator, where D is the delegate type:

     D operator -(D x, D y);

The binary -operator performs delegate removal when both operands are of some delegate type D. If the operands have different delegate types, a compile-time error occurs. If the first operand is null, the result of the operation is null. Otherwise, if the second operand is null, then the result of the operation is the value of the first operand. Otherwise, both operands represent invocation lists (§15.1) having one or more entries, and the result is a new invocation list consisting of the first operand’s list with the second operand’s entries removed from it, provided the second operand’s list is a proper contiguous sublist of the first operand’s list. [To determine sublist equality, corresponding entries are compared as for the delegate equality operator (§7.9.8).] Otherwise, the result is the value of the left operand. Neither of the operands’ lists is changed in the process. If the second operand’s list matches multiple sublists of contiguous entries in the first operand’s list, the rightmost matching sublist of contiguous entries is removed. If removal results in an empty list, the result is null. For example,

     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 cd3 = cd1 + cd2 + cd2 + cd1;                // M1 + M2 + M2 + M1
                  cd3 -= cd1;                                                  // => M1 + M2 + M2
                  cd3 = cd1 + cd2 + cd2 + cd1;                   // M1 + M2 + M2 + M1
                  cd3 -= cd1 + cd2;                                       // => M2 + M1
                  cd3 = cd1 + cd2 + cd2 + cd1;                  // M1 + M2 + M2 + M1
                  cd3 -= cd2 + cd2;                                      // => M1 + M1
                  cd3 = cd1 + cd2 + cd2 + cd1;                  // M1 + M2 + M2 + M1
                  cd3 -= cd2 + cd1;                                      // => M1 + M2
                  cd3 = cd1 + cd2 + cd2 + cd1;                  // M1 + M2 + M2 + M1
                  cd3 -= cd1 + cd1;                                      // => M1 + M2 + M2 + M1
              }
         }

7.8 Shift Operators

The << and >> operators are used to perform bit-shifting operations.

      shift-expression:
            additive-expression

            shift-expression << additive-expression
            shift-expression right-shift additive-expression

For an operation of the form x << count or x >> count, binary operator overload resolution (§7.2.4) is applied to select a specific operator implementation. The operands are converted to the parameter types of the selected operator, and the type of the result is the return type of the operator.

When declaring an overloaded shift operator, the type of the first operand must always be the class or struct containing the operator declaration, and the type of the second operand must always be int.

The predefined shift operators are listed below.

•  Shift left:

     int operator <<(int x, int count);
     uint operator <<(uint x, int count);
     long operator <<(long x, int count);
     ulong operator <<(ulong x, int count);

The << operator shifts x left by a number of bits computed as described below.

The high-order bits outside the range of the result type of x are discarded, the remaining bits are shifted left, and the low-order empty bit positions are set to zero.

•  Shift right:

     int operator >>(int x, int count);
     uint operator >>(uint x, int count);
     long operator >>(long x, int count);
     ulong operator >>(ulong x, int count);

The >> operator shifts x right by a number of bits computed as described below.

When x is of type int or long, the low-order bits of x are discarded, the remaining bits are shifted right, and the high-order empty bit positions are set to zero if x is non-negative and set to one if x is negative.

When x is of type uint or ulong, the low-order bits of x are discarded, the remaining bits are shifted right, and the high-order empty bit positions are set to zero.

For the predefined operators, the number of bits to shift is computed as follows:

•  When the type of x is int or uint, the shift count is given by the low-order five bits of count. In other words, the shift count is computed from count & 0x1F.

•  When the type of x is long or ulong, the shift count is given by the low-order six bits of count. In other words, the shift count is computed from count & 0x3F.

If the resulting shift count is zero, the shift operators simply return the value of x.

Shift operations never cause overflows and produce the same results in checked and unchecked contexts.

When the left operand of the >> operator is of a signed integral type, the operator performs an arithmetic shift right, wherein the value of the most significant bit (the sign bit) of the operand is propagated to the high-order empty bit positions. When the left operand of the >> operator is of an unsigned integral type, the operator performs a logical shift right, wherein the high-order empty bit positions are always set to zero. To perform the opposite operation of that inferred from the operand type, explicit casts can be used. For example, if x is a variable of type int, the operation unchecked((int)((uint)x >> y)) performs a logical shift right of x.

7.9 Relational and Type-Testing Operators

The ==, !=, <, >, <=, >=, is, and as operators are called the relational and type-testing operators.

       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 as type

      equality-expression:
              relational-expression
              equality-expression == relational-expression
              equality-expression != relational-expression

The is operator is described in §7.9.10. The as operator is described in §7.9.11.

The ==, !=, <, >, <=, and >= operators are comparison operators. For an operation of the form x op y, where op is a comparison operator, overload resolution (§7.2.4) is applied to select a specific operator implementation. The operands are converted to the parameter types of the selected operator, and the type of the result is the return type of the operator.

The predefined comparison operators are described in the following sections. All predefined comparison operators return a result of type bool, as described in the following table.

Image

7.9.1 Integer Comparison Operators

The predefined integer comparison operators are given here:

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

Each of these operators compares the numeric values of the two integer operands and returns a bool value that indicates whether the particular relation is true or false.

7.9.2 Floating Point Comparison Operators

The predefined floating point comparison operators are given here:

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

These operators compare the operands according to the rules of the IEEE 754 standard:

•  If either operand is NaN, the result is false for all operators except !=, for which the result is true. For any two operands, x!=y always produces the same result as !(x==y). However, when one or both operands are NaNs, the <, >, <=, and >= operators do not produce the same results as the logical negation of the opposite operator. For example, if either of x and y is NaN, then x<y is false, but !(x>=y) is true.

•  When neither operand is NaN, the operators compare the values of the two floating point operands with respect to the ordering

        -∞ < -max < … < -min < 0.0 == +0.0 < +min < … < +max < +∞

where min and max are, respectively, the smallest and largest positive finite values that can be represented in the given floating point format. This ordering has several notable effects:

-  Negative and positive zeros are considered equal.

-  A negative infinity is considered less than all other values, but equal to another nega-tive infinity.

-  A positive infinity is considered greater than all other values, but equal to another -positive infinity.

7.9.3 Decimal Comparison Operators

The predefined decimal comparison operators are shown here:

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

Each of these operators compares the numeric values of the two decimal operands and returns a bool value that indicates whether the particular relation is true or false. Each decimal comparison is equivalent to using the corresponding relational or equality operator of type System.Decimal.

7.9.4 Boolean Equality Operators

There are two predefined boolean equality operators:

          bool operator ==(bool x, bool y);
          bool operator !=(bool x, bool y);

The result of == is true if both x and y are true or if both x and y are false. Otherwise, the result is false.

The result of != is false if both x and y are true or if both x and y are false. Otherwise, the result is true. When the operands are of type bool, the != operator produces the same result as the ^ operator.

7.9.5 Enumeration Comparison Operators

Every enumeration type implicitly provides the following predefined comparison operators:

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

The result of evaluating x op y, where x and y are expressions of an enumeration type E with an underlying type U, and op is one of the comparison operators, is exactly the same as evaluating((U)x) op ((U)y). In other words, the enumeration type comparison operators simply compare the underlying integral values of the two operands.

7.9.6 Reference Type Equality Operators

There are two predefined reference type equality operators:

          bool operator ==(object x, object y);
          bool operator !=(object x, object y);

These operators return the result of comparing the two references for equality or non-equality.

Because the predefined reference type equality operators accept operands of type object, they apply to all types that do not declare applicable operator == and operator != members. Conversely, any applicable user-defined equality operators effectively hide the predefined reference type equality operators.

The predefined reference type equality operators require one of the following:

•  Both operands are reference-type values or the literal null. Furthermore, a standard implicit conversion (§6.3.1) exists from the type of either operand to the type of the other operand.

•  One operand is a value of type T, where T is a type-parameter, and the other operand is the literal null. Furthermore, T does not have the value type constraint.

Unless one of these conditions is true, a compile-time error occurs. These rules have several notable implications:

•  It is a compile-time error to use the predefined reference type equality operators to compare two references that are known to be different at compile time. For example, if the compile-time types of the operands are two class types A and B, and if neither A nor B derives from the other, then it would be impossible for the two operands to reference the same object. Thus the operation is considered a compile-time error.

•  The predefined reference type equality operators do not permit value type operands to be compared. Therefore, unless a struct type declares its own equality operators, it is not possible to compare values of that struct type.

•  The predefined reference type equality operators never cause boxing operations to occur for their operands. It would be meaningless to perform such boxing operations, because references to the newly allocated boxed instances would necessarily differ from all other references.

•  If an operand of a type parameter type T is compared to null, and the runtime type of T is a value type, the result of the comparison is false.

The following example checks whether an argument of an unconstrained type parameter type is null.

        class C<T>
        {
               void F(T x) {
                      if (x == null) throw new ArgumentNullException();
                      …
               }
        }

The x==null construct is permitted even though T could represent a value type, and the result is simply defined to be false when T is a value type.

For an operation of the form x==y or x!=y, if any applicable operator == or operator!= exists, the operator overload resolution (§7.2.4) rules will select that operator instead of the predefined reference type equality operator. Nevertheless, it is always possible to select the predefined reference type equality operator by explicitly casting one or both of the operands to type object. The example

        using System;
        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);
                }
        }

produces the following output:

        True
        False
        False
        False

In this example, the s and t variables refer to two distinct string instances containing the same characters. The first comparison outputs True because the predefined string equality operator (§7.9.7) is selected when both operands are of type string. The remaining comparisons all output False because the predefined reference type equality operator is selected when one or both of the operands are of type object.

This technique is not meaningful for value types, however. The example

        class Test
        {
                static void Main() {
                        int i = 123;
                        int j = 123;
                        System.Console.WriteLine((object)i == (object)j);
                }
        }

outputs False because the casts create references to two separate instances of boxed int values.

7.9.7 String Equality Operators

There are two predefined string equality operators:

         bool operator ==(string x, string y);
         bool operator !=(string x, string y);

Two string values are considered equal when one of the following is true:

•  Both values are null.

•  Both values are non-null references to string instances that have identical lengths and identical characters in each character position.

The string equality operators compare string values rather than string references. When two separate string instances contain the exact same sequence of characters, the values of the strings are equal, but the references are different. As described in §7.9.6, the reference type equality operators can be used to compare string references instead of string values.

7.9.8 Delegate Equality Operators

Every delegate type implicitly provides the following predefined comparison operators:

         bool operator ==(System.Delegate x, System.Delegate y);
         bool operator !=(System.Delegate x, System.Delegate y);

Two delegate instances are considered equal as follows:

•  If either of the delegate instances is null, they are equal if and only if both are null.

•  If the delegates have different runtime types, they are never equal.

•  If both of the delegate instances have an invocation list (§15.1), those instances are equal if and only if their invocation lists are the same length, and each entry in one’s invocation list is equal (as defined below) to the corresponding entry, in order, in the other’s invocation list.

The following rules govern the equality of invocation list entries:

•  If two invocation list entries both refer to the same static method, then the entries are equal

•  If two invocation list entries both refer to the same nonstatic method on the same target object (as defined by the reference equality operators), then the entries are equal.

•  Invocation list entries produced from evaluation of semantically identical anonymous-function-expression s with the same (possibly empty) set of captured outer variable instances are permitted (but not required) to be equal.

7.9.9 Equality Operators and null

The == and != operators permit one operand to be a value of a nullable type and the other to be the null literal, even if no predefined or user-defined operator (in nonlifted or lifted form) exists for the operation.

For an operation of one of the forms

         x == null null == x x != null null != x

where x is an expression of a nullable type, if operator overload resolution (§7.2.4) fails to find an applicable operator, the result is instead computed from the HasValue property of x. Specifically, the first two forms are translated into !x.HasValue, and the last two forms are translated into x.HasValue.

7.9.10 The is Operator

The is operator is used to dynamically check if the runtime type of an object is compatible with a given type. The result of the operation EisT, where E is an expression and T is a type, is a boolean value indicating whether E can successfully be converted to type T by a reference conversion, a boxing conversion, or an unboxing conversion. The operation is evaluated as follows, after type arguments have been substituted for all type parameters:

•  If E is an anonymous function, a compile-time error occurs.

•  If E is a method group or the null literal, of if the type of E is a reference type or a nullable type and the value of E is null, the result is false.

•  Otherwise, let D represent the dynamic type of E as follows:

-  If the type of E is a reference type, D is the runtime type of the instance reference by E.

-  If the type of E is a nullable type, D is the underlying type of that nullable type.

-   If the type of E is a non-nullable value type, D is the type of E.

•  The result of the operation depends on D and T as follows:

-  If T is a reference type, the result is true if D and T are the same type, if D is a reference type and an implicit reference conversion from D to T exists, or if D is a value type and a boxing conversion from D to T exists.

-  If T is a nullable type, the result is true if D is the underlying type of T.

-  If T is a non-nullable value type, the result is true if D and T are the same type.

-  Otherwise, the result is false.

The is operator does not consider user-defined conversions.

7.9.11 The as Operator

The as operator is used to explicitly convert a value to a given reference type or nullable type. Unlike a cast expression (§7.6.6), the as operator never throws an exception. Instead, if the indicated conversion is not possible, the resulting value is null.

In an operation of the form EasT, E must be an expression and T must be a reference type, a type parameter known to be a reference type, or a nullable type. Furthermore, at least one of the following must be true, or otherwise a compile-time error occurs:

•  An identity (§6.1.1), implicit nullable (§6.1.4), implicit reference (§6.1.6), boxing (§6.1.7), explicit nullable (§6.2.3), explicit reference (§6.2.4), or unboxing (§6.2.5) conversion exists from E to T.

•  The type of E or T is an open type.

•  E is the null literal.

The operation EasT produces the same result as

          E is T ? (T)(E) : (T)null

except that E is evaluated only once. The compiler can be expected to optimize EasT to perform at most one dynamic type check, as opposed to the two dynamic type checks implied by the expansion above.

Some conversions, such as user-defined conversions, are not possible with the as operator and should instead be performed using cast expressions.

In the example

        class X
        {
                public string F(object o) {
                        return o as string;                // Okay, string is a reference type
                }
                public T G<T>(object o) where T: Attribute {
                        return o as T;                        // Okay, T has a class constraint
                }
                public U H<U>(object o) {
                        return o as U;                        // Error, U is unconstrained
                }
        }

the type parameter T of G is known to be a reference type, because it has the class constraint. The type parameter U of H is not, however; hence, the use of the as operator in H is disallowed.

7.10 Logical Operators

The &, ^, and | operators are called the logical operators.

      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

For an operation of the form x op y, where op is one of the logical operators, overload resolution (§7.2.4) is applied to select a specific operator implementation. The operands are converted to the parameter types of the selected operator, and the type of the result is the return type of the operator.

The predefined logical operators are described in the following sections.

7.10.1 Integer Logical Operators

The predefined integer logical operators are listed here:

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

The & operator computes the bitwise logical AND of the two operands, the | operator computes the bitwise logical OR of the two operands, and the ^ operator computes the bitwise logical exclusive OR of the two operands. No overflows are possible from these operations.

7.10.2 Enumeration Logical Operators

Every enumeration type E implicitly provides the following predefined logical operators:

         E operator &(E x, E y);
         E operator |(E x, E y);
         E operator ^(E x, E y);

The result of evaluating x op y, where x and y are expressions of an enumeration type E with an underlying type U, and op is one of the logical operators, is exactly the same as evaluating(E)((U)x op (U)y). In other words, the enumeration type logical operators simply perform the logical operation on the underlying type of the two operands.

7.10.3 Boolean Logical Operators

There are three predefined boolean logical operators:

         bool operator &(bool x, bool y);
         bool operator |(bool x, bool y);
         bool operator ^(bool x, bool y);

The result of x&y is true if both x and y are true. Otherwise, the result is false.

The result of x|y is true if either x or y is true. Otherwise, the result is false.

The result of x^y is true if x is true and y is false, or if x is false and y is true. Otherwise, the result is false. When the operands are of type bool, the ^ operator computes the same result as the != operator.

7.10.4 Nullable Boolean Logical Operators

The nullable boolean type bool? can represent three values: true, false, and null. It is conceptually similar to the three-valued type used for boolean expressions in SQL. To ensure that the results produced by the & and | operators for bool? operands are consistent with SQL’s three-valued logic, two predefined operators are provided:

         bool? operator &(bool? x, bool? y);
         bool? operator |(bool? x, bool? y);

The following table lists the results produced by these operators for all combinations of the values true, false, and null.

Image

7.11 Conditional Logical Operators

The && and || operators are called the conditional logical operators. They are also called the “short-circuiting” logical operators.

       conditional-and-expression:
               inclusive-or-expression
               conditional-and-expression && inclusive-or-expression

      conditional-or-expression:
               conditional-and-expression
               conditional-or-expression || conditional-and-expression

The && and || operators are conditional versions of the & and | operators:

•  The operation x&&y corresponds to the operation x&y, except that y is evaluated only if x is not false.

•  The operation x||y corresponds to the operation x|y, except that y is evaluated only if x is not true.

An operation of the form x&&y or x||y is processed by applying overload resolution (§7.2.4) as if the operation was written x&y or x|y. Then,

•  If overload resolution fails to find a single best operator, or if overload resolution selects one of the predefined integer logical operators, a compile-time error occurs.

•  Otherwise, if the selected operator is one of the predefined boolean logical operators (§7.10.3) or nullable boolean logical operators (§7.10.4), the operation is processed as described in §7.11.1.

•  Otherwise, the selected operator is a user-defined operator, and the operation is processed as described in §7.11.2.

It is not possible to directly overload the conditional logical operators. However, because the conditional logical operators are evaluated in terms of the regular logical operators, overloads of the regular logical operators are, with certain restrictions, also considered overloads of the conditional logical operators. This issue is described further in §7.11.2.

7.11.1 Boolean Conditional Logical Operators

When the operands of && or || are of type bool, or when the operands are of types that do not define an applicable operator& or operator|, but do define implicit conversions to bool, the operation is processed as follows:

•  The operation x&&y is evaluated as x?y:false. In other words, x is first evaluated and converted to type bool. Then, if x is true, y is evaluated and converted to type bool, and this becomes the result of the operation. Otherwise, the result of the operation is false.

•  The operation x||y is evaluated as x?true:y. In other words, x is first evaluated and converted to type bool. Then, if x is true, the result of the operation is true. Otherwise, y is evaluated and converted to type bool, and this becomes the result of the operation.

7.11.2 User-Defined Conditional Logical Operators

When the operands of && or || are of types that declare an applicable user-defined operator& or operator|, both of the following conditions must hold, where T is the type in which the selected operator is declared:

•  The return type and the type of each parameter of the selected operator must be T. In other words, the operator must compute the logical AND or the logical OR of two operands of type T, and must return a result of type T.

•  T must contain declarations of operatortrue and operatorfalse.

A compile-time error occurs if either of these requirements is not satisfied. Otherwise, the && or || operation is evaluated by combining the user-defined operatortrue or operatorfalse with the selected user-defined operator:

•  The operation x&&y is evaluated as T.false(x)?x:T.&(x,y), where T.false(x) is an invocation of the operatorfalse declared in T, and T.&(x,y) is an invocation of the selected operator&. In other words, x is first evaluated, and operatorfalse is invoked on the result to determine if x is definitely false. Then, if x is definitely false, the result of the operation is the value previously computed for x. Otherwise, y is evaluated, and the selected operator& is invoked on the value previously computed for x and the value computed for y to produce the result of the operation.

•  The operation x||y is evaluated as T.true(x)?x:T.|(x,y), where T.true(x) is an invocation of the operatortrue declared in T, and T.|(x,y) is an invocation of the selected operator|. In other words, x is first evaluated, and operatortrue is invoked on the result to determine if x is definitely true. Then, if x is definitely true, the result of the operation is the value previously computed for x. Otherwise, y is evaluated, and the selected operator| is invoked on the value previously computed for x and the value computed for y to produce the result of the operation.

In either of these operations, the expression given by x is evaluated only once, and the expression given by y either is not evaluated or is evaluated exactly once.

For an example of a type that implements operatortrue and operatorfalse, see §11.4.2.

7.12 The Null Coalescing Operator

The ?? operator is called the null coalescing operator.

      null-coalescing-expression:
            conditional-or-expression
            conditional-or-expression
?? null-coalescing-expression

A null coalescing expression of the form a??b requires a to be of a nullable type or reference type. If a is non-null, the result of a??b is a; otherwise, the result is b. The operation evaluates b only if a is null.

The null coalescing operator is right-associative, meaning that operations are grouped from right to left. For example, an expression of the form a??b??c is evaluated as a??(b??c). In general terms, an expression of the form E1??E2????EN returns the first of the operands that is non-null, or null if all operands are null.

The type of the expression a??b depends on which implicit conversions are available between the types of the operands. In order of preference, the type of a??b is A0, A, or B, where A is the type of a, B is the type of b (provided that b has a type), and A0 is the underlying type of A if A is a nullable type, or A otherwise. Specifically, a??b is processed as follows:

•  If A is not a nullable type or a reference type, a compile-time error occurs.

•  If A is a nullable type and an implicit conversion exists from b to A0, the result type is A0. At runtime, a is evaluated first. If a is not null, a is unwrapped to type A0, and this becomes the result. Otherwise, b is evaluated and converted to type A0, and this becomes the result.

•  Otherwise, if an implicit conversion exists from b to A, the result type is A. At runtime, a is evaluated first. If a is not null, a becomes the result. Otherwise, b is evaluated and converted to type A, and this becomes the result.

•  Otherwise, if b has a type B and an implicit conversion exists from A0 to B, the result type is B. At runtime, a is evaluated first. If a is not null, a is unwrapped to type A0 (unless A and A0 are the same type) and converted to type B, and this becomes the result. Otherwise, b is evaluated and becomes the result.

•  Otherwise, a and b are incompatible, and a compile-time error occurs.

7.13 Conditional Operator

The ?: operator is called the conditional operator (or sometimes the ternary operator).

      conditional-expression:
              null-coalescing-expression
              null-coalescing-expression
? expression : expression

A conditional expression of the form b?x:y first evaluates the condition b. Then, if b is true, x is evaluated and becomes the result of the operation. Otherwise, y is evaluated and becomes the result of the operation. A conditional expression never evaluates both x and y.

The conditional operator is right-associative, meaning that operations are grouped from right to left. For example, an expression of the form a ? b : c ? d : e is evaluated as a ? b : (c ? d : e).

The first operand of the ?: operator must be an expression of a type that can be implicitly converted to bool, or an expression of a type that implements operator true. If neither of these requirements is satisfied, a compile-time error occurs.

The second and third operands of the ?: operator control the type of the conditional expression. Let X and Y be the types of the second and third operands. Then,

•  If X and Y are the same type, then this is the type of the conditional expression.

•  Otherwise, if an implicit conversion (§6.1) exists from X to Y, but not from Y to X, then Y is the type of the conditional expression.

•  Otherwise, if an implicit conversion (§6.1) exists from Y to X, but not from X to Y, then X is the type of the conditional expression.

•  Otherwise, no expression type can be determined, and a compile-time error occurs.

The runtime processing of a conditional expression of the form b ? x : y consists of the following steps:

•  First, b is evaluated, and the bool value of b is determined:

-   If an implicit conversion from the type of b to bool exists, then this implicit conversion is performed to produce a bool value.

-   Otherwise, the operator true defined by the type of b is invoked to produce a bool value.

•  If the bool value produced by the previous step is true, then x is evaluated and converted to the type of the conditional expression, and this becomes the result of the conditional expression.

•  Otherwise, y is evaluated and converted to the type of the conditional expression, and this becomes the result of the conditional expression.

7.14 Anonymous Function Expressions

An anonymous function is an expression that represents an “in-line” method definition. An anonymous function does not have a value in and of itself, but rather is convertible to a compatible delegate or expression tree type. The evaluation of an anonymous function conversion depends on the target type of the conversion: If it is a delegate type, the conversion evaluates to a delegate value referencing the method that the anonymous function defines. If it is an expression tree type, the conversion evaluates to an expression tree that represents the structure of the method as an object structure.

For historical reasons, two syntactic flavors of anonymous functions exist—namely, lambda-expression s and anonymous-method-expression s. For almost all purposes, lambda-expression s are more concise and expressive than anonymous-method-expression s, which remain in the language for backward compatibility.

      lambda-expression:
              anonymous-function-signature
=> anonymous-function-body

      anonymous-method-expression:
              delegate explicit-anonymous-function-signatureopt block

       anonymous-function-signature:
              explicit-anonymous-function-signature
              implicit-anonymous-function-signature

       explicit-anonymous-function-signature:
              ( explicit-anonymous-function-parameter-listopt )

      explicit-anonymous-function-parameter-list
              explicit-anonymous-function-parameter
              explicit-anonymous-function-parameter-list
, explicit-anonymous-function-parameter

      explicit-anonymous-function-parameter:
              anonymous-function-parameter-modifieropt type identifier

       anonymous-function-parameter-modifier:
              ref
              out

       implicit-anonymous-function-signature:
              ( implicit-anonymous-function-parameter-listopt )
              implicit-anonymous-function-parameter

      implicit-anonymous-function-parameter-list
              implicit-anonymous-function-parameter
              implicit-anonymous-function-parameter-list , implicit-anonymous-function-parameter

       implicit-anonymous-function-parameter:
              identifier

       anonymous-function-body:
              expression
              block

The => operator has the same precedence as assignment (=) and is right-associative.

The parameters of an anonymous function in the form of a lambda-expression can be explicitly or implicitly typed. In an explicitly typed parameter list, the type of each parameter is explicitly stated. In an implicitly typed parameter list, the types of the parameters are inferred from the context in which the anonymous function occurs—specifically, when the anonymous function is converted to a compatible delegate type or expression tree type, that type provides the parameter types (§6.5).

In an anonymous function with a single, implicitly typed parameter, the parentheses may be omitted from the parameter list. In other words, an anonymous function of the form

       ( param ) => expr

can be abbreviated to

        param => expr

The parameter list of an anonymous function in the form of an anonymous-method-expression is optional. If given, the parameters must be explicitly typed. If not, the anonymous function is convertible to a delegate with any parameter list not containing out parameters.

Some examples of anonymous functions follow:

        x => x + 1                                                    // Implicitly typed, expression body
        x => { return x + 1; }                                // Implicitly typed, statement body
        (int x) => x + 1                                           // Explicitly typed, expression body
        (int x) => { return x + 1; }                       // Explicitly typed, statement body
        (x, y) => x * y                                             // Multiple parameters
        () => Console.WriteLine()                     // No parameters
        delegate (int x) { return x + 1; }            // Anonymous method expression
        delegate { return 1 + 1; }                       // Parameter list omitted

The behavior of lambda-expression s and anonymous-method-expressions is the same except for the following points:

•  anonymous-method-expression s permit the parameter list to be omitted entirely, yielding convertibility to delegate types of any list of value parameters.

•  lambda-expressions permit parameter types to be omitted and inferred, whereas anonymous-method-expression s require parameter types to be explicitly stated.

•  The body of a lambda-expression can be an expression or a statement block, whereas the body of an anonymous-method-expression must be a statement block.

•  Because only lambda-expression s can have expression bodies, no anonymous-method-expression can be successfully converted to an expression tree type (§4.6).

7.14.1 Anonymous Function Signatures

The optional anonymous-function-signature of an anonymous function defines the names and optionally the types of the formal parameters for the anonymous function. The scope of the parameters of the anonymous function is the anonymous-function-body3.7). Together with the parameter list (if given), the anonymous-method-body constitutes a declaration space (§3.3). For this reason, it is a compile-time error for the name of a parameter of the anonymous function to match the name of a local variable, local constant, or parameter whose scope includes the anonymous-method-expression or lambda-expression.

If an anonymous function has an explicit-anonymous-function-signature, then the set of compatible delegate types and expression tree types is restricted to those that have the same parameter types and modifiers in the same order. In contrast to method group conversions (§6.6), contravariance of anonymous function parameter types is not supported. If an anonymous function does not have an anonymous-function-signature, then the set of compatible delegate types and expression tree types is restricted to those that have no out parameters.

An anonymous-function-signature cannot include attributes or a parameter array. Nevertheless, an anonymous-function-signature may be compatible with a delegate type whose parameter list contains a parameter array.

Note that conversion to an expression tree type, even if compatible, may still fail at compile time (§4.6).

7.14.2 Anonymous Function Bodies

The body ( expression or block ) of an anonymous function is subject to the following rules:

•  If the anonymous function includes a signature, the parameters specified in the signature are available in the body. If the anonymous function has no signature, it can be converted to a delegate type or expression type having parameters (§6.5), but the parameters cannot be accessed in the body.

•  Except for ref or out parameters specified in the signature (if any) of the nearest enclosing anonymous function, it is a compile-time error for the body to access a ref or out parameter.

•  When the type of this is a struct type, it is a compile-time error for the body to accessthis. This is true whether the access is explicit (as in this.x) or implicit (as in x where x is an instance member of the struct). This rule simply prohibits such access and does not affect whether member lookup returns a member of the struct.

•  The body has access to the outer variables (§7.14.4) of the anonymous function. Access of an outer variable will reference the instance of the variable that is active at the time the lambda-expression or anonymous-method-expression is evaluated (§7.14.5).

•  It is a compile-time error for the body to contain a goto statement, break statement, or continue statement whose target is outside the body or within the body of a contained anonymous function.

•  Areturn statement in the body returns control from an invocation of the nearest enclosing anonymous function, not from the enclosing function member. An expression specified in a return statement must be compatible with the delegate type or expression tree type to which the nearest enclosing lambda-expression or anonymous-method-expression is converted (§6.5).

It is explicitly unspecified whether there is any way to execute the block of an anonymous function other than through evaluation and invocation of the lambda-expression or anonymous-method-expression. In particular, the compiler may choose to implement an anonymous function by synthesizing one or more named methods or types. The names of any such synthesized elements must be of a form reserved for compiler use.

7.14.3 Overload Resolution

Anonymous functions in an argument list participate in type inference and overload resolution. Refer to §7.4.2.3 for the exact rules governing their behavior.

The following example illustrates the effect of anonymous functions on overload resolution.

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

The ItemList<T> class has two Sum methods. Each takes a selector argument, which extracts the value to sum over from a list item. The extracted value can be either an int or a double, and the resulting sum is likewise either an int or a double.

The Sum methods could, for example, be used to compute sums from a list of detail lines in some order.

        class Detail
        {
                public int UnitCount;
                public double UnitPrice;
                …
        }
        void ComputeSums() {
                ItemList<Detail> orderDetails = GetOrderDetails(…);
                int totalUnits = orderDetails.Sum(d => d.UnitCount);
                double orderTotal = orderDetails.Sum(d => d.UnitPrice * d.UnitCount);
                …
        }

In the first invocation of orderDetails.Sum, both Sum methods are applicable because the anonymous function d => d.UnitCount is compatible with both Func<Detail, int> and Func<Detail, double>. However, overload resolution picks the first Sum method because the conversion to Func<Detail, int> is better than the conversion to Func<Detail, double>.

In the second invocation of orderDetails.Sum, only the second Sum method is applicable because the anonymous function d => d.UnitPrice * d.UnitCount produces a value of type double. Thus overload resolution picks the second Sum method for that invocation.

7.14.4 Outer Variables

Any local variable, value parameter, or parameter array whose scope includes the lambda-expression or anonymous-method-expression is called an outer variable of the anonymous function. In an instance function member of a class, thethis value is considered a value parameter and is an outer variable of any anonymous function contained within the function member.

7.14.4.1 Captured Outer Variables

When an outer variable is referenced by an anonymous function, the outer variable is said to have been captured by the anonymous function. Ordinarily, the lifetime of a local variable is limited to execution of the block or statement with which it is associated (§5.1.7). However, the lifetime of a captured outer variable is extended at least until the delegate or expression tree created from the anonymous function becomes eligible for garbage collection.

In the example

        using System;
        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());
                }
        }

the local variable x is captured by the anonymous function, and the lifetime of x is extended at least until the delegate returned from F becomes eligible for garbage collection (which doesn’t happen until the very end of the program). Because each invocation of the anonymous function operates on the same instance of x, the example produces the following output:

        1
        2
        3

When a local variable or a value parameter is captured by an anonymous function, the local variable or parameter is no longer considered to be a fixed variable (§18.3), but is instead considered to be a moveable variable. Thus any unsafe code that takes the address of a captured outer variable must first use the fixed statement to fix the variable.

7.14.4.2 Instantiation of Local Variables

A local variable is considered to be instantiated when execution enters the scope of the variable. For example, when the following method is invoked, the local variable x is instantiated and initialized three times—once for each iteration of the loop.

        static void F() {
                for (int i = 0; i < 3; i++) {                        
                        int x = i * 2 + 1;
                        …
                }
        }

By comparison, moving the declaration of x outside the loop results in a single instantiation of x:

        static void F() {
                int x;
                for (int i = 0; i < 3; i++) {
                        x = i * 2 + 1;
                        …
                }
        }

When not captured, there is no way to observe exactly how often a local variable is instantiated—because the lifetimes of the instantiations are disjoint, it is possible for each instantiation to simply use the same storage location. However, when an anonymous function captures a local variable, the effects of instantiation become apparent.

The example

        using System;
        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();
                }
        }

produces the following output:

        1
        3
        5

When the declaration of x is moved outside the loop,

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

the output changes as follows:

        5
        5
        5

If a for-statement declares an iteration variable, that variable itself is considered to be declared outside of the loop. Thus, if the example is changed to capture the iteration variable itself,

        static D[] F() {                
                D[] result = new D[3];
                for (int i = 0; i < 3; i++) {
                      result[i] = () => { Console.WriteLine(i); };
                }
                return result;
        }

only one instance of the iteration variable is captured, which produces the following output:

        3
        3
        3

It is possible for anonymous function delegates to share some captured variables, yet have separate instances of others. For example, if F is changed to

        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("{0} {1}", ++x, ++y); };
                }
                return result;
        }

the three delegates capture the same instance of x but separate instances of y, and the output is as follows:

        1   1
        2   1
        3   1

Separate anonymous functions can capture the same instance of an outer variable. In the example

        using System;
        delegate void Setter(int value);
        delegate int Getter();
        class Test
        {
                static void Main() {
                        int x = 0;
                        Setter s = (int value) => { x = value; };
                        Getter g = () => { return x; };
                        s(5);
                        Console.WriteLine(g());
                        s(10);
                        Console.WriteLine(g());
                }
        }

the two anonymous functions capture the same instance of the local variable x, and they can “communicate” through that variable. This example results in the following output:

        5
        10

7.14.5 Evaluation of Anonymous Function Expressions

An anonymous function F must always be converted to a delegate type D or an expression tree type E, either directly or through the execution of a delegate creation expression new D(F). This conversion determines the result of the anonymous function, as described in §6.5.

7.15 Query Expressions

Query expressions provide a language-integrated syntax for queries that is similar to relational and hierarchical query languages such as SQL and XQuery.

       query-expression:
              from-clause query-body

       from-clause:
              from typeopt identifier in expression

       query-body:
              query-body-clausesopt select-or-group-clause query-continuationopt

       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 typeopt identifier in expression on expression equals expression

      join-into-clause:
              join typeopt identifier in expression on expression equals expression into
              identifier

      orderby-clause:
              orderby orderings

      orderings:
              ordering
              orderings , ordering

      ordering:
              expression ordering-directionopt

       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

A query expression begins with a from clause and ends with either a select or group clause. The initial from clause can be followed by zero or more from, let, where, join, or orderby clauses. Each from clause is a generator introducing a range variable, which ranges over the elements of a sequence. Each let clause introduces a range variable representing a value computed by means of previous range variables. Each where clause is a filter that excludes items from the result. Eachjoin clause compares specified keys of the source sequence with keys of another sequence, yielding matching pairs. Each orderby clause reorders items according to specified criteria. The finalselect or group clause specifies the shape of the result in terms of the range variables. Finally, an into clause can be used to “splice” queries by treating the results of one query as a generator in a subsequent query.

7.15.1 Ambiguities in Query Expressions

Query expressions contain a number of “contextual keywords”—that is, identifiers that have special meaning in a given context. Specifically, these contextual keywords are from, where, join, on, equals, into, let, orderby, ascending, descending, select, group, and by. To avoid ambiguities in query expressions caused by mixed use of these identifiers as keywords and simple names, the identifiers are always considered keywords when they occur anywhere within a query expression.

For this purpose, a query expression is any expression that starts with “from identifier” followed by any token except “;”, “=” , or “,”.

To use these words as identifiers within a query expression, prefix them with “@” (§2.4.2).

7.15.2 Query Expression Translation

The C# language does not directly specify the execution semantics of query expressions. Rather, query expressions are translated into invocations of methods that adhere to the query expression pattern (§7.15.3). Specifically, query expressions are translated into invocations of methods named Where, Select, SelectMany, Join, GroupJoin, OrderBy, OrderByDescending, ThenBy, ThenByDescending, GroupBy, and Cast. These methods are expected to have particular signatures and result types, as described in §7.15.3. They can be instance methods of the object being queried or extension methods that are external to the object, and they implement the actual execution of the query.

The translation from query expressions to method invocations is a syntactic mapping that occurs before any type binding or overload resolution has been performed. The translation is guaranteed to be syntactically correct, but it is not guaranteed to produce semantically correct C# code. Following translation of query expressions, the resulting method invocations are processed as regular method invocations. This processing may, in turn, uncover errors—for example, if the methods do not exist, if arguments have wrong types, or if the methods are generic and type inference fails.

A query expression is processed by repeatedly applying the following translations until no further reductions are possible. The translations are listed in order of application: Each section assumes that the translations in the preceding sections have been performed exhaustively, and once exhausted, a section will not be revisited later in the processing of the same query expression.

Assignment to range variables is not allowed in query expressions. However, a C# implementation is permitted to not always enforce this restriction, because satisfying this constraint may sometimes not be possible with the syntactic translation scheme presented here.

Certain translations inject range variables with transparent identifiers denoted by *. The special properties of transparent identifiers are discussed further in §7.15.2.7.

7.15.2.1 select and groupby Clauses with Continuations

A query expression with a continuation

         from into x…

is translated into

         from x in ( from )

The translations in the following sections assume that queries have no into continuations.

The example

         from c in customers
         group c by c.Country into g
         select new { Country = g.Key, CustCount = g.Count() }

is translated into

         from g in
                 from c in customers
                 group c by c.Country
         select new { Country = g.Key, CustCount = g.Count() }

Its final translation is

         customers.
         GroupBy(c => c.Country).
         Select(g => new { Country = g.Key, CustCount = g.Count() })

7.15.2.2 Explicit Range Variable Types

A from clause that explicitly specifies a range variable type

          from T x in e

is translated into

          from x in ( e ) . Cast < T > ( )

Ajoin clause that explicitly specifies a range variable type

          join T x in e on k1 equals k2

is translated into

          join x in ( e ) . Cast < T > ( ) on k1 equals k2

The translations in the following sections assume that queries have no explicit range variable types.

The example

          from Customer c in customers
          where c.City == "London"
          select c

is translated into

          from c in customers.Cast<Customer>()
          where c.City == "London"
          select c

The final translation is

          customers.
          Cast<Customer>().
          Where(c => c.City == "London")

Explicit range variable types are useful for querying collections that implement the non-generic IEnumerable interface, but not the generic IEnumerable<T> interface. In the preceding example, this would be the case ifcustomers were of type ArrayList.

7.15.2.3 Degenerate Query Expressions

A query expression of the form

          from x in e select x

is translated into

          ( e ) . Select ( x => x )

The example

        from c in customers
        select c

is translated into

        customers.Select(c => c)

A degenerate query expression is one that trivially selects the elements of the source. A later phase of the translation removes degenerate queries introduced by other translation steps by replacing those queries with their source. In this situation, it is important to ensure that the result of a query expression is never the source object itself, as that would reveal the type and identity of the source to the client of the query. As a consequence, this step protects degenerate queries written directly in source code by explicitly calling Select on the source. It is then up to the implementers of Select and other query operators to ensure that these methods never return the source object itself.

7.15.2.4 from, let, where, join, and orderby Clauses

A query expression with a second from clause followed by a select clause

        from x1 in e1
        from x2 in e2
        select v

is translated into

        ( e1 ) . SelectMany( x1 => e2 , ( x1 , x2 ) => v )

A query expression with a second from clause followed by something other than a select clause

        from x1 in e1
        from x2 in e2
        …

is translated into

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

A query expression with a let clause

        from x in e
        let y = f
        …

is translated into

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

A query expression with a where clause

        from x in e
        where f
        …

is translated into

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

A query expression with a join clause without an into followed by a select clause

        from x1 in e1
        join x2 in e2 on k1 equals k2
        select v

is translated into

        ( e1 ) . Join( e2 , x1 => k1 , x2 => k2 , ( x1 , x2 ) => v )

A query expression with a join clause without an into followed by something other than a select clause

        from x1 in e1
        join x2 in e2 on k1 equals k2
        …

is translated into

        from * in ( e1 ) . Join(
                e2 , x1 => k1 , x2 => k2 , ( x1 , x2 ) => new { x1 , x2 })
        …

A query expression with a join clause with an into followed by a select clause

        from x1 in e1
        join x2 in e2 on k1 equals k2 into g
        select v

is translated into

        ( e1 ) . GroupJoin( e2 , x1 => k1 , x2 => k2 , ( x1 , g ) => v )

A query expression with a join clause with an into followed by something other than a select clause

        from x1 in e1
        join x2 in e2 on k1 equals k2 into g
        …

is translated into

        from * in ( e1 ) . GroupJoin(
               e2 , x1 => k1 , x2 => k2 , ( x1 , g ) => new { x1 , g })
        …

A query expression with an orderby clause

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

is translated into

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

If an ordering clause specifies a descending direction indicator, an invocation of OrderBy-Descending or ThenByDescending is produced instead.

The following translations assume that there are no let, where, join, or orderby clauses, and no more than the one initial from clause in each query expression.

The example

        from c in customers
        from o in c.Orders
        select new { c.Name, o.OrderID, o.Total }

is translated into

        customers.
        SelectMany(c => c.Orders,
                  (c, o) => new { c.Name, o.OrderID, o.Total }
        )

The example

        from c in customers
        from o in c.Orders
        orderby o.Total descending
        select new { c.Name, o.OrderID, o.Total }

is translated into

        from * in customers.
                SelectMany(c => c.Orders, (c, o) => new { c, o })
        orderby o.Total descending
        select new { c.Name, o.OrderID, o.Total }

The final translation is

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

wherex is a compiler-generated identifier that is otherwise invisible and inaccessible.

The example

        from o in orders
        let t = o.Details.Sum(d => d.UnitPrice * d.Quantity)
        where t >= 1000
        select new { o.OrderID, Total = t }

is translated into

        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 }

The final translation is

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

wherex is a compiler-generated identifier that is otherwise invisible and inaccessible.

The example

        from c in customers
        join o in orders on c.CustomerID equals o.CustomerID
        select new { c.Name, o.OrderDate, o.Total }

is translated into

        customers.Join(orders, c => c.CustomerID, o => o.CustomerID,
                (c, o) => new { c.Name, o.OrderDate, o.Total })

The example

        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 }

is translated into

        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 }

The final translation is

        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)

wherex and y are compiler-generated identifiers that are otherwise invisible and inaccessible.

The example

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

has the final translation

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

7.15.2.5 select Clauses

A query expression of the form

        from x in e select v

is translated into

        ( e ) . Select ( x => v )

except when v is the identifier x. In the latter case, the translation is simply

        ( e )

For example,

        from c in customers.Where(c => c.City == "London")
        select c

is simply translated into

        customers.Where(c => c.City == "London")

7.15.2.6 groupby Clauses

A query expression of the form

        from x in e group v by k

is translated into

        ( e ) . GroupBy ( x => k , x => v )

except when v is the identifier x. In the latter case, the translation is

        ( e ) . GroupBy ( x => k )

The example

        from c in customers
        group c.Name by c.Country

is translated into

        customers.
        GroupBy(c => c.Country, c => c.Name)

7.15.2.7 Transparent Identifiers

Certain translations inject range variables with transparent identifiers denoted by *. Transparent identifiers are not a proper language feature; they exist only as an intermediate step in the query expression translation process.

When a query translation injects a transparent identifier, further translation steps propagate the transparent identifier into anonymous functions and anonymous object initializers. In those contexts, transparent identifiers have the following behavior:

•  When a transparent identifier occurs as a parameter in an anonymous function, the members of the associated anonymous type are automatically in scope in the body of the anonymous function.

•  When a member with a transparent identifier is in scope, the members of that member are in scope as well.

•  When a transparent identifier occurs as a member declarator in an anonymous object initializer, it introduces a member with a transparent identifier.

In the translation steps described earlier, transparent identifiers are always introduced together with anonymous types, with the intent of capturing multiple range variables as members of a single object. An implementation of C# is permitted to use a different mechanism than anonymous types to group together multiple range variables. The following translation examples assume that anonymous types are used, and show how transparent identifiers can be translated away.

The example

        from c in customers
        from o in c.Orders
        orderby o.Total descending
        select new { c.Name, o.Total }

is translated into

        from * in customers.
                SelectMany(c => c.Orders, (c, o) => new { c, o })
        orderby o.Total descending
        select new { c.Name, o.Total }

which is further translated into

        customers.
        SelectMany(c => c.Orders, (c, o) => new { c, o }).
        OrderByDescending(* => o.Total).
        Select(* => new { c.Name, o.Total })

When transparent identifiers are erased, the final translation is equivalent to

        customers.
        SelectMany(c => c.Orders, (c, o) => new { c, o }).
        OrderByDescending(x => x.o.Total).
        Select(x => new { x.c.Name, x.o.Total })

wherex is a compiler-generated identifier that is otherwise invisible and inaccessible.

The example

        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 }

is translated into

        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 }

which is further reduced to

        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 { y, p }).
        Select(* => new { c.Name, o.OrderDate, p.ProductName })

The final translation is

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

wherex, y, and z are compiler-generated identifiers that are otherwise invisible and inaccessible.

7.15.3 The Query Expression Pattern

The query expression pattern establishes a pattern of methods that types can implement to support query expressions. Because query expressions are translated to method invocations by means of a syntactic mapping, types have considerable flexibility in how they implement the query expression pattern. For example, the methods of the pattern can be implemented as instance methods or as extension methods because both kinds of methods have the same invocation syntax. Likewise, the methods can request delegates or expression trees because anonymous functions are convertible to both.

The recommended shape of a generic type C<T> that supports the query expression pattern is shown below. A generic type is used to illustrate the proper relationships between parameter and result types, but it is possible to implement the pattern for nongeneric types as well.

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

These methods use the generic delegate types Func<T1, R> and Func<T1, T2, R>, but they could equally well have used other delegate or expression tree types with the same relationships in parameter and result types.

Notice the recommended relationship between C<T> and O<T>, which ensures that the ThenBy and ThenByDescending methods are available only on the result of an OrderBy or OrderByDescending. Also notice the recommended shape of the result of GroupBy—a sequence of sequences, where each inner sequence has an additional Key property.

The System.Linq namespace provides an implementation of the query operator pattern for any type that implements theSystem.Collections.Generic.IEnumerable<T> interface.

7.16 Assignment Operators

The assignment operators assign a new value to a variable, a property, an event, or an indexer element.

      assignment:
             unary-expression assignment-operator expression

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

The left operand of an assignment must be an expression classified as a variable, a property access, an indexer access, or an event access.

The = operator is called the simple assignment operator. It assigns the value of the right operand to the variable, property, or indexer element given by the left operand. The left operand of the simple assignment operator may not be an event access (except as described in §10.8.1). The simple assignment operator is described in §7.16.1.

The assignment operators other than the = operator are called compound assignment operators. These operators perform the indicated operation on the two operands, and then assign the resulting value to the variable, property, or indexer element given by the left operand. The compound assignment operators are described in §7.16.2.

The += and -= operators with an event access expression as the left operand are called event assignment operators. No other assignment operator is valid with an event access as the left operand. The event assignment operators are described in §7.16.3.

The assignment operators are right-associative, meaning that operations are grouped from right to left. For example, an expression of the form a = b = c is evaluated as a = (b = c).

7.16.1 Simple Assignment

The = operator is called the simple assignment operator. In a simple assignment, the right operand must be an expression of a type that is implicitly convertible to the type of the left operand. The operation assigns the value of the right operand to the variable, property, or indexer element given by the left operand.

The result of a simple assignment expression is the value assigned to the left operand. The result has the same type as the left operand and is always classified as a value.

If the left operand is a property or indexer access, the property or indexer must have a set accessor. If this is not the case, a compile-time error occurs.

The runtime processing of a simple assignment of the form x = y consists of the following steps:

•  If x is classified as a variable:

-  x is evaluated to produce the variable.

-  y is evaluated and, if required, converted to the type of x through an implicit conversion (§6.1).

-  If the variable given by x is an array element of a reference-type, a runtime check is performed to ensure that the value computed for y is compatible with the array instance of which x is an element. The check succeeds if y is null, or if an implicit reference conversion (§6.1.6) exists from the actual type of the instance referenced by y to the actual element type of the array instance containing x. Otherwise, a System. ArrayTypeMismatchException is thrown.

-   The value resulting from the evaluation and conversion of y is stored into the location given by the evaluation of x.

•  If x is classified as a property or indexer access:

-   The instance expression (if x is not static) and the argument list (if x is an indexer access) associated with x are evaluated, and the results are used in the subsequent set accessor invocation.

-  y is evaluated and, if required, converted to the type of x through an implicit conversion (§6.1).

-   The set accessor of x is invoked with the value computed for y as its value argument.

The array covariance rules (§12.5) permit a value of an array typeA[] to be a reference to an instance of an array type B[ ], provided an implicit reference conversion exists from B to A. Because of these rules, assignment to an array element of a reference-type requires a runtime check to ensure that the value being assigned is compatible with the array instance. In the example

        string[ ] sa = new string[10];
        object[ ] oa = sa;

        oa[0] = null;                                        // Okay
        oa[1] = "Hello";                                // Okay
        oa[2] = new ArrayList();                 // ArrayTypeMismatchException

the last assignment causes a System.ArrayTypeMismatchException to be thrown because an instance of ArrayList cannot be stored in an element of a string[ ].

When a property or indexer declared in a struct-type is the target of an assignment, the instance expression associated with the property or indexer access must be classified as a variable. If the instance expression is classified as a value, a compile-time error occurs. Because of the points raised in §7.5.4, the same rule also applies to fields.

Given the declarations:

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

in the example

        Point p = new Point();
        p.X = 100;
        p.Y = 100;
        Rectangle r = new Rectangle();
        r.A = new Point(10, 10);
        r.B = p;

the assignments to p.X, p.Y, r.A, and r.B are permitted because p and r are variables. However, in the example

        Rectangle r = new Rectangle();
        r.A.X = 10;
        r.A.Y = 10;
        r.B.X = 100;
        r.B.Y = 100;

the assignments are all invalid, because r.A and r.B are not variables.

7.16.2 Compound Assignment

An operation of the form x op= y is processed by applying binary operator overload resolution (§7.2.4) as if the operation was written x op y. Then,

If the return type of the selected operator is implicitly convertible to the type of x, the operation is evaluated as x = x op y, except that x is evaluated only once.

•  Otherwise, if the selected operator is a predefined operator, if the return type of the selected operator is explicitly convertible to the type of x, and if y is implicitly convertible to the type of x or the operator is a shift operator, then the operation is evaluated as x = (T)(x op y), where T is the type of x, except that x is evaluated only once.

•  Otherwise, the compound assignment is invalid, and a compile-time error occurs.

The term “evaluated only once” means that in the evaluation of x op y, the results of any constituent expressions of x are temporarily saved and then reused when performing the assignment to x. For example, in the assignment A()[B()] += C(), where A is a method returning int[ ], and B and C are methods returning int, the methods are invoked only once, in the order A, B, C.

When the left operand of a compound assignment is a property access or indexer access, the property or indexer must have both a get accessor and a set accessor. If this is not the case, a compile-time error occurs.

The second rule permits x op= y to be evaluated as x = (T)(x op y) in certain contexts. The rule exists such that the predefined operators can be used as compound operators when the left operand is of type sbyte, byte, short, ushort, or char. Even when both arguments are of one of those types, the predefined operators produce a result of type int, as described in §7.2.6.2. Thus, without a cast, it would not be possible to assign the result to the left operand.

The intuitive effect of the rule for predefined operators is simply that x op= y is permitted if both of x op y and x = y are permitted. In the example

        byte b = 0;
        char ch = '';
        int i = 0;
        b += 1;                        // Okay
        b += 1000;                // Error, b = 1000 not permitted
        b += i;                        // Error, b = i not permitted
        b += (byte)i;              // Okay
        ch += 1;                    // Error, ch = 1 not permitted
        ch += (char)1;         // Okay

the intuitive reason for each error is that a corresponding simple assignment would also have been an error.

This also means that compound assignment operations support lifted operations. In the example

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

the lifted operator +(int?,int?) is used.

7.16.3 Event Assignment

If the left operand of a += or -= operator is classified as an event access, then the expression is evaluated as follows:

•  The instance expression, if any, of the event access is evaluated.

•  The right operand of the += or -= operator is evaluated and, if required, converted to the type of the left operand through an implicit conversion (§6.1).

•  An event accessor of the event is invoked, with an argument list consisting of the right operand, after evaluation and, if necessary, conversion. If the operator was+=, the add accessor is invoked; if the operator was -=, the remove accessor is invoked.

An event assignment expression does not yield a value. Thus an event assignment expression is valid only in the context of a statement-expression8.6).

7.17 Expressions

An expression is either a non-assignment-expression or an assignment.

     expression:
             non-assignment-expression
             assignment

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

7.18 Constant Expressions

A constant-expression is an expression that can be fully evaluated at compile time.

        constant-expression:
                expression

A constant expression must be the null literal or a value with one of the following types: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, string, or any enumeration type. Only the following constructs are permitted in constant expressions:

•  Literals (including the null literal)

•  References to const members of class and struct types

•  References to members of enumeration types

•  References to const parameters or local variables

•  Parenthesized subexpressions, which are themselves constant expressions

•  Cast expressions, provided the target type is one of the types listed above

•  checked and unchecked expressions

•  Default value expressions

•  The predefined +, !, and ~ unary operators

•  The predefined +, *, /, %, <<, >>, &, |, ^, &&, ||, ==, !=, <, >, <=, and >= binary operators, provided each operand is of a type listed above

•  The ?: conditional operator

The following conversions are permitted in constant expressions:

•  Identity conversions

•  Numeric conversions

•  Enumeration conversions

•  Constant expression conversions

•  Implicit and explicit reference conversions, provided that the source of the conversions is a constant expression that evaluates to the null value

Other conversions including boxing, unboxing, and implicit reference conversions of non-null values are not permitted in constant expressions. For example,

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

In this example, the initialization of i is an error because a boxing conversion is required. The initialization of str is an error because an implicit reference conversion from a non-null value is required.

Whenever an expression fulfills the requirements listed above, the expression is evaluated at compile time. This is true even if the expression is a subexpression of a larger expression that contains nonconstant constructs.

The compile-time evaluation of constant expressions uses the same rules as runtime evaluation of nonconstant expressions, except that where runtime evaluation would have thrown an exception, compile-time evaluation causes a compile-time error to occur.

Unless a constant expression is explicitly placed in an unchecked context, overflows that occur in integral-type arithmetic operations and conversions during the compile-time evaluation of the expression always cause compile-time errors (§7.18).

Constant expressions occur in the contexts listed below. In these contexts, a compile-time error occurs if an expression cannot be fully evaluated at compile time.

•  Constant declarations (§10.4)

•  Enumeration member declarations (§14.3)

•  case labels of a switch statement (§8.7.2)

•  goto case statements (§8.9.3)

•  Dimension lengths in an array creation expression (§7.5.10.4) that includes an initializer

•  Attributes (§17)

An implicit constant expression conversion (§6.1.8) permits a constant expression of type int to be converted to sbyte, byte, short, ushort, uint, or ulong, provided the value of the constant expression is within the range of the destination type.

7.19 Boolean Expressions

A boolean-expression is an expression that yields a result of type bool, either directly or through application of operator true in certain contexts as specified in the following discussion.

      boolean-expression:
            expression

The controlling conditional expression of an if-statement8.7.1), while-statement8.8.1), do-statement8.8.2), or for-statement8.8.3) is a boolean-expression. The controlling conditional expression of the ?: operator (§7.13) follows the same rules as a boolean-expression, but for reasons of operator precedence is classified as a conditional-or-expression.

A boolean-expression is required to be of a type that can be implicitly converted to bool or of a type that implements operator true. If neither requirement is satisfied, a compile-time error occurs.

When a boolean expression is of a type that cannot be implicitly converted to bool but does implement operator true, then following evaluation of the expression, the operator true implementation provided by that type is invoked to produce a bool value.

The DBBool struct type in §11.4.2 provides an example of a type that implements operator true and operator false.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset