CATEGORII DOCUMENTE |
Primary expressions include the simplest forms of expressions.
primary-expression:
array-creation-expression
primary-expression-no-array-creation
primary-expression-no-array-creation:
literal
simple-name
parenthesized-expression
member-access
invocation-expression
element-access
this-access
base-access
post-increment-expression
post-decrement-expression
new-expression
typeof-expression
sizeof-expression
checked-expression
unchecked-expression
Primary expressions are divided between array-creation-expressions and primary-expression-no-array-creations. 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];
A primary-expression that consists of a literal (2.4.4) is classified as a value.
A simple-name consists of a single identifier.
simple-name:
identifier
A simple-name is evaluated and classified as follows:
If the simple-name appears within a block and if the block contains a local variable or parameter with the given name, then the simple-name refers to that local variable or parameter and is classified as a variable.
Otherwise, for each type T, starting with the immediately enclosing class, struct, or enumeration declaration and continuing with each enclosing outer class or struct declaration (if any), if a member lookup of the simple-name in T produces a match:
If T is 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 T is 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 method, an instance accessor, or an instance constructor, the result is the same as a member access (7.5.4) of the form this.E, where E is the simple-name.
Otherwise, the result is the same as a member access (7.5.4) of the form T.E, where E is the simple-name. In this case, it is an error for the simple-name to refer to an instance member.
Otherwise, 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 the namespace contains a namespace member with the given name, then the simple-name refers to that member and, depending on the member, is classified as a namespace or a type.
Otherwise, if that namespace has a corresponding namespace declaration enclosing the location where the simple-name occurs, then:
If the namespace declaration contains a using-alias-directive that associates the given name 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 with the given name, then the simple-name refers to that type.
Otherwise, if the namespaces imported by the using-namespace-directives of the namespace declaration contain more than one type with the given name, then the simple-name is ambiguous and an error occurs.
Otherwise, the name given by the simple-name is undefined and an error occurs.
For each occurrence of a given identifier as a simple-name in an expression, every other occurrence of the same identifier as a simple-name in an expression within the immediately enclosing block (8.2) or switch-block (8.7.2) must refer to the same entity. This rule ensures that the meaning of a name in the context of an expression is always the same within a block.
The example
class
Test
}
}
is in 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
else
}
}
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 right operand of a member access (7.5.4). For example:
struct
Point
}
The example above 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.
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, an error occurs. Otherwise, the result of the parenthesized-expression is the result of the evaluation of the contained expression.
A member-access consists of a primary-expression or a predefined-type, followed by a "." token, followed by an identifier.
member-access:
primary-expression . identifier
predefined-type . identifier
predefined-type: one of
bool byte char decimal double float int long
object sbyte short string uint ulong ushort
A member-access of the form E.I, where E is a primary-expression or a predefined-type and I is an identifier, is evaluated and classified as follows:
If E is a namespace and I is the name of an accessible member of that namespace, then the result is that member and, depending on the member, is classified as a namespace or a type.
If E is a predefined-type or a primary-expression classified as a type, and a member lookup (7.3) of I in E produces a match, then E.I is evaluated and classified as follows:
If I identifies a type, then the result is that type.
If I identifies one or more methods, then the result is a method group with no associated instance expression.
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-declarations (10.7), then E.I is processed exactly as if I was 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 an 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 produces a match, then E.I is evaluated and classified as follows:
First, 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 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-declarations (10.7), 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, E.I is an invalid member reference, and an error occurs.
In a member access of the form E.I, if E is a single identifier, and if the meaning of E as a simple-name (7.5.2) is a constant, field, property, local variable, or parameter with the same type as the meaning of E as a type-name (3.8), then both possible meanings of E are permitted. The two possible meanings of E.I are never ambiguous, since 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 of E where an error would have otherwise occurred. For example:
struct Color
}
class A
static void G()
}
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.
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.2). If the primary-expression is neither a method group nor a value of a delegate-type, an error occurs.
The optional argument-list (7.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 cannot be an operand of any operator, and is permitted only in the context of a statement-expression (8.6).
Otherwise, the result is a value of the type returned by the method or delegate.
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 and A is an optional argument-list, consists of the following steps:
The set of candidate methods for the method invocation is constructed. Starting with the set of methods associated with M, which were found by a previous member lookup (7.3), the set is reduced to those methods that are applicable with respect to the argument list A. The set reduction consists of applying the following rules to each method T.N in the set, where T is the type in which the method N is declared:
If N is not applicable with respect to A (7.4.2.1), then N is removed from the set.
If N is applicable with respect to A (7.4.2.1), then all methods declared in a base type of T are removed from the set.
If the resulting set of candidate methods is empty, then no applicable methods exist, and an error occurs. If the candidate methods are not all declared in the same type, the method invocation is ambiguous, and an error occurs (this latter situation can only occur for an invocation of a method in an interface that has multiple direct base interfaces, as described in 13.2.5).
The best method of the set of candidate methods is identified using the overload resolution rules of 7.4.2. If a single best method cannot be identified, the method invocation is ambiguous, and an error occurs.
Given a best method, the invocation of 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 requirement is satisfied, a compile-time error occurs.
Once a method has been selected and validated at compile-time by the above steps, the actual run-time invocation is processed according to the rules of function member invocation described in 7.4.3.
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 overload resolution on the set of applicable, accessible, non-override methods declared in that type and invoke the method thus selected.
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.2.1) with respect to the argument-list of the invocation-expression.
The run-time 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.NullReferenceException is thrown and no further steps are executed.
Otherwise, D is a reference to a delegate instance. A function member invocation (7.4.3) is performed on the method referenced by the delegate. If the method is an instance method, the instance of the invocation becomes the instance referenced by the delegate.
An element-access consists of a primary-expression-no-array-creation, 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-expression-no-array-creation [ expression-list ]
expression-list:
expression
expression-list , expression
If the primary-expression-no-array-creation 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-expression-no-array-creation 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).
For an array access, the primary-expression-no-array-creation 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, 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 run-time processing of an array access of the form P[A], where P is a primary-expression-no-array-creation 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, 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, since 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.NullReferenceException 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.
For an indexer access, the primary-expression-no-array-creation 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-expression-no-array-creation 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 A (7.4.2.1), then I is removed from the set.
If I is applicable with respect to A (7.4.2.1), then all indexers declared in a base type of S are removed from the set.
If the resulting set of candidate indexers is empty, then no applicable indexers exist, and an error occurs. If the candidate indexers are not all declared in the same type, the indexer access is ambiguous, and an error occurs (this latter situation can only occur for an indexer access on an instance of an interface that has multiple direct base interfaces).
The best indexer of the set of candidate indexers is identified using the overload resolution rules of 7.4.2. If a single best indexer cannot be identified, the indexer access is ambiguous, and an 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.13.1). In all other cases, the get-accessor is invoked to obtain the current value (7.1.1).
A this-access consists of the reserved word this.
this-access:
this
A this-access is permitted only in the block of an instance method, an instance accessor, or an instance constructor. It has one of the following meanings:
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 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 method or instance accessor of a struct, it is classified as a variable. The type of the variable is the struct within which the usage occurs, and the variable represents the struct for which the method or accessor was invoked. The this variable of an instance method of a struct behaves exactly the same as a ref parameter of the struct type.
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 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 constructor of a struct, it is classified as a variable. The type of the variable is 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-this in particular means that the variable must be definitely assigned in every execution path of the instance constructor.
Use of this in a primary-expression in a context other than the ones listed above is an error. In particular, it is not possible to refer to this in a static method, a static property accessor, or in a variable-initializer of a field declaration.
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. A base-access is permitted only in the block of an instance method, an instance accessor, or an instance constructor. 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], except this is viewed as an instance of the base class.
When a base-access references a function member (7.4), the function member is considered non-virtual for purposes of function member invocation (7.4.3). 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, an error occurs.
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 and a set accessor. If this is not the case, a compile-time error occurs.
Unary operator overload resolution (7.2.3) is applied to select a specific operator implementation. Predefined ++ and -- operators exist for the following types: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, and any enum type. The predefined ++ operators return the value produced by adding 1 to the operand, and the predefined -- operators return the value produced by subtracting 1 from the operand.
The run-time 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 of x 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, as described in 7.6.7. The result of x++ or x-- is the value of x before the operation, whereas the result of ++x or --x is the value of 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.
The new operator is used to create new instances of types.
new-expression:
object-creation-expression
delegate-creation-expression
There are three forms of new expressions:
Object creation expressions are used to create a 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.
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 )
The type of an object-creation-expression must be a class-type or a value-type. The type cannot be an abstract class-type.
The optional argument-list (7.4.1) is permitted only if the type is a class-type or a struct-type.
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 class-type or a struct-type:
If T is an abstract class-type, an error occurs.
The instance constructor to invoke is determined using the overload resolution rules of 7.4.2. The set of candidate instance constructors consists of all accessible instance constructors declared in T. If the set is empty, or if a single best constructor cannot be identified, an 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 an error occurs.
The run-time 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 invocation (7.4.3). A reference to the newly allocated instance is automatically passed to the instance constructor and the instance can be accessed from within the instance constructor as this.
If T is a struct-type:
An instance of type T is created by allocating a temporary local variable. Since 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 invocation (7.4.3). A reference to the newly allocated instance is automatically passed to the instance constructor and the instance can be accessed from within the instance constructor as this.
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
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. Since the length of an array dimension must be nonnegative, it is an error to specify a constant-expression that evaluates to a negative value.
For single-dimensional arrays, array elements are stored in increasing index order, starting with index 0 and ending with index Length - 1. For multi-dimensional arrays, array elements are stored such that the indices of the rightmost dimension are increased first, then the next left dimension, and so on to the left.
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 form, the rank of the specified array type 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[,] , , };
exactly corresponds to
new int[3, 2] , , };
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 run-time 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, 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 thus 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 sub-arrays, and the statement
int[][] a = new int[100][5]; // Error
is an error. Instantiation of the sub-arrays must instead be performed manually, as in
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, that is when the sub-arrays are all of the same length, it is more efficient to use a multi-dimensional array. In the example above, instantiation of the array of arrays creates 101 objects-one outer array and 100 sub-arrays. In contrast,
int[,] = new int[100, 5];
creates only a single object, a two-dimensional array, and accomplishes the allocation in a single statement.
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 (7.1) 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 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 set of methods identified by E must include exactly one method that is compatible (15.1) with D, and this method becomes the one to which the newly created delegate refers. If no matching method exists, or if more than one matching method exists, an error occurs. If the selected method is an instance method, the instance expression associated with E determines the target object of the delegate.
As in a method invocation, the selected method must be compatible with the context of the method group: If the method is a static method, the method group must have resulted from a simple-name or a member-access through a type. If the method is an instance method, the method group must have resulted from a simple-name or a member-access through a variable or value. If the selected method does not match the context of the method group, an error occurs.
The result is a value of type D, namely a newly created delegate that refers to the selected method and target object.
Otherwise, if E is a value of a delegate-type:
The delegate-type of E must be compatible (15.1) with D, or otherwise an error occurs.
The result is a value of type D, namely a newly created delegate that refers to the same invocation list as E.
Otherwise, the delegate creation expression is invalid, and an error occurs.
The run-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:
If the method selected at compile-time is a static method, the target object of the delegate is null. Otherwise, the selected method is an instance method, and the target object of the delegate is determined from the instance expression associated with E:
The instance expression is evaluated. If this evaluation causes an exception, no further steps are executed.
If the instance expression is of a reference-type, the value computed by the instance expression becomes the target object. If the target object is null, a System.NullReferenceException is thrown and no further steps are executed.
If the instance expression is of a value-type, a boxing operation (4.3.1) is performed to convert the value to an object, and this object becomes the target object.
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 a reference to the method that was determined at compile-time and a reference to the target object computed above.
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 references to the same invocation list as the delegate instance given by E.
The method and object to which a delegate refers are determined when the delegate is instantiated and then remain constant for the entire lifetime of the delegate. In other words, it is not possible to change the target method or object of a delegate once it has been created. (When two delegates are combined or one is removed from another, a new delegate results; no existing delegate has its content changed.)
It is not possible to create a delegate that refers to a property, indexer, user-defined operator, instance constructor, static constructor, or destructor.
As described above, 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
static double Square(double 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.
The typeof operator is used to obtain the System.Type object for a type.
typeof-expression:
typeof ( type
)
typeof ( void )
The first form of typeof-expression consists of a typeof keyword followed by a parenthesized 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.
The second form of typeof-expression consists of a typeof keyword followed by a parenthesized void keyword. The result of this form is a System.Type object that represents the lack of a type. The type object returned is distinct from the type object returned for any type. This special type object is useful in 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 example
class
Test
;
for (int i = 0; i <
t.Length; i++)
}
}
produces the following output:
Int32
Int32
String
Double[]
Void
Note that int and System.Int32 are the same type.
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, and the unchecked operator evaluates the contained expression in an unchecked context. A checked-expression or unchecked-expression corresponds exactly to a parenthesized-expression (7.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.7), 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.
When one of the above operations produce 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.15), a compile-time error occurs. Otherwise, when the operation is performed at run-time, a System.OverflowException 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 non-constant expressions (expressions that are evaluated at run-time) 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 (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.
In the example
class
Test
static int G()
static int H()
}
no compile-time errors are reported since neither of the expressions can be evaluated at compile-time. At run-time, 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
static int G()
static int H()
}
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(), but since the evaluation takes place in an unchecked context, the overflow is not reported.
The checked and unchecked operators only affect the overflow checking context for those operations that are textually contained within the "(" and ")" tokens. The operators have no effect on function members that are invoked as a result of evaluating the contained expression. In the example
class
Test
static int F()
}
the use of checked in F() does not affect the evaluation of x * y in Multiply(), and x * y is therefore 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
Both of the hexadecimal constants above 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.
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 1007
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved