7. Expressions

An expression is a sequence of operators and operands. This chapter defines the syntax, order of evaluation of operands and operators, and meaning of expressions.

7.1 Expression Classifications

An expression is classified as one of the following:

•  A value. Every value has an associated type.

•  A variable. Every variable has an associated type—namely, the declared type of the variable.

•  A namespace. An expression with this classification can appear only as the left-hand side member-access7.5.4) or as an operand for the as operator (§7.9.11), the is operator (§7.9.10), or the typeof operator (§7.5.11). In any other context, an expression classified as a type causes a compile-time error.

•  A method group, which is a set of overloaded methods resulting from a member lookup (§7.3). A method group may have an associated instance expression and an associated type argument list. When an instance method is invoked, the result of evaluating the instance expression becomes the instance represented by this7.5.7). A method group is permitted in an invocation-expression7.5.5), a delegate-creation-expression7.5.10.5), and as the left-hand side of an is operator; it can also be implicitly converted to a compatible delegate type (§6.6). In any other context, an expression classified as a method group causes a compile-time error.

•  A null literal. An expression with this classification can be implicitly converted to a reference type or a nullable type.

•  An anonymous function. An expression with this classification can be implicitly converted to a compatible delegate type or expression tree type.

•  A property access. Every property access has an associated type—namely, the type of the property. Furthermore, a property access may have an associated instance expression. When an accessor (the get or set block) of an instance property access is invoked, the result of evaluating the instance expression becomes the instance represented by this7.5.7).

•  An event access. Every event access has an associated type—namely, the type of the event. Furthermore, an event access may have an associated instance expression. An event access may appear as the left-hand operand of the += and -= operators (§7.16.3). In any other context, an expression classified as an event access causes a compile-time error.

•  An indexer access. Every indexer access has an associated type—namely, the element type of the indexer. Furthermore, an indexer access has an associated instance expression and an associated argument list. When an accessor (the get or set block) of an indexer access is invoked, the result of evaluating the instance expression becomes the instance represented by this7.5.7), and the result of evaluating the argument list becomes the parameter list of the invocation.

•  Nothing. This occurs when the expression is an invocation of a method with a return type of void. An expression classified as nothing is valid only in the context of a statement-expression8.6).

The final result of an expression is never a namespace, type, method group, or event access. Rather, as noted previously, these categories of expressions are intermediate constructs that are permitted only in certain contexts.

A property access or indexer access is always reclassified as a value by performing an invocation of the get-accessor or the set-accessor. The particular accessor is determined by the context of the property or indexer access: If the access is the target of an assignment, the set-accessor is invoked to assign a new value (§7.16.1). Otherwise, the get-accessor is invoked to obtain the current value (§7.1.1).

7.1.1 Values of Expressions

Most of the constructs that involve an expression ultimately require the expression to denote a value. In such cases, if the actual expression denotes a namespace, a type, a method group, or nothing, a compile-time error occurs. However, if the expression denotes a property access, an indexer access, or a variable, then the value of the property, indexer, or variable is implicitly substituted:

•  The value of a variable is simply the value currently stored in the storage location identified by the variable. A variable must be considered definitely assigned (§5.3) before its value can be obtained; otherwise, a compile-time error occurs.

•  The value of a property access expression is obtained by invoking the get-accessor of the property. If the property has no get-accessor, a compile-time error occurs. Otherwise, a function member invocation (§7.4.4) is performed, and the result of the invocation becomes the value of the property access expression.

•  The value of an indexer access expression is obtained by invoking the get-accessor of the indexer. If the indexer has no get-accessor, a compile-time error occurs. Otherwise, a function member invocation (§7.4.4) is performed with the argument list associated with the indexer access expression, and the result of the invocation becomes the value of the indexer access expression.

7.2 Operators

Expressions are constructed from operands and operators. The operators of an expression indicate which operations to apply to the operands. Examples of operators include +, -, *, /, and new. Examples of operands include literals, fields, local variables, and expressions.

There are three kinds of operators:

•  Unary operators. The unary operators take one operand and use either prefix notation (such as –x) or postfix notation (such as x++).

•  Binary operators. The binary operators take two operands and all use infix notation (such as x + y).

•  Ternary operator. Only one ternary operator (?:) exists; it takes three operands and uses infix notation (c? x: y).

The order of evaluation of operators in an expression is determined by the precedence and associativity of the operators (§7.2.1).

Operands in an expression are evaluated from left to right. For example, in F(i) + G(i++) * H(i), method F is called using the old value of i, then method G is called with the old value of i, and finally method H is called with the new value of i. This evaluation order is separate from and unrelated to operator precedence.

Certain operators can be overloaded. Operator overloading permits user-defined operator implementations to be specified for operations where one or both of the operands are of a user-defined class or struct type (§7.2.2).

7.2.1 Operator Precedence and Associativity

When an expression contains multiple operators, the precedence of the operators controls the grouping of the individual expressions into operands. For example, the expression x + y * z is evaluated as x + (y * z) because the * operator has higher precedence than the binary + operator. The precedence of an operator is established by the definition of its associated grammar production. For example, an additive-expression consists of a sequence of multiplicative-expressions separated by + or - operators, thus giving the + and - operators lower precedence than the *, /, and % operators.

The following table summarizes all operators in order of precedence from highest to lowest.

image

When an operand occurs between two operators with the same precedence, the associativity of the operators controls the grouping of the expressions into operands:

•  Except for the assignment operators, all binary operators are left-associative, meaning that operations are grouped from left to right. For example, x + y + z is evaluated as (x + y) + z.

•  The assignment operators and the conditional operator (?:) are right-associative, meaning that operations are grouped from right to left. For example, x = y = z is evaluated as x = (y = z).

Precedence and associativity can be controlled using parentheses. For example, x + y * z first multiplies y by z and then adds the result to x, but (x + y) * z first adds x and y and then multiplies the result by z.

7.2.2 Operator Overloading

All unary and binary operators have predefined implementations that are automatically available in any expression. In addition to the predefined implementations, user-defined implementations can be introduced by including operator declarations in classes and structs (§10.10). User-defined operator implementations always take precedence over predefined operator implementations: Only when no applicable user-defined operator implementations exist will the predefined operator implementations be considered, as described in §7.2.3 and §7.2.4.

The overloadable unary operators are

        +   -   !   ~   ++   --   true false

Although true and false are not used explicitly in expressions (and, therefore, are not included in the precedence table in §7.2.1), they are considered operators because they are invoked in several expression contexts: boolean expressions (§7.19), and expressions involving the conditional (§7.13), and conditional logical operators (§7.11).

The overloadable binary operators are

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

Only the operators listed here can be overloaded. In particular, it is not possible to overload member access, method invocation, or the =, &&, ||, ??, ?:, =>, checked, unchecked, new, typeof, default, as, and is operators.

When a binary operator is overloaded, the corresponding assignment operator, if any, is also implicitly overloaded. For example, an overload of operator * is also an overload of operator *=. This issue is explained further in §7.16.2. Note that the assignment operator itself (=) cannot be overloaded. An assignment always performs a simple bitwise copy of a value into a variable.

Cast operations, such as (T)x, are overloaded by providing user-defined conversions (§6.4).

Element access, such as a[x], is not considered an overloadable operator. Instead, user-defined indexing is supported through indexers (§10.9).

In expressions, operators are referenced using operator notation. In declarations, operators are referenced using functional notation. The following table shows the relationship between operator and functional notations for unary and binary operators. In the first entry, op denotes any overloadable unary prefix operator. In the second entry, op denotes the unary postfix ++ and -- operators. In the third entry, op denotes any overloadable binary operator.

        Image

User-defined operator declarations always require at least one of the parameters to be of the class or struct type that contains the operator declaration. Thus it is not possible for a user-defined operator to have the same signature as a predefined operator.

User-defined operator declarations cannot modify the syntax, precedence, or associativity of an operator. For example, the / operator is always a binary operator, always has the precedence level specified in §7.2.1, and is always left-associative.

While it is possible for a user-defined operator to perform any computation it pleases, implementations that produce results other than those that are intuitively expected are strongly discouraged. For example, an implementation of operator == should compare the two operands for equality and return an appropriate bool result.

The descriptions of individual operators in §7.5 through §7.11 specify the predefined implementations of the operators and any additional rules that apply to each operator. The descriptions make use of the terms unary operator overload resolution, binary operator overload resolution, and numeric promotion, definitions of which are found in the following sections.

7.2.3 Unary Operator Overload Resolution

An operation of the form op x or x op, where op is an overloadable unary operator, and x is an expression of type X, is processed as follows:

•  The set of candidate user-defined operators provided by X for the operation operator op(x) is determined using the rules of §7.2.5.

•  If the set of candidate user-defined operators is not empty, then it becomes the set of candidate operators for the operation. Otherwise, the predefined unary operator op implementations, including their lifted forms, become the set of candidate operators for the operation. The predefined implementations of a given operator are specified in the description of the operator (§7.5 and §7.6).

•  The overload resolution rules of §7.4.3 are applied to the set of candidate operators to select the best operator with respect to the argument list (x), and this operator becomes the result of the overload resolution process. If overload resolution fails to select a single best operator, a compile-time error occurs.

7.2.4 Binary Operator Overload Resolution

An operation of the form x op y, where op is an overloadable binary operator, x is an expression of type X, and y is an expression of type Y, is processed as follows:

•  The set of candidate user-defined operators provided by X and Y for the operation operator op(x, y) is determined. The set consists of the union of the candidate operators provided by X and the candidate operators provided by Y, each determined using the rules of §7.2.5. If X and Y are the same type, or if X and Y are derived from a common base type, then shared candidate operators occur in the combined set only once.

•  If the set of candidate user-defined operators is not empty, then it becomes the set of candidate operators for the operation. Otherwise, the predefined binary operator op implementations, including their lifted forms, become the set of candidate operators for the operation. The predefined implementations of a given operator are specified in the description of the operator (§7.7 through §7.11).

•  The overload resolution rules of §7.4.3 are applied to the set of candidate operators to select the best operator with respect to the argument list (x, y), and this operator becomes the result of the overload resolution process. If overload resolution fails to select a single best operator, a compile-time error occurs.

7.2.5 Candidate User-Defined Operators

Given a type T and an operation operator op(A), where op is an overloadable operator and A is an argument list, the set of candidate user-defined operators provided by T for operator op(A) is determined as follows:

•  Determine the type T0. If T is a nullable type, T0 is its underlying type; otherwise, T0 is equal to T.

•  For all operator op declarations in T0 and all lifted forms of such operators, if at least one operator is applicable (§7.4.3.1) with respect to the argument list A, then the set of candidate operators consists of all such applicable operators in T0.

•  Otherwise, if T0 is object, the set of candidate operators is empty.

•  Otherwise, the set of candidate operators provided by T0 is the set of candidate operators provided by the direct base class of T0, or the effective base class of T0 if T0 is a type parameter.

7.2.6 Numeric Promotions

•  Numeric promotion consists of automatically performing certain implicit conversions of the operands of the predefined unary and binary numeric operators. It is not a distinct mechanism, but rather an effect of applying overload resolution to the predefined operators. Numeric promotion specifically does not affect evaluation of user-defined operators, although user-defined operators can be implemented to exhibit similar effects.

As an example of numeric promotion, consider the predefined implementations of the binary * operator:

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

When overload resolution rules (§7.4.3) are applied to this set of operators, the effect is to select the first of the operators for which implicit conversions exist from the operand types.

For example, for the operation b * s, where b is a byte and s is a short, overload resolution selects operator *(int, int) as the best operator. Thus the effect is that b and s are converted to int, and the type of the result is int. Likewise, for the operation i * d, where i is an int and d is a double, overload resolution selects operator *(double, double) as the best operator.

7.2.6.1 Unary Numeric Promotions

Unary numeric promotion occurs for the operands of the predefined +, and ~ unary operators. Unary numeric promotion simply consists of converting operands of type sbyte, byte, short, ushort, or char to type int. Additionally, for the unary operator, unary numeric promotion converts operands of type uint to type long.

7.2.6.2 Binary Numeric Promotions

Binary numeric promotion occurs for the operands of the predefined +, *, /, %, &, |, ^, ==, !=, >, <, >=, and <= binary operators. Binary numeric promotion implicitly converts both operands to a common type that, in case of the nonrelational operators, also becomes the result type of the operation. Binary numeric promotion consists of applying the following rules, in the order they appear here:

•  If either operand is of type decimal, the other operand is converted to type decimal, or a compile-time error occurs if the other operand is of type float or double.

•  Otherwise, if either operand is of type double, the other operand is converted to type double.

•  Otherwise, if either operand is of type float, the other operand is converted to type float.

•  Otherwise, if either operand is of type ulong, the other operand is converted to type ulong, or a compile-time error occurs if the other operand is of type sbyte, short, int, or long.

•  Otherwise, if either operand is of type long, the other operand is converted to type long.

•  Otherwise, if either operand is of type uint and the other operand is of type sbyte, short, or int, both operands are converted to type long.

•  Otherwise, if either operand is of type uint, the other operand is converted to type uint.

•  Otherwise, both operands are converted to type int.

Note that the first rule disallows any operations that mix the decimal type with the double and float types. This rule follows from the fact that there are no implicit conversions between the decimal type and the double and float types.

Also note that it is not possible for an operand to be of type ulong when the other operand is of a signed integral type. No integral type exists that can represent the full range of ulong as well as the signed integral types.

In both of the above cases, a cast expression can be used to explicitly convert one operand to a type that is compatible with the other operand.

In the example

        decimal AddPercent(decimal x, double percent) {
                 return x * (1.0 + percent / 100.0);
        }

a compile-time error occurs because a decimal cannot be multiplied by a double. The error is resolved by explicitly converting the second operand to decimal as follows:

        decimal AddPercent(decimal x, double percent) {
                 return x * (decimal)(1.0 + percent / 100.0);
        }

7.2.7 Lifted Operators

Lifted operators permit predefined and user-defined operators that operate on non-nullable value types to also be used with nullable forms of those types. Lifted operators are constructed from predefined and user-defined operators that meet certain requirements, as described here:

•  For the unary operators

        +   ++   -   --   !   ~

a lifted form of an operator exists if the operand and result types are both non-nullable value types. The lifted form is constructed by adding a single ? modifier to the operand and result types. The lifted operator produces a null value if the operand is null. Otherwise, the lifted operator unwraps the operand, applies the underlying operator, and wraps the result.

•  For the binary operators

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

a lifted form of an operator exists if the operand and result types are all non-nullable value types. The lifted form is constructed by adding a single ? modifier to each operand and result type. The lifted operator produces a null value if one or both operands are null (an exception being the & and | operators of the bool? type, as described in §7.10.3). Otherwise, the lifted operator unwraps the operands, applies the underlying operator, and wraps the result.

•  For the equality operators

        ==   !=

a lifted form of an operator exists if the operand types are both non-nullable value types and if the result type is bool. The lifted form is constructed by adding a single ? modifier to each operand type. The lifted operator considers two null values equal, and a null value unequal to any non-null value. If both operands are non-null, the lifted operator unwraps the operands and applies the underlying operator to produce the bool result.

•  For the relational operators

        <   >   <=   >=

a lifted form of an operator exists if the operand types are both non-nullable value types and if the result type is bool. The lifted form is constructed by adding a single ? modifier to each operand type. The lifted operator produces the value false if one or both operands are null. Otherwise, the lifted operator unwraps the operands and applies the underlying operator to produce the bool result.

7.3 Member Lookup

A member lookup is the process whereby the meaning of a name in the context of a type is determined. A member lookup can occur as part of evaluating a simple-name7.5.2) or a member-access7.5.4) in an expression. If the simple-name or member-access occurs as the simple-expression of an invocation-expression7.5.5.1), the member is said to be invoked.

If a member is a method or event, or if it is a constant, field, or property of a delegate type (§15), then the member is said to be invocable.

•  Member lookup considers not only the name of a member, but also the number of type parameters the member has and whether the member is accessible. For the purposes of member lookup, generic methods and nested generic types have the number of type parameters indicated in their respective declarations and all other members have zero type parameters.

A member lookup of a name N with K type parameters in a type T is processed as follows:

•  A set of accessible members named N is determined:

-  If T is a type parameter, then the set is the union of the sets of accessible members named N in each of the types specified as a primary constraint or secondary constraint (§10.1.5) for T, along with the set of accessible members named N in object.

-  Otherwise, the set consists of all accessible (§3.5) members named N in T, including inherited members and the accessible members named N in object. If T is a constructed type, the set of members is obtained by substituting type arguments as described in §10.3.2. Members that include an override modifier are excluded from the set.

•  If K is zero, all nested types whose declarations include type parameters are removed. If K is not zero, all members with a different number of type parameters are removed. Note that when K is zero, methods having type parameters are not removed, because the type inference process (§7.4.2) might be able to infer the type arguments.

•  If the member is invoked, all non-invocable members are removed from the set.

Members that are hidden by other members are removed from the set. For every member S.M in the set, where S is the type in which the member M is declared, the following rules are applied:

-  If M is a constant, field, property, event, or enumeration member, then all members declared in a base type of S are removed from the set.

-  If M is a type declaration, then all nontypes declared in a base type of S are removed from the set, and all type declarations with the same number of type parameters as M declared in a base type of S are removed from the set.

-  If M is a method, then all nonmethod members declared in a base type of S are removed from the set.

•  Interface members that are hidden by class members are removed from the set. This step has an effect only if T is a type parameter and T has both an effective base class other than object and a nonempty effective interface set (§10.1.5). For every member S.M in the set, where S is the type in which the member M is declared, the following rules are applied if S is a class declaration other than object:

-  If M is a constant, field, property, event, enumeration member, or type declaration, then all members declared in an interface declaration are removed from the set.

-  If M is a method, then all nonmethod members declared in an interface declaration are removed from the set, and all methods with the same signature as M declared in an interface declaration are removed from the set.

•  Having removed hidden members, the result of the lookup is determined:

-  If the set consists of a single member that is not a method, then this member is the -result of the lookup.

-  Otherwise, if the set contains only methods, then this group of methods is the result -of the lookup.

-  Otherwise, the lookup is ambiguous, and a compile-time error occurs.

For member lookups in types other than type parameters and interfaces, and member lookups in interfaces that strictly follow a single-inheritance scheme (each interface is the inheritance chain has exactly zero or one direct base interface), the effect of the lookup rules is simply that derived members hide base members with the same name or signature. Such single-inheritance lookups are never ambiguous. The ambiguities that can possibly arise from member lookups in multiple-inheritance interfaces are described in §13.2.5.

7.3.1 Base Types

For purposes of member lookup, a type T is considered to have the following base types:

•  If T is object, then T has no base type.

•  If T is an enum-type, then the base types of T are the class types System.Enum, System. ValueType, and object.

•  If T is a struct-type, then the base types of T are the class types System.ValueType and object.

•  If T is a class-type, then the base types of T are the base classes of T, including the class type object.

•  If T is an interface-type, then the base types of T are the base interfaces of T and the class type object.

•  If T is an array-type, then the base types of T are the class types System.Array and object.

•  If T is a delegate-type, then the base types of T are the class types System.Delegate and object.

7.4 Function Members

Function members are members that contain executable statements. Function members are always members of types and cannot be members of namespaces. C# defines the following categories of function members:

•  Methods

•  Properties

•  Events

•  Indexers

•  User-defined operators

•  Instance constructors

•  Static constructors

•  Destructors

Except for destructors and static constructors (which cannot be invoked explicitly), the statements contained in function members are executed through function member invocations. The actual syntax for writing a function member invocation depends on the particular function member category.

The argument list (§7.4.1) of a function member invocation provides actual values or variable references for the parameters of the function member.

Invocations of methods, indexers, operators, and instance constructors employ overload resolution to determine which of a candidate set of function members to invoke. This process is described in §7.4.3.

Once a particular function member has been identified at compile time, possibly through overload resolution, the actual runtime process of invoking the function member follows that described in §7.4.4.

The following table summarizes the processing that takes place in constructs involving the six categories of function members that can be explicitly invoked. In the table, e, x, y, and value indicate expressions classified as variables or values, T indicates an expression classified as a type, F is the simple name of a method, and P is the simple name of a property.

Image

Image

7.4.1 Argument Lists

Every function member or delegate invocation includes an argument list that provides actual values or variable references for the parameters of the function member. The syntax for specifying the argument list of a function member invocation depends on the function member category:

•  For instance constructors, methods, and delegates, the arguments are specified as an argument-list, as described later in this section.

•  For properties, the argument list is empty when invoking the get accessor, and consists of the expression specified as the right operand of the assignment operator when invoking the set accessor.

•  For events, the argument list consists of the expression specified as the right operand of the += or -= operator.

•  For indexers, the argument list consists of the expressions specified between the square brackets in the indexer access. When invoking the set accessor, the argument list also includes the expression specified as the right operand of the assignment operator.

•  For user-defined operators, the argument list consists of the single operand of the unary operator or the two operands of the binary operator.

The arguments of properties (§10.7), events (§10.8), and user-defined operators (§10.10) are always passed as value parameters (§10.6.1.1). parameter arrays (§10.6.1.4). Reference and output parameters are not supported for these categories of function members.

The arguments of an instance constructor, method, or delegate invocation are specified as an argument-list:

      argument-list:
             argument
             argument-list , argument

       argument:
             expression
             ref variable-reference
             out variable-reference

An argument-list consists of one or more arguments, separated by commas. Each argument can take one of the following forms:

•  An expression, indicating that the argument is passed as a value parameter (§10.6.1.1).

•  The keyword ref followed by a variable-reference5.4), indicating that the argument is passed as a reference parameter (§10.6.1.2). A variable must be definitely assigned (§5.3) before it can be passed as a reference parameter. The keyword out, followed by a variable-reference5.4) indicating that the argument, is passed as an output parameter (§10.6.1.3). A variable is considered definitely assigned (§5.3) following a function member invocation in which the variable is passed as an output parameter.

During the runtime processing of a function member invocation (§7.4.4), the expressions or variable references of an argument list are evaluated in order, from left to right, as follows:

•  For a value parameter, the argument expression is evaluated and an implicit conversion (§6.1) to the corresponding parameter type is performed. The resulting value becomes the initial value of the value parameter in the function member invocation.

•  For a reference or output parameter, the variable reference is evaluated and the resulting storage location becomes the storage location represented by the parameter in the function member invocation. If the variable reference given as a reference or output parameter is an array element of a reference-type, a runtime check is performed to ensure that the element type of the array is identical to the type of the parameter. If this check fails, a System.ArrayTypeMismatchException is thrown.

Methods, indexers, and instance constructors may declare their rightmost parameter to be a parameter array (§10.6.1.4). Such function members are invoked either in their normal form or in their expanded form, depending on which form is applicable (§7.4.3.1):

•  When a function member with a parameter array is invoked in its normal form, the argument given for the parameter array must be a single expression of a type that is implicitly convertible (§6.1) to the parameter array type. In this case, the parameter array acts precisely like a value parameter.

•  When a function member with a parameter array is invoked in its expanded form, the invocation must specify zero or more arguments for the parameter array, where each argument is an expression of a type that is implicitly convertible (§6.1) to the element type of the parameter array. In this case, the invocation creates an instance of the parameter array type with a length corresponding to the number of arguments, initializes the elements of the array instance with the given argument values, and uses the newly created array instance as the actual argument.

The expressions of an argument list are always evaluated in the order they are written. For example,

         class Test
         {
                 static void F(int x, int y, int z) {
                        System.Console.WriteLine("x = {0}, y = {1}, z = {2}", x, y, z);
                 }
                 static void Main() {
                        int i = 0;
                        F(i++, i++, i++);
                 }
        }

produces the output

        x   =   0,   y   =   1,   z   =   2

The array covariance rules (§12.5) permit a value of an array type A[ ] 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, when an array element of a reference-type is passed as a reference or output parameter, a runtime check is required to ensure that the actual element type of the array is identical to that of the parameter. In the example

        class Test
        {
                 static void F(ref object x) {…}
                 static void Main() {
                        object[ ] a = new object[10];
                        object[ ] b = new string[10];
                        F(ref a[0]);             // Okay
                        F(ref b[1]);             // ArrayTypeMismatchException
                 }
        }

the second invocation of F causes a System.ArrayTypeMismatchException to be thrown because the actual element type of b is string and not object.

When a function member with a parameter array is invoked in its expanded form, the invocation is processed exactly as if an array creation expression with an array initializer (§7.5.10.4) was inserted around the expanded parameters. For example, given the declaration

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

the following invocations of the expanded form of the method

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

correspond exactly to

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

In particular, note that an empty array is created when zero arguments are given for the parameter array.

7.4.2 Type Inference

When a generic method is called without specifying type arguments, a type inference process attempts to infer type arguments for the call. The presence of type inference allows a more convenient syntax to be used for calling a generic method; it also allows the programmer to avoid specifying redundant type information. For example, given the method declaration

        class Chooser
        {
                static Random rand = new Random();
                public static T Choose<T>(T first, T second) {
                        return (rand.Next(2) == 0)? first: second;
                }
       }

it is possible to invoke the Choose method without explicitly specifying a type argument:

        int i = Chooser.Choose(5, 213);                              // Calls Choose<int>
        string s = Chooser.Choose("foo", "bar");            // Calls Choose<string>

Through type inference, the type arguments int and string are determined from the arguments to the method.

Type inference occurs as part of the compile-time processing of a method invocation (§7.5.5.1) and takes place before the overload resolution step of the invocation. When a particular method group is specified in a method invocation, and no type arguments are specified as part of the method invocation, type inference is applied to each generic method in the method group. If type inference succeeds, then the inferred type arguments are used to determine the types of arguments for subsequent overload resolution. If overload resolution chooses to invoke a generic method, then the inferred type arguments for that generic method are used as the actual type arguments for the invocation. If type inference for a particular method fails, that method does not participate in overload resolution. The failure of type inference in and of itself does not cause a compile-time error. However, it often leads to a compile-time error when overload resolution subsequently fails to find any applicable methods.

If the supplied number of arguments differs from the number of parameters in the method, then inference immediately fails. Otherwise, assume that the generic method has the following signature:

        Tr M<X1…Xn>(T1 x1 … Tm xm)

With a method call of the form M(E1…Em), the task of type inference is to find unique type arguments S1…Sn for each of the type parameters X1…Xn so that the call M<S1…Sn>(E1…Em) becomes valid.

During the process of inference, each type parameter Xi is either fixed to a particular type Si or unfixed with an associated set of bounds. Each of the bounds is some type T. Initially, each type parameter Xi is unfixed with an empty set of bounds.

Type inference takes place in phases. Each phase will try to infer type arguments for more type parameters based on the findings of the previous phase. The first phase makes some initial inferences of bounds, whereas the second phase fixes type parameters to specific types and infers further bounds. The second phase may have to be repeated a number of times.

Type inference takes place in situations other than generic method calls. Type inference for conversion of method groups is described in §7.4.2.12, and the process of finding the best common type of a set of expressions is described in §7.4.2.13.

7.4.2.1 The First Phase

For each of the method arguments Ei:

•  If Ei is an anonymous function, an explicit parameter type inference7.4.2.7) is made from Ei toTi

•  Otherwise, an output type inference7.4.2.6) is made from Ei to Ti

7.4.2.2 The Second Phase

The second phase proceeds as follows:

•  All unfixed type parameters Xi that do not depend on7.4.2.5) any Xj are fixed (§7.4.2.10).

•  If no such type parameters exist, all unfixed type parameters Xi are fixed for which all of the following criteria hold:

-  There is at least one type parameter Xj that depends on Xi

-  Xi has a nonempty set of bounds

•  If no such type parameters exist and there are still unfixed type parameters, type inference fails.

•  Otherwise, if no further unfixed type parameters exist, type inference succeeds.

•  Otherwise, for all arguments Ei with corresponding parameter type Ti where the output types7.4.2.4) contain unfixed type parameters Xj but the input types7.4.2.3) do not, an output type inference7.4.2.6) is made from Ei to Ti. Then the second phase is repeated.

7.4.2.3 Input Types

If E is a method group or implicitly typed anonymous function, and T is a delegate type or expression tree type, then all the parameter types of T are input types of E with typeT.

7.4.2.4 Output Types

If E is a method group or an anonymous function, and T is a delegate type or expression tree type, then the return type of T is an output type of E with type T.

7.4.2.5 Dependence

An unfixed type parameter Xi depends directly on an unfixed type parameter Xj if for some argument Ek with type Tk Xj occurs in an input type of Ek with type Tk and Xi occurs in an output type of Ek with type Tk.

Xj depends on Xi if Xj depends directly on Xi or if Xi depends directly on Xk and Xk depends on Xj. Thus depends on is the transitive but not reflexive closure of depends directly on.

7.4.2.6 Output Type Inferences

An output type inference is made from an expression E to a type T in the following way:

•  If E is an anonymous function with inferred return type U7.4.2.11), and T is a delegate type or expression tree type with return type Tb, then a lower-bound inference7.4.2.9) is made from U to Tb.

•  Otherwise, if E is a method group and T is a delegate type or expression tree type with parameter types T1…Tk and return type Tb, and overload resolution of E with the types T1…Tk yields a single method with return type U, then a lower-bound inference is made from U to Tb.

•  Otherwise, if E is an expression with type U, then a lower-bound inference is made from U to T.

•  Otherwise, no inferences are made.

7.4.2.7 Explicit Parameter Type Inferences

An explicit parameter type inference is made from an expression E to a type T in the following way:

•  If E is an explicitly typed anonymous function with parameter types U1…Uk, and T is a delegate type or expression tree type with parameter types V1…Vk, then for each Ui an exact inference7.4.2.8) is made from Ui to the corresponding Vi.

7.4.2.8 Exact Inferences

An exact inference from a type U to a type V is made as follows:

•  If V is one of the unfixed Xi, then U is added to the set of bounds for Xi.

•  Otherwise, if U is an array type Ue[…] and V is an array type Ve[…] of the same rank, then an exact inference from Ue to Ve is made.

•  Otherwise, if V is a constructed type C<V1…Vk> and U is a constructed type C<U1…Uk>, then an exact inference is made from each Ui to the corresponding Vi.

•  Otherwise, no inferences are made.

7.4.2.9 Lower-Bound Inferences

A lower-bound inference from a type U to a type V is made as follows:

•  If V is one of the unfixed Xi, then U is added to the set of bounds for Xi.

•  Otherwise, if U is an array type Ue[…] and V is an array type Ve[…] of the same rank, or if U is a one-dimensional array type Ue[ ] and V is one of IEnumerable<Ve>, ICollection<Ve> or IList<Ve>, then

-   If Ue is known to be a reference type, then a lower-bound inference from Ue to Ve is made

-   Otherwise, an exact inference from Ue to Ve is made

•  Otherwise, if V is a constructed type C<V1…Vk>, and there is a unique set of types U1… Uk such that a standard implicit conversion exists from U to C<U1…Uk>, then an exact inference is made from each Ui to the corresponding Vi.

•  Otherwise, no inferences are made.

7.4.2.10 Fixing

An unfixed type parameter Xi with a set of bounds is fixed as follows:

•  The set of candidate types Uj starts out as the set of all types in the set of bounds for Xi.

•  We then examine each bound for Xi: For each bound U of Xi, all types Uj to which there is not a standard implicit conversion from U are removed from the candidate set.

•  If among the remaining candidate types Uj, there is a unique type V from which there is a standard implicit conversion to all the other candidate types, then Xi is fixed to V.

•  Otherwise, type inference fails.

7.4.2.11 Inferred Return Type

The inferred return type of an anonymous function F is used during type inference and overload resolution. The inferred return type can only be determined for an anonymous function where all parameter types are known, either because they are explicitly given, provided through an anonymous function conversion, or inferred during type inference on an enclosing generic method invocation. The inferred return type is determined as follows:

•  If the body of F is an expression, then the inferred return type of F is the type of that expression.

•  If the body of F is a block and the set of expressions in the block’s return statements has a best common type T7.4.2.13), then the inferred return type of F is T.

•  Otherwise, a return type cannot be inferred for E.

As an example of type inference involving anonymous functions, consider the Select extension method declared in the System.Linq.Enumerable class:

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

Assuming the System.Linq namespace was imported with a using clause, and given a class Customer with a Name property of type string, the Select method can be used to select the names of a list of customers:

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

The extension method invocation (§7.5.5.2) of Select is processed by rewriting the invocation to a static method invocation:

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

Because type arguments were not explicitly specified, type inference is used to infer the type arguments. First, the customers argument is related to the source parameter, inferring T to be Customer. Then, using the anonymous function type inference process described earlier, c is given type Customer, and the expression c.Name is related to the return type of the selector parameter, inferring S to be string. Thus the invocation is equivalent to

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

and the result is of type IEnumerable<string>.

The following example demonstrates how anonymous function type inference allows type information to “flow” between arguments in a generic method invocation. Given the method

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

type inference for the invocation

        double seconds = F("1:15:30", s => TimeSpan.Parse(s), t => t.TotalSeconds);

proceeds as follows: First, the argument "1:15:30" is related to the value parameter, inferring X to be string. Then, the parameter of the first anonymous function, s, is given the inferred type string, and the expression TimeSpan.Parse(s) is related to the return type of f1, inferring Y to be System.TimeSpan. Finally, the parameter of the second anonymous function, t, is given the inferred type System.TimeSpan, and the expression t. TotalSeconds is related to the return type of f2, inferring Z to be double. Thus the result of the invocation is of type double.

7.4.2.12 Type Inference for Conversion of Method Groups

Similar to what happens with calls of generic methods, type inference must also be applied when a method group M containing a generic method is converted to a given delegate type D6.6). Given a method

        Tr M<X1…Xn>(T1 x1 … Tm xm)

and the method group M being assigned to the delegate type D, the task of type inference is to find type arguments S1…Sn so that the expression

        M<S1…Sn>

becomes compatible (§15.1) with D.

Unlike the type inference algorithm for generic method calls, in this case there are only argument types—no argument expressions. In particular, there are no anonymous functions and hence no need for multiple phases of inference.

Instead, all Xi are considered unfixed, and a lower-bound inference is made from each argument type Uj of D to the corresponding parameter type Tj of M. If for any of the Xi no bounds were found, type inference fails. Otherwise, all Xi are fixed to corresponding Si, which are the result of type inference.

7.4.2.13 Finding the Best Common Type of a Set of Expressions

In some cases, a common type needs to be inferred for a set of expressions. In particular, the element types of implicitly typed arrays and the return types of anonymous functions with block bodies are found in this way.

Intuitively, given a set of expressions E1…Em this inference should be equivalent to calling a method

        Tr M<X>(X x1 … X xm)

with Ei as arguments.

More precisely, the inference starts out with an unfixed type parameter X. Output type inferences are then made from each Ei to X. Finally, X is fixed and, if successful, the resulting type S is the resulting best common type for the expressions. If no such S exists, the expressions have no best common type.

7.4.3 Overload Resolution

Overload resolution is a compile-time mechanism for selecting the best function member to invoke given an argument list and a set of candidate function members. Overload resolution selects the function member to invoke in the following distinct contexts within C#:

•  Invocation of a method named in an invocation-expression7.5.5.1)

•  Invocation of an instance constructor named in an object-creation-expression7.5.10.1)

•  Invocation of an indexer accessor through an element-access7.5.6)

•  Invocation of a predefined or user-defined operator referenced in an expression (§7.2.3 and §7.2.4)

Each of these contexts defines the set of candidate function members and the list of arguments in its own unique way, as described in detail in the sections listed previously. For example, the set of candidates for a method invocation does not include methods marked override7.3), and methods in a base class are not candidates if any method in a derived class is applicable (§7.5.5.1).

Once the candidate function members and the argument list have been identified, the selection of the best function member is the same in all cases:

•  Given the set of applicable candidate function members, the best function member in that set is located. If the set contains only one function member, then that function member is the best function member. Otherwise, the best function member is the one function member that is better than all other function members with respect to the given argument list, provided that each function member is compared to all other function members using the rules in §7.4.3.2. If there is not exactly one function member that is better than all other function members, then the function member invocation is ambiguous and a compile-time error occurs.

The following sections define the exact meanings of the terms applicable function member and better function member.

7.4.3.1 Applicable Function Member

A function member is said to be an applicable function member with respect to an argument list A when all of the following are true:

•  The number of arguments in A is identical to the number of parameters in the function member declaration.

•  For each argument in A, the parameter passing mode of the argument (i.e., value, ref, or outA) is identical to the parameter passing mode of the corresponding parameter, and

-   For a value parameter or a parameter array, an implicit conversion (§6.1) exists from -the argument to the type of the corresponding parameter, or

-  For a ref or out parameter, the type of the argument is identical to the type of the corresponding parameter. After all, a ref or out parameter is an alias for the argument passed.

For a function member that includes a parameter array, if the function member is applicable by the preceding rules, it is said to be applicable in its normal form. If a function member that includes a parameter array is not applicable in its normal form, the function member may instead be applicable in its expanded form:

•  The expanded form is constructed by replacing the parameter array in the function member declaration with zero or more value parameters of the element type of the parameter array such that the number of arguments in the argument list A matches the total number of parameters. If A has fewer arguments than the number of fixed parameters in the function member declaration, the expanded form of the function member cannot be constructed and, therefore, is not applicable.

•  Otherwise, the expanded form is applicable if for each argument in A, the parameter passing mode of the argument is identical to the parameter passing mode of the corresponding parameter, and

-  For a fixed value parameter or a value parameter created by the expansion, an implicit -conversion (§6.1) exists from the type of the argument to the type of the corresponding parameter, or

-  For a ref or out parameter, the type of the argument is identical to the type of the corresponding parameter.

7.4.3.2 Better Function Member

Given an argument list A with a set of argument expressions { E1, E2, …, EN } and two applicable function members MP and MQ with parameter types { P1, P2, …, PN } and { Q1, Q2, …, QN }, MP is defined to be a better function member than MQ if

•  For each argument, the implicit conversion from EX to QX is not better than the implicit conversion from EX to PX, and

•  For at least one argument, the conversion from EX to PX is better than the conversion from EX to QX.

When performing this evaluation, if MP or MQ is applicable in its expanded form, then PX or QX refers to a parameter in the expanded form of the parameter list.

If the parameter type sequences {P1, P2, …, PN} and {Q1, Q2, …, QN} are identical, then the following tie-breaking rules are applied, in order, to determine the better function member.

•  If MP is a nongeneric method and MQ is a generic method, then MP is better than MQ.

•  Otherwise, if MP is applicable in its normal form and MQ has a params array and is applicable only in its expanded form, then MP is better than MQ.

•  Otherwise, if MP has fewer declared parameters than MQ, then MP is better than MQ. This can occur if both methods have params arrays and are applicable only in their expanded forms.

•  Otherwise, if MP has more specific parameter types than MQ, then MP is better than MQ. Let {R1, R2, …, RN} and {S1, S2, …, SN} represent the uninstantiated and unexpanded parameter types of MP and MQ. MP’s parameter types are more specific than MQ’s parameter types if, for each parameter, RX is not less specific than SX, and, for at least one parameter, RX is more specific than SX:

-  A type parameter is less specific than a type that is not a type parameter.

-  Recursively, a constructed type is more specific than another constructed type (with -the same number of type arguments) if at least one type argument is more specific and no type argument is less specific than the corresponding type argument in the other.

-  An array type is more specific than another array type (with the same number of -dimensions) if the element type of the first is more specific than the element type of the second.

•  Otherwise, if one member is a nonlifted operator and the other is a lifted operator, the nonlifted operator is better.

•  Otherwise, neither function member is better.

7.4.3.3 Better Conversion from an Expression

Given an implicit conversion C1 that converts from an expression E to a type T1, and an implicit conversion C2 that converts from an expression E to a type T2, C1 is a better conversion than C2 if T1 and T2 are different types and at least one of the following holds:

•  E has a type S and the conversion from S to T1 is better than the conversion from S to T2.

•  E is an anonymous function, T1 and T2 are delegate types or expression tree types with identical parameter lists, an inferred return type X exists for E in the context of that parameter list (§7.4.2.11), and one of the following holds:

-  T1 has a return type Y1, and T2 has a return type Y2, and the conversion from X to Y1 is better than the conversion from X to Y2.

-  T1 has a return type Y, and T2 is void returning.

7.4.3.4 Better Conversion from a Type

Given a conversion C1 that converts from a type S to a type T1, and a conversion C2 that converts from a type S to a type T2, C1 is a better conversion than C2 if T1 and T2 are different types and at least one of the following conditions holds:

•  S is T1.

•  An implicit conversion from T1 to T2 exists, and no implicit conversion from T2 to T1 exists.

•  T1 is a signed integral type and T2 is an unsigned integral type. Specifically:

-  T1 is sbyte and T2 is byte, ushort, uint, or ulong.

-  T1 is short and T2 is ushort, uint, or ulong.

-  T1 is int and T2 is uint, or ulong.

-  T1 is long and T2 is ulong.

Note that this may define a conversion to be better even in cases where no implicit conversion is defined. For instance, the conversion of the expression 6 to short is better than the conversion of 6 to ushort, because a conversion of any type to short is better than a conversion to ushort.

7.4.3.5 Overloading in Generic Classes

While signatures as declared must be unique, it is possible that substitution of type arguments might result in identical signatures. In such cases, the tie-breaking rules of overload resolution will pick the most specific member.

The following examples show overloads that are valid and invalid according to this rule:

         interface I1<T> {…}
         interface I2<T> {…}
         class G1<U>
         {
                 int F1(U u);                                   // Overload resolution for G<int>.F1
                 int F1(int i);                                 // will pick nongeneric
                 void F2(I1<U> a);                        // Valid overload
                 void F2(I2<U> a);
         }
         class G2<U,V>
         {
                 void F3(U u, V v);                       // Valid, but overload resolution for
                 void F3(V v, U u);                       // G2<int, int>.F3 will fail
                 void F4(U u, I1<V> v);              // Valid, but overload resolution for
                 void F4(I1<V> v, U u);              // G2<I1<int>,int>.F4 will fail
                 void F5(U u1, I1<V> v2);         // Valid overload
                 void F5(V v1, U u2);
                 void F6(ref U u);                      // Valid overload
                 void F6(out V v);
       }

7.4.4 Function Member Invocation

This section describes the process that takes place at runtime to invoke a particular function member. It is assumed that a compile-time process has already determined the particular member to invoke, possibly by applying overload resolution to a set of candidate function members.

For purposes of describing the invocation process, function members are classified into two categories:

•  Static function members—instance constructors, static methods, static property accessors, and user-defined operators. Static function members are always nonvirtual.

•  Instance function members—instance methods, instance property accessors, and indexer accessors. Instance function members are either nonvirtual or virtual, and they are always invoked on a particular instance. The instance is computed by an instance expression, and it becomes accessible within the function member as this7.5.7).

The runtime processing of a function member invocation consists of the following steps, where M is the function member and, if M is an instance member, E is the instance expression:

•  If M is a static function member:

-  The argument list is evaluated as described in §7.4.1.

-  M is invoked.

•  If M is an instance function member declared in a value-type:

-  E is evaluated. If this evaluation causes an exception, then no further steps are executed.

-  If E is not classified as a variable, then a temporary local variable of E’s type is created and the value of E is assigned to that variable. E is then reclassified as a reference to that temporary local variable. The temporary variable is accessible as this within M, but not in any other way. Only when E is a true variable does it become possible for the caller to observe the changes that M makes to this.

-  The argument list is evaluated as described in §7.4.1.

-  M is invoked. The variable referenced by E becomes the variable referenced by this.

•  If M is an instance function member declared in a reference-type:

-  E is evaluated. If this evaluation causes an exception, then no further steps are executed.

-  The argument list is evaluated as described in §7.4.1.

-  If the type of E is a value-type, a boxing conversion (§4.3.1) is performed to convert E to type object, and E is considered to be of type object in the following steps. In this case, M could only be a member of System.Object.

-  The value of E is checked to be valid. If the value of E is null, a System.Null Reference-Exception is thrown and no further steps are executed.

-  The function member implementation to invoke is determined:

•  If the compile-time type of E is an interface, the function member to invoke is the implementation of M provided by the runtime type of the instance referenced by E. This function member is determined by applying the interface mapping rules (§13.4.4) to determine the implementation of M provided by the runtime type of the instance referenced by E.

•  Otherwise, if M is a virtual function member, the function member to invoke is the implementation of M provided by the runtime type of the instance referenced by E. This function member is determined by applying the rules for determining the most derived implementation (§10.6.3) of M with respect to the runtime type of the instance referenced by E.

•  Otherwise, M is a nonvirtual function member, and the function member to invoke is M itself.

-  The function member implementation determined in the step above is invoked. The -object referenced by E becomes the object referenced by this.

7.4.4.1 Invocations on Boxed Instances

A function member implemented in a value-type can be invoked through a boxed instance of that value-type in the following situations:

•  When the function member is an override of a method inherited from type object and is invoked through an instance expression of type object

•  When the function member is an implementation of an interface function member and is invoked through an instance expression of an interface-type

•  When the function member is invoked through a delegate

In these situations, the boxed instance is considered to contain a variable of the value-type, and this variable becomes the variable referenced by this within the function member invocation. In particular, when a function member is invoked on a boxed instance, the function member can potentially modify the value contained in the boxed instance.

7.5 Primary Expressions

Primary expressions include the simplest forms of expressions.

     primary-expression:
             primary-no-array-creation-expression
            array-creation-expression

     primary-no-array-creation-expression:
            literal
            simple-name
            parenthesized-expression
            member-access
            invocation-expression
            element-access
            this-access
            base-access
            post-increment-expression
            post-decrement-expression
            object-creation-expression
            delegate-creation-expression
            anonymous-object-creation-expression
            typeof-expression
             checked-expression
            unchecked-expression
            default-value-expression
            anonymous-method-expression

Primary expressions are divided between array-creation-expressions and primary-no-array-creation-expressions. Treating array-creation-expression in this way, rather than listing it along with the other simple expression forms, enables the grammar to disallow potentially confusing code such as

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

which would otherwise be interpreted as

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

7.5.1 Literals

A primary-expression that consists of a literal2.4.4) is classified as a value.

7.5.2 Simple Names

A simple-name consists of an identifier, optionally followed by a type argument list:

     simple-name:
            identifier type-argument-listopt

A simple-name is either of the form I or of the form I<A1, …, AK>, where I is a single identifier and <A1, …, AK> is an optional type-argument-list. When no type-argument-list is specified, consider K to be zero. The simple-name is evaluated and classified as follows:

•  If K is zero, the simple-name appears within a block. and the block’s (or an enclosing block’s) local variable declaration space (§3.3) contains a local variable, parameter, or constant with name I, then the simple-name refers to that local variable, parameter, or constant and is classified as a variable or value.

•  If K is zero, the simple-name appears within the body of a generic method declaration, and that declaration includes a type parameter with name I, then the simple-name refers to that type parameter.

•  Otherwise, for each instance type T10.3.1), starting with the instance type of the immediately enclosing type declaration and continuing with the instance type of each enclosing class or struct declaration (if any):

-  If K is zero and the declaration of T includes a type parameter with name I, then the simple-name refers to that type parameter.

-  Otherwise, if a member lookup (§7.3) of I in T with K type arguments produces a match:

•  If T is the instance type of the immediately enclosing class or struct type and the lookup identifies one or more methods, the result is a method group with an associated instance expression of this. If a type argument list was specified, it is used in calling a generic method (§7.5.5.1).

•  Otherwise, if T is the instance type of the immediately enclosing class or struct type, if the lookup identifies an instance member, and if the reference occurs within the block of an instance constructor, an instance method, or an instance accessor, the result is the same as a member access (§7.5.4) of the form this.I. This can happen only when K is zero.

•  Otherwise, the result is the same as a member access (§7.5.4) of the form T.I or T.I<A1, …, AK>. In this case, it is a compile-time error for the simple-name to refer to an instance member.

•  Otherwise, for each namespace N, starting with the namespace in which the simple-name occurs, continuing with each enclosing namespace (if any), and ending with the global namespace, the following steps are evaluated until an entity is located:

-  If K is zero and I is the name of a namespace in N, then:

•  If the location where the simple-name occurs is enclosed by a namespace declaration for N and the namespace declaration contains an extern-alias-directive or using-alias-directive that associates the name I with a namespace or type, then the simple-name is ambiguous and a compile-time error occurs.

•  Otherwise, the simple-name refers to the namespace named I in N.

-  Otherwise, if N contains an accessible type having name I and K type parameters, then:

•  If K is zero and the location where the simple-name occurs is enclosed by a namespace declaration for N and the namespace declaration contains an extern-alias-directive or using-alias-directive that associates the name I with a namespace or type, then the simple-name is ambiguous and a compile-time error occurs.

•  Otherwise, the namespace-or-type-name refers to the type constructed with the given type arguments.

-  Otherwise, if the location where the simple-name occurs is enclosed by a namespace declaration for N:

•  If K is zero and the namespace declaration contains an extern-alias-directive or using-alias-directive that associates the name I with an imported namespace or type, then the simple-name refers to that namespace or type.

•  Otherwise, if the namespaces imported by the using-namespace-directives of the namespace declaration contain exactly one type having name I and K type parameters, then the simple-name refers to that type constructed with the given type arguments.

•  Otherwise, if the namespaces imported by the using-namespace-directives of the namespace declaration contain more than one type having name I and K type parameters, then the simple-name is ambiguous and an error occurs.

•  Note that this entire step is exactly parallel to the corresponding step in the processing of a namespace-or-type-name3.8).

•  Otherwise, the simple-name is undefined and a compile-time error occurs.

7.5.2.1 Invariant Meaning in Blocks

For each occurrence of a given identifier as a simple-name in an expression or declarator, within the local variable declaration space (§3.3) immediately enclosing that occurrence, every other occurrence of the same identifier as a simple-name in an expression or a declarator must refer to the same entity. This rule ensures that the meaning of a name is always the same within a given block, switch-block, for-statement, foreach-statement, using-statement, or anonymous function.

The example

         class Test
        {
                 double x;
                 void F(bool b) {
                        x = 1.0;
                        if (b) {
                             int x;
                             x = 1;
                       }
               }
        }

results in a compile-time error because x refers to different entities within the outer block (the extent of which includes the nested block in the if statement). In contrast, the example

        class Test
        {
                 double x;
                 void F(bool b) {
                        if (b) {
                              x = 1.0;
                        }
                        else {
                               int x;
                               x = 1;
                        }
                  }
         }

is permitted because the name x is never used in the outer block.

Note that the rule of invariant meaning applies only to simple names. It is perfectly valid for the same identifier to have one meaning as a simple name and another meaning as the right operand of a member access (§7.5.4). For example,

        struct Point
        {
               int x, y;
               public Point(int x, int y) {
                       this.x = x;
                       this.y = y;
               }
         }

illustrates a common pattern of using the names of fields as parameter names in an instance constructor. In the example, the simple names x and y refer to the parameters, but that does not prevent the member access expressions this.x and this.y from accessing the fields.

7.5.3 Parenthesized Expressions

A parenthesized-expression consists of an expression enclosed in parentheses.

       parenthesized-expression:
               ( expression )

A parenthesized-expression is evaluated by evaluating the expression within the parentheses. If the expression within the parentheses denotes a namespace, type, or method group, a compile-time error occurs. Otherwise, the result of the parenthesized-expression is the result of the evaluation of the contained expression.

7.5.4 Member Access

A member-access consists of a primary-expression, a predefined-type, or a qualified-alias-member, followed by a “.” token, followed by an identifier, optionally followed by a type-argument-list.

member-access:
       primary-expression . identifier type-argument-listopt
       predefined-type . identifier type-argument-listopt
      qualified-alias-member . identifier

predefined-type: one of

Image

The qualified-alias-member production is defined in §9.7.

A member-access is either of the form E.I or of the form E.I<A1, …, AK>, where E is a primary-expression, I is a single identifier, and <A1, …, AK> is an optional type-argument-list. When no type-argument-list is specified, consider K to be zero. The member-access is evaluated and classified as follows:

•  If K is zero, E is a namespace, and E contains a nested namespace with name I, then the result is that namespace.

•  Otherwise, if E is a namespace and E contains an accessible type having name I and K type parameters, then the result is that type constructed with the given type arguments.

•  If E is a predefined-type or a primary-expression classified as a type, if E is not a type parameter, and if a member lookup (§7.3) of I in E with K type parameters produces a match, then E.I is evaluated and classified as follows:

-  If I identifies a type, then the result is that type constructed with the given type arguments.

-  If I identifies one or more methods, then the result is a method group with no associated instance expression. If a type argument list was specified, it is used in calling a generic method (§7.5.5.1).

-  If I identifies a static property, then the result is a property access with no associated instance expression.

-  If I identifies a static field:

•  If the field is readonly and the reference occurs outside the static constructor of the class or struct in which the field is declared, then the result is a value—namely, the value of the static field I in E.

•  Otherwise, the result is a variable—namely, the static field I in E.

-  If I identifies a static event:

•  If the reference occurs within the class or struct in which the event is declared, and the event was declared without event-accessor-declarations10.8), then E.I is processed exactly as if I were a static field.

•  Otherwise, the result is an event access with no associated instance expression.

-  If I identifies a constant, then the result is a value—namely, the value of that constant.

-  If I identifies an enumeration member, then the result is a value—namely, the value of that enumeration member.

-  Otherwise, E.I is an invalid member reference, and a compile-time error occurs.

•  If E is a property access, indexer access, variable, or value, the type of which is T, and a member lookup (§7.3) of I in T with K type arguments produces a match, then E.I is evaluated and classified as follows:

-  If E is a property or indexer access, then the value of the property or indexer access is obtained (§7.1.1) and E is reclassified as a value.

-  If I identifies one or more methods, then the result is a method group with an associated instance expression of E. If a type argument list was specified, it is used in calling a generic method (§7.5.5.1).

-  If I identifies an instance property, then the result is a property access with an associated instance expression of E.

-  If T is a class-type and I identifies an instance field of that class-type:

•  If the value of E is null, then a System.NullReferenceException is thrown.

•  Otherwise, if the field is readonly and the reference occurs outside an instance constructor of the class in which the field is declared, then the result is a value— namely, the value of the field I in the object referenced by E.

•  Otherwise, the result is a variable—namely, the field I in the object referenced by E.

-  If T is a struct-type and I identifies an instance field of that struct-type:

•  If E is a value, or if the field is readonly and the reference occurs outside an instance constructor of the struct in which the field is declared, then the result is a value— namely, the value of the field I in the struct instance given by E.

•  Otherwise, the result is a variable—namely, the field I in the struct instance given by E.

-  If I identifies an instance event:

•  If the reference occurs within the class or struct in which the event is declared, and the event was declared without event-accessor-declarations10.8), then E.I is processed exactly as if I was an instance field.

•  Otherwise, the result is an event access with an associated instance expression of E.

•  Otherwise, an attempt is made to process E.I as an extension method invocation (§7.5.5.2). If this fails, E.I is an invalid member reference, and a compile-time error occurs.

7.5.4.1 Identical Simple Names and Type Names

In a member access of the form E.I, if E is a single identifier, and if the meaning of E as a simple-name7.5.2) is a constant, field, property, local variable, or parameter with the same type as the meaning of E as a type-name3.8), then both possible meanings of E are permitted. The two possible meanings of E.I are never ambiguous, because I must necessarily be a member of the type E in both cases. In other words, the rule simply permits access to the static members and nested types of E where a compile-time error would otherwise have occurred. For example,

        struct Color
        {
                 public static readonly Color White = new Color(…);
                 public static readonly Color Black = new Color(…);
                 public Color Complement() {…}
        }
        class A {
                 public Color Color;                            // Field Color of type Color
                 void F() {
                       Color = Color.Black;                  // References Color.Black static member
                       Color = Color.Complement();    // Invokes Complement() on Color field
                 }
                 static void G() {
                       Color c = Color.White;              // References Color.White static member
                 }
       }

Within the A class, those occurrences of the Color identifier that reference the Color type are underlined, and those that reference the Color field are not underlined.

7.5.4.2 Grammar Ambiguities

The productions for simple-name7.5.2) and member-access7.5.4) can give rise to ambiguities in the grammar for expressions. For example, the statement

        F(G<A, B>(7));

could be interpreted as a call to F with two arguments, G < A and B > (7). Alternatively, it could be interpreted as a call to F with one argument, which is a call to a generic method G with two type arguments and one regular argument.

If a sequence of tokens can be parsed (in context) as a simple-name7.5.2), member-access7.5.4), or pointer-member-access18.5.2) ending with a type-argument-list4.4.1), the token immediately following the closing > token is examined. If it is one of

        (   )   ]   }   :   ;   ,   .   ?   ==   !=

then the type-argument-list is retained as part of the simple-name, member-access, or pointer-member-access, and any other possible parse of the sequence of tokens is discarded. Otherwise, the type-argument-list is not considered to be part of the simple-name, member-access, or pointer-member-access, even if there is no other possible parse of the sequence of tokens. Note that these rules are not applied when parsing a type-argument-list in a namespace-or-type-name3.8). The statement

        F(G < A, B > (7));

will, according to this rule, be interpreted as a call to F with one argument, which is a call to a generic method G with two type arguments and one regular argument. The statements

        F(G < A, B > 7);
        F(G < A, B >> 7);

will each be interpreted as a call to F with two arguments. The statement

        x = F < A > +y;

will be interpreted as a less than operator, greater than operator, and unary plus operator, as if the statement had been written x = (F < A) > (+y), instead of as a simple-name with a type-argument-list followed by a binary plus operator. In the statement

        x = y is C<T> + z;

the tokens C<T> are interpreted as a namespace-or-type-name with a type-argument-list.

7.5.5 Invocation Expressions

An invocation-expression is used to invoke a method.

       invocation-expression:
               primary-expression
( argument-listopt )

The primary-expression of an invocation-expression must be a method group or a value of a delegate-type. If the primary-expression is a method group, the invocation-expression is a method invocation (§7.5.5.1). If the primary-expression is a value of a delegate-type, the invocation-expression is a delegate invocation (§7.5.5.3). If the primary-expression is neither a method group nor a value of a delegate-type, a compile-time error occurs.

The optional argument-list7.4.1) provides values or variable references for the parameters of the method.

The result of evaluating an invocation-expression is classified as follows:

•  If the invocation-expression invokes a method or delegate that returns void, the result is nothing. An expression that is classified as nothing is permitted only in the context of a statement-expression8.6) or as the body of a lambda-expression7.14).

•  Otherwise, the result is a value of the type returned by the method or delegate.

7.5.5.1 Method Invocations

For a method invocation, the primary-expression of the invocation-expression must be a method group. The method group identifies the one method to invoke or the set of overloaded methods from which to choose a specific method to invoke. In the latter case, determination of the specific method to invoke is based on the context provided by the types of the arguments in the argument-list.

The compile-time processing of a method invocation of the form M(A), where M is a method group (possibly including a type-argument-list), and A is an optional argument-list, consists of the following steps:

•  The set of candidate methods for the method invocation is constructed. For each method F associated with the method group M:

-  If F is nongeneric, F is a candidate when

•  M has no type argument list, and

•  F is applicable with respect to A7.4.3.1).

-  If F is generic and M has no type argument list, F is a candidate when

•  Type inference (§7.4.2) succeeds, inferring a list of type arguments for the call, and

•  Once the inferred type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of F satisfy their constraints (§4.4.4), and the parameter list of F is applicable with respect to A7.4.3.1).

-  If F is generic and M includes a type argument list, F is a candidate when

•  F has the same number of method type parameters as were supplied in the type argument list, and

•  Once the type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of F satisfy their constraints (§4.4.4), and the parameter list of F is applicable with respect to A7.4.3.1).

•  The set of candidate methods is reduced to contain only methods from the most derived types: For each method C.F in the set, where C is the type in which the method F is declared, all methods declared in a base type of C are removed from the set. Furthermore, if C is a class type other than object, all methods declared in an interface type are removed from the set. (This latter rule has no effect except when the method group was the result of a member lookup on a type parameter having an effective base class other than object and a nonempty effective interface set.)

•  If the resulting set of candidate methods is empty, then further processing along the following steps are abandoned, and instead an attempt is made to process the invocation as an extension method invocation (§7.5.5.2). If this attempt fails, then no applicable methods exist, and a compile-time error occurs.

•  The best method of the set of candidate methods is identified using the overload resolution rules of §7.4.3. If a single best method cannot be identified, the method invocation is considered ambiguous, and a compile-time error occurs. When performing overload resolution, the parameters of a generic method are considered after substituting the type arguments (supplied or inferred) for the corresponding method type parameters.

•  Final validation of the chosen best method is performed:

-  The method is validated in the context of the method group: If the best method is a -static method, the method group must have resulted from a simple-name or a member-access through a type. If the best method is an instance method, the method group must have resulted from a simple-name, a member-access through a variable or value, or a base-access. If neither of these requirements is true, a compile-time error occurs.

-  If the best method is a generic method, the type arguments (supplied or inferred) are -checked against the constraints (§4.4.4) declared on the generic method. If any type argument does not satisfy the corresponding constraint(s) on the type parameter, a compile-time error occurs.

Once a method has been selected and validated at compile time by the preceding steps, the actual runtime invocation is processed according to the rules of function member invocation described in §7.4.4.

The intuitive effect of the resolution rules described above is as follows: To locate the particular method invoked by a method invocation, start with the type indicated by the method invocation and proceed up the inheritance chain until at least one applicable, accessible, non-override method declaration is found. Then perform type inference and overload resolution on the set of applicable, accessible, non-override methods declared in that type and invoke the method selected in this way. If no method was found, try instead to process the invocation as an extension method invocation.

7.5.5.2 Extension Method Invocations

In a method invocation (§7.5.5.1) of one of the forms

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

if the normal processing of the invocation finds no applicable methods, an attempt is made to process the construct as an extension method invocation. The objective is to find the best type-name C, so that the corresponding static method invocation can take place:

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

An extension method Ci.Mj is considered eligible if

•  Ci is a nongeneric, non-nested class.

•  The name of Mj is identifier.

•  Mj is accessible and applicable when applied to the arguments as a static method as shown earlier.

•  An implicit identity, reference, or boxing conversion exists from expr to the type of the first parameter of Mj.

The search for C proceeds as follows:

•  Starting with the closest enclosing namespace declaration, continuing with each enclosing namespace declaration, and ending with the containing compilation unit, successive attempts are made to find a candidate set of extension methods:

-  If the given namespace or compilation unit directly contains nongeneric type declara-tions Ci with eligible extension methods Mj, then the set of those extension methods is the candidate set.

•  If namespaces imported by using namespace directives in the given namespace or -compilation unit directly contain nongeneric type declarations Ci with eligible extension methods Mj, then the set of those extension methods is the candidate set.

•  If no candidate set is found in any enclosing namespace declaration or compilation unit, a compile-time error occurs.

•  Otherwise, overload resolution is applied to the candidate set as described in (§7.4.3). If no single best method is found, a compile-time error occurs.

•  C is the type within which the best method is declared as an extension method.

Using C as a target, the method call is then processed as a static method invocation (§7.4.4).

The preceding rules mean that instance methods take precedence over extension methods, that extension methods available in inner namespace declarations take precedence over extension methods available in outer namespace declarations, and that extension methods declared directly in a namespace take precedence over extension methods imported into that same namespace with a using namespace directive. For example,

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

Here, B’s method takes precedence over the first extension method, and C’s method takes precedence over both extension methods.

        public static class C
        {
                public static void F(this int i) { Console.WriteLine("C.F({0})", i); }
                public static void G(this int i) { Console.WriteLine("C.G({0})", i); }
                public static void H(this int i) { Console.WriteLine("C.H({0})", i); }
        }
        namespace N1
        {
                public static class D
                {
                        public static void F(this int i) { Console.WriteLine("D.F({0})", i); }
                        public static void G(this int i) { Console.WriteLine("D.G({0})", i); }
                }
        }
        namespace N2
        {
                using N1;
                public static class E
                {
                        public static void F(this int i) { Console.WriteLine("E.F({0})", i); }
                }
                class Test
                {
                        static void Main(string[ ] args)
                        {
                               1.F();
                               2.G();
                               3.H();
                        }
                }
        }

The output of this example is

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

D.G takes precedence over C.G, and E.F takes precedence over both D.F and C.F.

7.5.5.3 Delegate Invocations

For a delegate invocation, the primary-expression of the invocation-expression must be a value of a delegate-type. Furthermore, considering the delegate-type to be a function member with the same parameter list as the delegate-type, the delegate-type must be applicable (§7.4.3.1) with respect to the argument-list of the invocation-expression.

The runtime processing of a delegate invocation of the form D(A), where D is a primary-expression of a delegate-type and A is an optional argument-list, consists of the following steps:

•  D is evaluated. If this evaluation causes an exception, no further steps are executed.

•  The value of D is checked to be valid. If the value of D is null, a System.NullReference-Exception is thrown and no further steps are executed.

•  Otherwise, D is a reference to a delegate instance. Function member invocations (§7.4.4) are performed on each of the callable entities in the invocation list of the delegate. For callable entities consisting of an instance and instance method, the instance for the invocation is the instance contained in the callable entity.

7.5.6 Element Access

An element-access consists of a primary-no-array-creation-expression, followed by a “[” token, followed by an expression-list, followed by a “]” token. The expression-list consists of one or more expressions, separated by commas.

        element-access:
                primary-no-array-creation-expression [ expression-list ]
        expression-list:
                expression
                expression-list , expression

If the primary-no-array-creation-expression of an element-access is a value of an array-type, the element-access is an array access (§7.5.6.1). Otherwise, the primary-no-array-creation-expression must be a variable or value of a class, struct, or interface type that has one or more indexer members, in which case the element-access is an indexer access (§7.5.6.2).

7.5.6.1 Array Access

For an array access, the primary-no-array-creation-expression of the element-access must be a value of an array-type. The number of expressions in the expression-list must be the same as the rank of the array-type, and each expression must be of type int, uint, long, or ulong, or of a type that can be implicitly converted to one or more of these types.

The result of evaluating an array access is a variable of the element type of the array—namely, the array element selected by the value(s) of the expression(s) in the expression-list.

The runtime processing of an array access of the form P[A], where P is a primary-no-array-creation-expression of an array-type and A is an expression-list, consists of the following steps:

•  P is evaluated. If this evaluation causes an exception, no further steps are executed.

•  The index expressions of the expression-list are evaluated in order, from left to right. Following evaluation of each index expression, an implicit conversion (§6.1) to one of the following types is performed: int, uint, long, or ulong. The first type in this list for which an implicit conversion exists is chosen. For instance, if the index expression is of type short, then an implicit conversion to int is performed, because implicit conversions from short to int and from short to long are possible. If evaluation of an index expression or the subsequent implicit conversion causes an exception, then no further index expressions are evaluated and no further steps are executed.

•  The value of P is checked to be valid. If the value of P is null, a System.NullReference-Exception is thrown and no further steps are executed.

•  The value of each expression in the expression-list is checked against the actual bounds of each dimension of the array instance referenced by P. If one or more values are out of range, a System.IndexOutOfRangeException is thrown and no further steps are executed.

•  The location of the array element given by the index expression(s) is computed, and this location becomes the result of the array access.

7.5.6.2 Indexer Access

For an indexer access, the primary-no-array-creation-expression of the element-access must be a variable or value of a class, struct, or interface type, and this type must implement one or more indexers that are applicable with respect to the expression-list of the element-access.

The compile-time processing of an indexer access of the form P[A], where P is a primary-no-array-creation-expression of a class, struct, or interface type T, and A is an expression-list, consists of the following steps:

•  The set of indexers provided by T is constructed. The set consists of all indexers declared in T or a base type of T that are not override declarations and are accessible in the current context (§3.5).

•  The set is reduced to those indexers that are applicable and not hidden by other indexers. The following rules are applied to each indexer S.I in the set, where S is the type in which the indexer I is declared:

-  If I is not applicable with respect to A7.4.3.1), then I is removed from the set.

-  If I is applicable with respect to A7.4.3.1), then all indexers declared in a base type of S are removed from the set.

-  If I is applicable with respect to A7.4.3.1) and S is a class type other than object, all indexers declared in an interface are removed from the set.

•  If the resulting set of candidate indexers is empty, then no applicable indexers exist, and a compile-time error occurs.

•  The best indexer of the set of candidate indexers is identified using the overload resolution rules of §7.4.3. If a single best indexer cannot be identified, the indexer access is ambiguous, and a compile-time error occurs.

•  The index expressions of the expression-list are evaluated in order, from left to right. The result of processing the indexer access is an expression classified as an indexer access. The indexer access expression references the indexer determined in the step above, and has an associated instance expression of P and an associated argument list of A.

Depending on the context in which it is used, an indexer access causes invocation of either the get-accessor or the set-accessor of the indexer. If the indexer access is the target of an assignment, the set-accessor is invoked to assign a new value (§7.16.1). In all other cases, the get-accessor is invoked to obtain the current value (§7.1.1).

7.5.7 this Access

A this-access consists of the reserved word this.

        this-access:
               this

A this-access is permitted only in the block of an instance constructor, an instance method, or an instance accessor. It has one of the following meanings:

•  When this is used in a primary-expression within an instance constructor of a class, it is classified as a value. The type of the value is the instance type (§10.3.1) of the class within which the usage occurs, and the value is a reference to the object being constructed.

•  When this is used in a primary-expression within an instance method or instance accessor of a class, it is classified as a value. The type of the value is the instance type (§10.3.1) of the class within which the usage occurs, and the value is a reference to the object for which the method or accessor was invoked.

•  When this is used in a primary-expression within an instance constructor of a struct, it is classified as a variable. The type of the variable is the instance type (§10.3.1) of the struct within which the usage occurs, and the variable represents the struct being constructed. The this variable of an instance constructor of a struct behaves exactly the same as an out parameter of the struct type—in particular, the variable must be definitely assigned in every execution path of the instance constructor.

•  When this is used in a primary-expression within an instance method or instance accessor of a struct, it is classified as a variable. The type of the variable is the instance type (§10.3.1) of the struct within which the usage occurs.

-  If the method or accessor is not an iterator (§10.14), the this variable represents the struct for which the method or accessor was invoked, and behaves exactly the same as a ref parameter of the struct type.

-  If the method or accessor is an iterator, the this variable represents a copy of the struct for which the method or accessor was invoked, and behaves exactly the same as a value parameter of the struct type.

Use of this in a primary-expression in a context other than the ones listed above is a compile-time error. In particular, it is not possible to refer to this in a static method, in a static property accessor, or in a variable-initializer of a field declaration.

7.5.8 base Access

A base-access consists of the reserved word base followed by either a “.” token and an identifier or an expression-list enclosed in square brackets:

        base-access:
               base . identifier
               base [ expression-list ]

A base-access is used to access base class members that are hidden by similarly named members in the current class or struct. This kind of access is permitted only in the block of an instance constructor, an instance method, or an instance accessor. When base.I occurs in a class or struct, I must denote a member of the base class of that class or struct. Likewise, when base[E] occurs in a class, an applicable indexer must exist in the base class.

At compile time, base-access expressions of the form base.I and base[E] are evaluated exactly as if they were written ((B)this).I and ((B)this)[E], where B is the base class of the class or struct in which the construct occurs. Thus base.I and base[E] correspond to this.I and this[E], respectively, except this is viewed as an instance of the base class.

When a base-access references a virtual function member (a method, property, or indexer), the determination of which function member to invoke at runtime (§7.4.4) changes. The function member that is invoked is determined by finding the most derived implementation (§10.6.3) of the function member with respect to B (instead of with respect to the runtime type of this, as would be usual in a nonbase access). Thus, within an override of a virtual function member, a base-access can be used to invoke the inherited implementation of the function member. If the function member referenced by a base-access is abstract, a compile-time error occurs.

7.5.9 Postfix Increment and Decrement Operators

      post-increment-expression:
             primary-expression ++
      post-decrement-expression:
             primary-expression   --

The operand of a postfix 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 postfix 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, and decimal, plus 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 postfix 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 value of x is saved.

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

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

-  The saved value of x 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 and the returned value is saved.

-  The selected operator is invoked with the saved value ofx as its argument.

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

-  The saved value of x becomes the result of the operation.

The ++ and -- operators also support prefix notation (§7.6.5). 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 into 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.5.10 The new Operator

The new operator is used to create new instances of types.

Three forms of new expressions exist:

•  Object creation expressions are used to create new instances of class types and value types.

•  Array creation expressions are used to create new instances of array types.

•  Delegate creation expressions are used to create new instances of delegate types.

The new operator implies creation of an instance of a type, but does not necessarily imply dynamic allocation of memory. In particular, instances of value types require no additional memory beyond the variables in which they reside, and no dynamic allocations occur when new is used to create instances of value types.

7.5.10.1 Object Creation Expressions

An object-creation-expression is used to create a new instance of a class-type or a value-type.

      object-creation-expression:
              new type ( argument-listopt ) object-or-collection-initializeropt
              new type object-or-collection-initializer
        object-or-collection-initializer:
              object-initializer
              collection-initializer

The type of an object-creation-expression must be a class-type, a value-type, or a type-parameter. The type cannot be an abstract class-type.

The optional argument-list7.4.1) is permitted only if the type is a class-type or a struct-type.

An object creation expression can omit the constructor argument list and enclosing parentheses provided it includes an object initializer or a collection initializer. Omitting the constructor argument list and enclosing parentheses is equivalent to specifying an empty argument list.

Processing of an object creation expression that includes an object initializer or a collection initializer consists of first processing the instance constructor and then processing the member or element initializations specified by the object initializer (§7.5.10.2) or collection initializer (§7.5.10.3).

The compile-time processing of an object-creation-expression of the form new T(A), where T is a class-type or a value-type and A is an optional argument-list, consists of the following steps:

•  If T is a value-type and A is not present:

-  The object-creation-expression is a default constructor invocation. The result of the object-creation-expression is a value of type T—namely, the default value for T as defined in §4.1.1.

•  Otherwise, if T is a type-parameter and A is not present:

-  If no value type constraint or constructor constraint (§10.1.5) has been specified for T, a compile-time error occurs.

-  The result of the object-creation-expression is a value of the runtime type that the type parameter has been bound to—namely, the result of invoking the default constructor of that type. The runtime type may be a reference type or a value type.

•  Otherwise, if T is a class-type or a struct-type:

-  If T is an abstract class-type, a compile-time error occurs.

-  The instance constructor to invoke is determined using the overload resolution rules -of §7.4.3. The set of candidate instance constructors consists of all accessible instance constructors declared in T that are applicable with respect to A7.4.3.1). If the set of candidate instance constructors is empty, or if a single best instance constructor cannot be identified, a compile-time error occurs.

-  The result of the object-creation-expression is a value of type T—namely, the value produced by invoking the instance constructor determined in the step above.

•  Otherwise, the object-creation-expression is invalid, and a compile-time error occurs.

The runtime processing of an object-creation-expression of the form new T(A), where T is class-type or a struct-type and A is an optional argument-list, consists of the following steps:

•  If T is a class-type:

-  A new instance of class T is allocated. If there is not enough memory available to allocate the new instance, a System.OutOfMemoryException is thrown and no further steps are executed.

-  All fields of the new instance are initialized to their default values (§5.2).

-  The instance constructor is invoked according to the rules of function member invo-cation (§7.4.4). A reference to the newly allocated instance is automatically passed to the instance constructor, and the instance can be accessed from within that constructor as this.

•  If T is a struct-type:

-  An instance of type T is created by allocating a temporary local variable. Because an instance constructor of a struct-type is required to definitely assign a value to each field of the instance being created, no initialization of the temporary variable is necessary.

-  The instance constructor is invoked according to the rules of function member invo-cation (§7.4.4). A reference to the newly allocated instance is automatically passed to the instance constructor, and the instance can be accessed from within that constructor as this.

7.5.10.2 Object Initializers

An object initializer specifies values for zero or more fields or properties of an object.

      object-initializer:
              { member-initializer-listopt }
              { member-initializer-list , }
       member-initializer-list:
              member-initializer
              member-initializer-list , member-initializer
       member-initializer:
              identifier = initializer-value
       initializer-value:
              expression
              object-or-collection-initializer

An object initializer consists of a sequence of member initializers, enclosed by “{” and “}” tokens and separated by commas. Each member initializer must name an accessible field or property of the object being initialized, followed by an equals sign and an expression or an object initializer or collection initializer. It is an error for an object initializer to include more than one member initializer for the same field or property. It is not possible for the object initializer to refer to the newly created object it is initializing.

A member initializer that specifies an expression after the equals sign is processed in the same way as an assignment (§7.16.1) to the field or property.

A member initializer that specifies an object initializer after the equals sign is a nested object initializer—that is, an initialization of an embedded object. Instead of assigning a new value to the field or property, the assignments in the nested object initializer are treated as assignments to members of the field or property. Nested object initializers cannot be applied to properties with a value type, or to read-only fields with a value type.

A member initializer that specifies a collection initializer after the equals sign is an initialization of an embedded collection. Instead of assigning a new collection to the field or property, the elements given in the initializer are added to the collection referenced by the field or property. The field or property must be of a collection type that satisfies the requirements specified in §7.5.10.3.

The following class represents a point with two coordinates:

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

An instance of Point can be created and initialized as follows:

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

This code has the same effect as

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

where __a is an otherwise invisible and inaccessible temporary variable. The following class represents a rectangle created from two points:

        public class Rectangle
        {
                 Point p1, p2;
                 public Point P1 { get { return p1; } set { p1 = value; } }
                 public Point P2 { get { return p2; } set { p2 = value; } }
        }

An instance of Rectangle can be created and initialized as follows:

        Rectangle r = new Rectangle {
                 P1 = new Point { X = 0, Y = 1 },
                 P2 = new Point { X = 2, Y = 3 }
        };

This code has the same effect as

        Rectangle __r = new Rectangle();
        Point __p1 = new Point();
        __p1.X = 0;
        __p1.Y = 1;
        __r.P1 = __p1;
        Point __p2 = new Point();
        __p2.X = 2;
        __p2.Y = 3;
        __r.P2 = __p2;
        Rectangle r = __r;

where __r, __p1, and __p2 are temporary variables that are otherwise invisible and inaccessible.

If Rectangle’s constructor allocates the two embedded Point instances,

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

the following construct can be used to initialize the embedded Point instances instead of assigning new instances:

        Rectangle r = new Rectangle {
                P1 = { X = 0, Y = 1 },
                P2 = { X = 2, Y = 3 }
        };

This code has the same effect as

        Rectangle __r = new Rectangle();
        __r.P1.X = 0;
        __r.P1.Y = 1;
        __r.P2.X = 2;
        __r.P2.Y = 3;
        Rectangle r = __r;

7.5.10.3 Collection Initializers

A collection initializer specifies the elements of a collection.

      collection-initializer:
              { element-initializer-list }
              { element-initializer-list , }
      element-initializer-list:
              element-initializer
              element-initializer-list , element-initializer
        element-initializer:
              non-assignment-expression
              { expression-list }

A collection initializer consists of a sequence of element initializers, enclosed by “{” and “}” tokens and separated by commas. Each element initializer specifies an element to be added to the collection object being initialized, and consists of a list of expressions enclosed by “{” and “}” tokens and separated by commas. A single-expression element initializer can be written without braces, but cannot then be an assignment expression, to avoid ambiguity with member initializers. The non-assignment-expression production is defined in §7.17.

The following example of an object creation expression includes a collection initializer:

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

The collection object to which a collection initializer is applied must be of a type that implements System.Collections.IEnumerable; otherwise, a compile-time error will occur. For each specified element, the collection initializer invokes, in order, an Add method on the target object with the expression list of the element initializer as argument list, applying normal overload resolution for each invocation. Thus the collection object must contain an applicable Add method for each element initializer.

The following class represents a contact with a name and a list of phone numbers:

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

A List<Contact> can be created and initialized as follows:

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

This code has the same effect as

         var __clist = new List<Contact>();
         Contact __c1 = new Contact();
         __c1.Name = "Chris Smith";
         __c1.PhoneNumbers.Add("206-555-0101");
         __c1.PhoneNumbers.Add("425-882-8080");
         __clist.Add(__c1);
         Contact __c2 = new Contact();
         __c2.Name = "Bob Harris";
         __c2.PhoneNumbers.Add("650-555-0199");
         __clist.Add(__c2);
         var contacts = __clist;

where __clist, __c1, and __c2 are temporary variables that are otherwise invisible and inaccessible.

7.5.10.4 Array Creation Expressions

An array-creation-expression is used to create a new instance of an array-type.

      array-creation-expression:
             new non-array-type [ expression-list ] rank-specifiersopt array-initializeropt
             new array-type array-initializer
             new rank-specifier array-initializer

An array creation expression of the first form allocates an array instance of the type that results from deleting each of the individual expressions from the expression list. For example, the array creation expression new int[10, 20] produces an array instance of type int[,], and the array creation expression new int[10][,] produces an array of type int[ ][,]. Each expression in the expression list must be of type int, uint, long, or ulong, or of a type that can be implicitly converted to one or more of these types. The value of each expression determines the length of the corresponding dimension in the newly allocated array instance. Because the length of an array dimension must be non-negative, it is a compile-time error to have a constant-expression with a negative value in the expression list.

Except in an unsafe context (§18.1), the layout of arrays is unspecified.

If an array creation expression of the first form includes an array initializer, each expression in the expression list must be a constant, and the rank and dimension lengths specified by the expression list must match those of the array initializer.

In an array creation expression of the second or third form, the rank of the specified array type or rank specifier must match that of the array initializer. The individual dimension lengths are inferred from the number of elements in each of the corresponding nesting levels of the array initializer. Thus the expression

          new int[,] {{0, 1}, {2, 3}, {4, 5}}

exactly corresponds to

        new int[3, 2] {{0, 1}, {2, 3}, {4, 5}}

An array creation expression of the third form is referred to as an implicitly typed array creation expression. It is similar to the second form, except that the element type of the array is not explicitly given, but rather is determined as the best common type (§7.4.2.13) of the set of expressions in the array initializer. For a multi-dimensional array (i.e., one where the rank-specifier contains at least one comma), this set comprises all expressions found in nested array-initializers.

Array initializers are described further in §12.6.

The result of evaluating an array creation expression is classified as a value—namely, a reference to the newly allocated array instance. The runtime processing of an array creation expression consists of the following steps:

•  The dimension length expressions of the expression-list are evaluated in order, from left to right. Following evaluation of each expression, an implicit conversion (§6.1) to one of the following types is performed: int, uint, long, or ulong. The first type in this list for which an implicit conversion exists is chosen. If evaluation of an expression or the subsequent implicit conversion causes an exception, then no further expressions are evaluated and no further steps are executed.

•  The computed values for the dimension lengths are validated as follows. If one or more of the values are less than zero, a System.OverflowException is thrown and no further steps are executed.

•  An array instance with the given dimension lengths is allocated. If there is not enough memory available to allocate the new instance, a System.OutOfMemoryException is thrown and no further steps are executed.

•  All elements of the new array instance are initialized to their default values (§5.2).

•  If the array creation expression contains an array initializer, then each expression in the array initializer is evaluated and assigned to its corresponding array element. The evaluations and assignments are performed in the order the expressions are written in the array initializer—in other words, elements are initialized in increasing index order, with the rightmost dimension increasing first. If evaluation of a given expression or the subsequent assignment to the corresponding array element causes an exception, then no further elements are initialized (and the remaining elements will, therefore, have their default values).

An array creation expression permits instantiation of an array with elements of an array type, but the elements of such an array must be manually initialized. For example, the statement

        int[ ][ ] a = new int[100][ ];

creates a single-dimensional array with 100 elements of type int[ ]. The initial value of each element is null. It is not possible for the same array creation expression to also instantiate the subarrays, and the statement

        int[ ][ ] a = new int[100][5];                  // Error

results in a compile-time error. Instantiation of the subarrays must instead be performed manually, as shown here:

        int[ ][ ] a = new int[100][ ];
        for (int i = 0; i < 100; i++) a[i] = new int[5];

When an array of arrays has a “rectangular” shape (i.e., when the subarrays are all of the same length), it is more efficient to use a multi-dimensional array. In the previous example, instantiation of the array of arrays creates 101 objects—one outer array and 100 subarrays. In contrast,

        int[,] = new int[100, 5];

creates only a single object, a two-dimensional array, and accomplishes the allocation in a single statement.

The following are examples of implicitly typed array creation expressions:

         var a = new[ ] { 1, 10, 100, 1000 };                                                        // int[ ]
         var b = new[ ] { 1, 1.5, 2, 2.5 };                                                               // double[ ]
         var c = new[,] { { "hello", null }, { "world", "!" } };                         // string[,]
         var d = new[ ] { 1, "one", 2, "two" };                                                    // Error

The last expression causes a compile-time error because neither int nor string is implicitly convertible to the other, so there is no best common type. An explicitly typed array creation expression must be used in this case—for example, specifying the type to be object[ ]. Alternatively, one of the elements can be cast to a common base type, which would then become the inferred element type.

Implicitly typed array creation expressions can be combined with anonymous object initializers (§7.5.10.6) to create anonymously typed data structures. For example,

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

7.5.10.5 Delegate Creation Expressions

A delegate-creation-expression is used to create a new instance of a delegate-type.

       delegate-creation-expression:
               new delegate-type ( expression )

The argument of a delegate creation expression must be a method group, an anonymous function, or a value of a delegate-type. If the argument is a method group, it identifies the method and, for an instance method, the object for which to create a delegate. If the argument is an anonymous function, it directly defines the parameters and method body of the delegate target. If the argument is a value of a delegate-type, it identifies a delegate instance of which to create a copy.

The compile-time processing of a delegate-creation-expression of the form new D(E), where D is a delegate-type and E is an expression, consists of the following steps:

•  If E is a method group, the delegate creation expression is processed in the same way as a method group conversion (§6.6) from E to D.

•  If E is an anonymous function, the delegate creation expression is processed in the same way as an anonymous function conversion (§6.5) from E to D.

•  If E is a value of a delegate type, E must be compatible (§15.1) with D, and the result is a reference to a newly created delegate of type D that refers to the same invocation list as E. If E is not compatible with D, a compile-time error occurs.

The runtime processing of a delegate-creation-expression of the form new D(E), where D is a delegate-type and E is an expression, consists of the following steps:

•  If E is a method group, the delegate creation expression is evaluated as a method group conversion (§6.6) from E to D.

•  If E is an anonymous function, the delegate creation is evaluated as an anonymous function conversion from E to D6.5).

•  If E is a value of a delegate-type:

-   E is evaluated. If this evaluation causes an exception, no further steps are executed.

-  If the value of E is null, a System.NullReferenceException is thrown and no further steps are executed.

-  A new instance of the delegate type D is allocated. If there is not enough memory available to allocate the new instance, a System.OutOfMemoryException is thrown and no further steps are executed.

-  The new delegate instance is initialized with the same invocation list as the delegate -instance given by E.

The invocation list of a delegate is determined when the delegate is instantiated and then remains constant for the entire lifetime of the delegate. In other words, it is not possible to change the target callable entities of a delegate once it has been created. When two delegates are combined or one is removed from another (§15.1), a new delegate results; no existing delegate has its contents changed.

It is not possible to create a delegate that refers to a property, indexer, user-defined operator, instance constructor, destructor, or static constructor.

As described earlier, when a delegate is created from a method group, the formal parameter list and return type of the delegate determine which of the overloaded methods to select. In the example

        delegate double DoubleFunc(double x);
        class A
        {
                DoubleFunc f = new DoubleFunc(Square);
                static float Square(float x) {
                       return x * x;
                 }
                 static double Square(double x) {
                       return x * x;
                }
          }

the A.f field is initialized with a delegate that refers to the second Square method because that method exactly matches the formal parameter list and return type of DoubleFunc. Had the second Square method not been present, a compile-time error would have occurred.

7.5.10.6 Anonymous Object Creation Expressions

An anonymous-object-creation-expression is used to create an object of an anonymous type.

     anonymous-object-creation-expression:
            new anonymous-object-initializer
     anonymous-object-initializer:
            { member-declarator-listopt }
            { member-declarator-list , }
     member-declarator-list:
            member-declarator
            member-declarator-list , member-declarator
     member-declarator:
            simple-name
            member-access
            base-access
            identifier = expression

An anonymous object initializer declares an anonymous type and returns an instance of that type. An anonymous type is a nameless class type that inherits directly from object. The members of an anonymous type are a sequence of read-only properties inferred from the anonymous object initializer used to create an instance of the type. Specifically, an anonymous object initializer of the form

      new { p1 = e1 , p2 = e2 , , pn = en }

declares an anonymous type equivalent to

        class __Anonymous1
        {
                private readonly T1 f1 ;
                private readonly T2 f2;
                
                private readonly Tn fn ;
                public __Anonymous1(T1 a1, T2 a2, , Tn an) {
                       f1 = a1;
                       f2 = a2;
                       …
                       fn = an;
                }
                public T1 p1{ get { return f1 ; } }
                public T2 p2 { get { return f2; } }
                
                public Tn pn { get { return fn; } }
                public override bool Equals(object o) { … }
                public override int GetHashCode() { … }
          }

where each Tx is the type of the corresponding expression ex. The expression used in a member-declarator must have a type. It is, therefore, a compile-time error for an expression in a member-declarator to be null or an anonymous function. It is also a compile-time error for the expression to have an unsafe type.

The name of an anonymous type is automatically generated by the compiler and cannot be referenced in program text.

Within the same program, two anonymous object initializers that specify a sequence of properties of the same names and compile-time types in the same order will produce instances of the same anonymous type.

In the example

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

the assignment on the last line is permitted because p1 and p2 are of the same anonymous type.

The Equals and GetHashcode methods on anonymous types override the methods inherited from object, and are defined in terms of the Equals and GetHashcode of the properties. Thus two instances of the same anonymous type are equal if and only if all their properties are equal.

A member declarator can be abbreviated to a simple name (§7.5.2), a member access (§7.5.4), or a base access (§7.5.8). Such a projection initializer is shorthand for a declaration of and assignment to a property with the same name. Specifically, member declarators of the forms

        identifier expr . identifier

are precisely equivalent to the following, respectively:

        identifer = identifier identifier = expr . identifier

Thus, in a projection initializer, the identifier selects both the value and the field or property to which the value is assigned. Intuitively, a projection initializer projects not just a value, but also the name of the value.

7.5.11 The typeof Operator

The typeof operator is used to obtain the System.Type object for a type.

      typeof-expression:
              typeof ( type )
              typeof ( unbound-type-name )
              typeof ( void )
      unbound-type-name:
              identifier generic-dimension-specifieropt
              identifier :: identifier generic-dimension-specifieropt
              unbound-type-name . identifier generic-dimension-specifieropt
      generic-dimension-specifier:
              < commasopt >

      commas:
              ,
              commas ,

The first form of typeof-expression consists of a typeof keyword followed by a paren thesized type. The result of an expression of this form is the System.Type object for the indicated type. There is only one System.Type object for any given type. As a consequence, for a type T, typeof(T) == typeof (T) is always true.

The second form of typeof-expression consists of a typeof keyword followed by a parenthesized unbound-type-name. An unbound-type-name is very similar to a type-name3.8), except that an unbound-type-name contains generic-dimension-specifiers where a type-name contains type-argument-lists. When the operand of a typeof-expression is a sequence of tokens that satisfies the grammars of both unbound-type-name and type-name—namely, when it contains neither a generic-dimension-specifier nor a type-argument-list—the sequence of tokens is considered to be a type-name. The meaning of an unbound-type-name is determined as follows:

•  Convert the sequence of tokens to a type-name by replacing each generic-dimension-specifier with a type-argument-list having the same number of commas and the keyword object as each type-argument.

•  Evaluate the resulting type-name, while ignoring all type parameter constraints.

•  The unbound-type-name resolves to the unbound generic type associated with the resulting constructed type (§4.4.3).

The result of the typeof-expression is the System.Type object for the resulting unbound generic type.

The third form of typeof-expression consists of a typeof keyword followed by a parenthesized void keyword. The result of an expression of this form is the System.Type object that represents the absence of a type. The type object returned by typeof(void) is distinct from the type object returned for any type. This special type object is useful in class libraries that allow reflection onto methods in the language, where those methods wish to have a way to represent the return type of any method, including void methods, with an instance of System.Type.

The typeof operator can be used on a type parameter. The result is the System.Type object for the runtime type that was bound to the type parameter. The typeof operator can also be used on a constructed type or an unbound generic type (§4.4.3). The System.Type object for an unbound generic type is not the same as the System.Type object of the instance type. The instance type is always a closed constructed type at runtime, so its System.Type object depends on the runtime type arguments in use, while the unbound generic type has no type arguments.

The example

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

produces the following output:

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

Note that int and System.Int32 are the same type. Also note that the result of typeof(X<>) does not depend on the type argument, whereas the result of typeof(X<T>) does.

7.5.12 The checked and unchecked Operators

The checked and unchecked operators are used to control the overflow checking context for integral-type arithmetic operations and conversions.


        checked-expression:
                 checked ( expression )
        unchecked-expression:
                 unchecked ( expression )

The checked operator evaluates the contained expression in a checked context; the unchecked operator evaluates the contained expression in an unchecked context. A checked-expression or unchecked-expression corresponds exactly to a parenthesized-expression7.5.3), except that the contained expression is evaluated in the given overflow checking context.

The overflow checking context can also be controlled through the checked and unchecked statements (§8.11).

The following operations are affected by the overflow checking context established by the checked and unchecked operators and statements:

•  The predefined ++ and -- unary operators (§7.5.9 and §7.6.5), when the operand is of an integral type

•  The predefined - unary operator (§7.6.2), when the operand is of an integral type

•  The predefined +, -, *, and / binary operators (§7.7), when both operands are of integral types

•  Explicit numeric conversions (§6.2.1) from one integral type to another integral type, or fromfloat or double to an integral type

When one of these operations produces a result that is too large to represent in the destination type, the context in which the operation is performed controls the resulting behavior:

•  In a checked context, if the operation is a constant expression (§7.18), a compile-time error occurs. Otherwise, when the operation is performed at runtime, a System. Overflow-Exception is thrown.

•  In an unchecked context, the result is truncated by discarding any high-order bits that do not fit in the destination type.

For nonconstant expressions (i.e., expressions that are evaluated at runtime) that are not enclosed by any checked or unchecked operators or statements, the default overflow checking context is unchecked unless external factors (such as compiler switches and execution environment configuration) call for checked evaluation.

For constant expressions (i.e., expressions that can be fully evaluated at compile time), the default overflow checking context is always checked. Unless a constant expression is explicitly placed in an unchecked context, overflows that occur during the compile-time evaluation of the expression always cause compile-time errors.

The body of an anonymous function is not affected by the checked or unchecked contexts in which the anonymous function occurs.

In the example

        class Test
        {
                 static readonly int x = 1000000;
                 static readonly int y = 1000000;
                 static int F() {
                        return checked(x * y);                  // Throws OverflowException
                 }
                 static int G() {
                        return unchecked(x * y);             // Returns -727379968
                 }
                 static int H() {
                        return x * y;                                   // Depends on default
                 }
          }

no compile-time errors are reported, because neither of the expressions can be evaluated at compile-time. At runtime, the F method throws a System.OverflowException, and the G method returns -727379968 (the lower 32 bits of the out-of-range result). The behavior of the H method depends on the default overflow checking context for the compilation, but it is either the same as F or the same as G.

In the example

        class Test
        {
                 const int x = 1000000;
                 const int y = 1000000;
                 static int F() {
                        return checked(x * y);                 // Compile error, overflow
                 }
                 static int G() {
                        return unchecked(x * y);            // Returns -727379968
                 }
                 static int H() {
                        return x * y;                                  // Compile error, overflow
                 }
         }

the overflows that occur when evaluating the constant expressions in F and H cause compile-time errors to be reported because the expressions are evaluated in a checked context. An overflow also occurs when evaluating the constant expression in G. Because the evaluation takes place in an unchecked context, however, the overflow is not reported.

The checked and unchecked operators affect only the overflow checking context for those operations that are textually contained within the “(” and “)” tokens. These operators do not have any effect on function members that are invoked as a result of evaluating the contained expression. In the example

        class Test
        {
                 static int Multiply(int x, int y) {
                        return x * y;
                 }
                 static int F() {
                        return checked(Multiply(1000000, 1000000));
                 }
          }

the use of checked in F does not affect the evaluation of x * y in Multiply, so x * y is evaluated in the default overflow checking context.

The unchecked operator is convenient when writing constants of the signed integral types in hexadecimal notation. For example,

        class Test
        {
                 public const int AllBits = unchecked((int)0xFFFFFFFF);
                 public const int HighBit = unchecked((int)0x80000000);
          }

Both of these hexadecimal constants are of type uint. Because the constants are outside the int range, without the unchecked operator, the casts to int would produce compile-time errors.

The checked and unchecked operators and statements allow programmers to control certain aspects of some numeric calculations. However, the behavior of some numeric operators depends on their operands’ data types. For example, multiplying two decimals always results in an exception on overflow even within an explicitly unchecked construct. Similarly, multiplying two floats never results in an exception on overflow even within an explicitly checked construct. In addition, other operators are never affected by the mode of checking, whether default or explicit.

7.5.13 Default Value Expressions

A default value expression is used to obtain the default value (§5.2) of a type. Typically, a default value expression is used for type parameters, because it may not be known whether the type parameter is a value type or a reference type. (No conversion exists from the null literal to a type parameter unless the type parameter is known to be a reference type.)

       default-value-expression:
              default ( type )

If the type in a default-value-expression evaluates at runtime to a reference type, the result is null converted to that type. If the type in a default-value-expression evaluates at runtime to a value type, the result is the value-type’s default value (§4.1.2).

A default-value-expression is a constant expression (§7.18) if the type is a reference type or a type parameter that is known to be a reference type (§10.1.5). Likewise, a default-value-expression is a constant expression if the type is one of the following value types: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, or any enumeration type.

7.5.14 Anonymous Method Expressions

An anonymous-method-expression is one of two ways of defining an anonymous function. These expressions are described in more detail in §7.14.

7.6 Unary Operators

The +, -, !, ~, ++, --, and cast operators are called the unary operators.

      unary-expression:
           primary-expression
           + unary-expression
           - unary-expression
           ! unary-expression
           ~ unary-expression
           pre-increment-expression
           pre-decrement-expression
          cast-expression

7.6.1 Unary Plus 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 unary plus operators are listed here:

         int operator +(int x);
         uint operator +(uint x);
         long operator +(long x);
         ulong operator +(ulong x);
         float operator +(float x);
         double operator +(double x);
         decimal operator +(decimal x);

For each of these operators, the result is simply the value of the operand.

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

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