10. Classes

A class is a data structure that may contain data members (constants and fields), function members (methods, properties, events, indexers, operators, instance constructors, destructors, and static constructors), and nested types. Class types support inheritance, a mechanism whereby a derived class can extend and specialize a base class.

10.1 Class Declarations

A class-declaration is a type-declaration9.6) that declares a new class.

        class-declaration:
                attributesopt class-modifiersopt partialopt class identifier type-parameter-listopt
                                            class-baseopt type-parameter-constraints-clausesopt class-body ;opt

A class-declaration consists of an optional set of attributes (§17), followed by an optional set of class-modifiers10.1.1), followed by an optional partial modifier, followed by the keyword class and an identifier that names the class, followed by an optional type-parameter-list10.1.3), followed by an optional class-base specification (§10.1.4), followed by an optional set of type-parameter-constraints-clauses10.1.5), followed by a class-body10.1.6), optionally followed by a semicolon.

A class declaration cannot supply type-parameter-constraints-clauses unless it also supplies a type-parameter-list.

A class declaration that supplies a type-parameter-list is a generic class declaration. Additionally, any class nested inside a generic class declaration or a generic struct declaration is itself a generic class declaration, because type parameters for the containing type must be supplied to create a constructed type.

10.1.1 Class Modifiers

A class-declaration may optionally include a sequence of class modifiers:

        class-modifiers:
                class-modifier class-modifiers
                class-modifier

        class-modifier:

                new
                public
                protected
                internal
                private
                abstract
                sealed
                static

It is a compile-time error for the same modifier to appear multiple times in a class declaration.

The new modifier is permitted on nested classes. It specifies that the class hides an inherited member by the same name, as described in §10.3.4. It is a compile-time error for the new modifier to appear on a class declaration that is not a nested class declaration.

The public, protected, internal, and private modifiers control the accessibility of the class. Depending on the context in which the class declaration occurs, some of these modifiers may not be permitted (§3.5.1).

The abstract, sealed, and static modifiers are discussed in the following sections.

10.1.1.1 Abstract Classes

The abstract modifier is used to indicate that a class is incomplete and that it is intended to be used only as a base class. An abstract class differs from a nonabstract class in the following ways:

•  An abstract class cannot be instantiated directly, and it is a compile-time error to use the new operator on an abstract class. While it is possible to have variables and values whose compile-time types are abstract, such variables and values will necessarily either be null or contain references to instances of nonabstract classes derived from the abstract types.

•  An abstract class is permitted (but not required) to contain abstract members.

•  An abstract class cannot be sealed.

When a nonabstract class is derived from an abstract class, the nonabstract class must include actual implementations of all inherited abstract members, thereby overriding those abstract members. In the example

        abstract class A
        {
                public abstract void F();
        }
        abstract class B: A
        {
                public void G() {}
        }
        class C: B
        {
                public override void F() {
                        // Actual implementation of F
                }
        }

the abstract class A introduces an abstract method F. Class B introduces an additional method G, but because it doesn’t provide an implementation of F, B must also be declared as abstract. Class C overrides F and provides an actual implementation. Because there are no abstract members in C, C is permitted (but not required) to be nonabstract.

10.1.1.2 Sealed Classes

The sealed modifier is used to prevent derivation from a class. A compile-time error occurs if a sealed class is specified as the base class of another class.

A sealed class cannot also be an abstract class.

The sealed modifier is primarily used to prevent unintended derivation, but it also enables certain runtime optimizations. In particular, because a sealed class is known to never have any derived classes, it is possible to transform virtual function member invocations on sealed class instances into nonvirtual invocations.

10.1.1.3 Static Classes

The static modifier is used to mark the class being declared as a static class. A static class cannot be instantiated, cannot be used as a type, and can contain only static members. Only a static class can contain declarations of extension methods (§10.6.9).

A static class declaration is subject to the following restrictions:

•  A static class may not include a sealed or abstract modifier. Because a static class cannot be instantiated or derived from, however, it behaves as if it was both sealed and abstract.

•  A static class may not include a class-base specification (§10.1.4) and cannot explicitly specify a base class or a list of implemented interfaces. A static class implicitly inherits from type object.

•  A static class can contain only static members (§10.3.7). Note that constants and nested types are classified as static members.

•  A static class cannot have members with protected or protected internal declared accessibility.

It is a compile-time error to violate any of these restrictions.

A static class has no instance constructors. It is not possible to declare an instance constructor in a static class, and no default instance constructor (§10.11.4) is provided for a static class.

The members of a static class are not automatically static, and the member declarations must explicitly include a static modifier (except for constants and nested types). When a class is nested within a static outer class, the nested class is not a static class unless it explicitly includes a static modifier.

10.1.1.3.1 Referencing Static Class Types

A namespace-or-type-name3.8) is permitted to reference a static class if

•  The namespace-or-type-name is the T in a namespace-or-type-name of the form T.I, or

•  The namespace-or-type-name is the T in a typeof-expression7.5.11) of the form typeof(T).

A primary-expression7.5) is permitted to reference a static class if

•  The primary-expression is the E in a member-access7.5.4) of the form E.I.

In any other context, it is a compile-time error to reference a static class. For example, it is an error for a static class to be used as a base class, a constituent type (§10.3.8) of a member, a generic type argument, or a type parameter constraint. Likewise, a static class cannot be used in an array type, a pointer type, a new expression, a cast expression, an is expression, an as expression, a sizeof expression, or a default value expression.

10.1.2 partial Modifier

The partial modifier is used to indicate that this class-declaration is a partial type declaration. Multiple partial type declarations with the same name within an enclosing namespace or type declaration combine to form one type declaration, following the rules specified in §10.2.

Having the declaration of a class distributed over separate segments of program text can be useful if these segments are produced or maintained in different contexts. For instance, one part of a class declaration may be machine generated, whereas the other is manually authored. Textual separation of the two prevents updates by one class declaration from conflicting with updates by the other.

10.1.3 Type Parameters

A type parameter is a simple identifier that denotes a placeholder for a type argument supplied to create a constructed type. A type parameter is a formal placeholder for a type that will be supplied later. By contrast, a type argument (§4.4.1) is the actual type that is substituted for the type parameter when a constructed type is created.

        type-parameter-list:
                < type-parameters >

        type-parameters:
                attributesopt type-parameter
                type-parameters , attributesopt type-parameter

        type-parameter:
                identifier

Each type parameter in a class declaration defines a name in the declaration space (§3.3) of that class. Thus it cannot have the same name as another type parameter or a member declared in that class. A type parameter cannot have the same name as the type itself.

10.1.4 Class Base Specification

A class declaration may include a class-base specification, which defines the direct base class of the class and the interfaces (§13) directly implemented by the class.

        class-base:
                : class-type
                : interface-type-list
                : class-type , interface-type-list

        interface-type-list:
                interface-type
                interface-type-list , interface-type

The base class specified in a class declaration can be a constructed class type (§4.4). A base class cannot be a type parameter on its own, although it can involve the type parameters that are in scope.

        class Extend<V>: V {}                        // Error, type parameter used as base class

10.1.4.1 Base Classes

When a class-type is included in the class-base, it specifies the direct base class of the class being declared. If a class declaration has no class-base, or if the class-base lists only interface types, the direct base class is assumed to be object. A class inherits members from its direct base class, as described in §10.3.3.

In the example

        class A {}
        class B: A {}

class A is said to be the direct base class of B, and B is said to be derived from A. Because A does not explicitly specify a direct base class, its direct base class is implicitly object.

For a constructed class type, if a base class is specified in the generic class declaration, the base class of the constructed type is obtained by substituting, for each type-parameter in the base class declaration, the corresponding type-argument of the constructed type. Given the generic class declarations

        class B<U,V> {…}
        class G<T>: B<string,T[]> {…}

the base class of the constructed type G<int> would be B<string,int[]>.

The direct base class of a class type must be at least as accessible as the class type itself (§3.5.2). For example, it is a compile-time error for a public class to derive from a private or internal class.

The direct base class of a class type must not be any of the following types: System.Array, System.Delegate, System.MulticastDelegate, System.Enum, or System.ValueType. Furthermore, a generic class declaration cannot use System.Attribute as a direct or indirect base class.

While determining the meaning of the direct base class specification A of a class B, the direct base class of B is temporarily assumed to be object. Intuitively, this practice ensures that the meaning of a base class specification cannot recursively depend on itself. The example

        class A<T> {
                public class B{}
        }
        class C : A<C.B> {}

is in error because in the base class specification A<C.B>, the direct base class of C is considered to be object; hence (by the rules of §3.8), C is not considered to have a member B.

The base classes of a class type are the direct base class and its base classes. In other words, the set of base classes is the transitive closure of the direct base class relationship. Referring to the previous example, the base classes of B are A and object. In the example

        class A {…}
        class B<T>: A {…}
        class C<T>: B<IComparable<T>> {…}
        class D<T>: C<T[]> {…}

the base classes of D<int> are C<int[]>, B<IComparable<int[]>>, A, and object.

Except for class object, every class type has exactly one direct base class. The object class has no direct base class and is the ultimate base class of all other classes.

When a class B derives from a class A, it is a compile-time error for A to depend on B. A class directly depends on its direct base class (if any) and directly depends on the class within which it is immediately nested (if any). Given this definition, the complete set of classes upon which a class depends is the reflexive and transitive closure of the directly depends on relationship.

The example

        class A: A {}

is erroneous because the class depends on itself. Likewise, the example

        class A: B {}
        class B: C {}
        class C: A {}

is in error because the classes circularly depend on themselves. Finally, the example

        class A: B.C {}
        class B: A
        {
                public class C {}
        }

results in a compile-time error because A depends on B.C (its direct base class), which depends on B (its immediately enclosing class), which circularly depends on A.

Note that a class does not depend on the classes that are nested within it. In the example

        class A
        {
                class B: A {}
        }

B depends on A (because A is both its direct base class and its immediately enclosing class), but A does not depend on B (because B is neither a base class nor an enclosing class of A). Thus the example is valid.

It is not possible to derive from a sealed class. In the example

        sealed class A {}
        class B: A {}                        // Error, cannot derive from a sealed class

class B is in error because it attempts to derive from the sealed class A.

10.1.4.2 Interface Implementations

A class-base specification may include a list of interface types, in which case the class is said to directly implement the given interface types. Interface implementations are discussed further in §13.4.

10.1.5 Type Parameter Constraints

Generic type and method declarations can optionally specify type parameter constraints by including type-parameter-constraints-clauses.

        type-parameter-constraints-clauses:
                type-parameter-constraints-clause
                type-parameter-constraints-clauses type-parameter-constraints-clause

        type-parameter-constraints-clause:
                where type-parameter : type-parameter-constraints

        type-parameter-constraints:
                primary-constraint
                secondary-constraints
                constructor-constraint
                primary-constraint , secondary-constraints
                primary-constraint , constructor-constraint
                secondary-constraints , constructor-constraint
                primary-constraint , secondary-constraints , constructor-constraint

        primary-constraint:
                class-type
                class
                struct
        secondary-constraints:
                interface-type
                type-parameter
                secondary-constraints , interface-type
                secondary-constraints , type-parameter
        constructor-constraint:
                new ( )

Each type-parameter-constraints-clause consists of the token where, followed by the name of a type parameter, followed by a colon and the list of constraints for that type parameter. There can be at most one where clause for each type parameter, and the where clauses can be listed in any order. Like the get and set tokens in a property accessor, the where token is not a keyword.

The list of constraints given in a where clause can include any of the following components, in this order: a single primary constraint, one or more secondary constraints, and the constructor constraint, new().

A primary constraint can be a class type or the reference type constraint class or the value type constraint struct. A secondary constraint can be a type-parameter or interface-type.

The reference type constraint specifies that a type argument used for the type parameter must be a reference type. All class types, interface types, delegate types, array types, and type parameters known to be a reference type (as defined below) satisfy this constraint.

The value type constraint specifies that a type argument used for the type parameter must be a non-nullable value type. All non-nullable struct types, enum types, and type parameters having the value type constraint satisfy this constraint. Although classified as a value type, a nullable type (§4.1.10) does not satisfy the value type constraint. A type parameter having the value type constraint cannot also have the constructor-constraint.

Pointer types are never allowed to be type arguments and are not considered to satisfy either the reference type constraints or the value type constraints.

If a constraint is a class type, an interface type, or a type parameter, that type specifies a minimal “base type” that every type argument used for that type parameter must support. Whenever a constructed type or generic method is used, the type argument is checked against the constraints on the type parameter at compile time. The type argument supplied must satisfy the conditions described in section 4.4.4.

A class-type constraint must satisfy the following rules:

•  The type must be a class type.

•  The type must not be sealed.

•  The type must not be one of the following types: System.Array, System.Delegate, System.Enum, or System.ValueType.

•  The type must not be object. Because all types derive from object, such a constraint would have no effect if it were permitted.

•  At most one constraint for a given type parameter can be a class type.

A type specified as an interface-type constraint must satisfy the following rules:

•  The type must be an interface type.

•  A type must not be specified more than once in a given where clause.

In either case, the constraint can involve any of the type parameters of the associated type or method declaration as part of a constructed type, and can involve the type being declared.

Any class or interface type specified as a type parameter constraint must be at least as accessible (§3.5.4) as the generic type or method being declared.

A type specified as a type-parameter constraint must satisfy the following rules:

•  The type must be a type parameter.

•  A type must not be specified more than once in a given where clause.

In addition, there must be no cycles in the dependency graph of type parameters, where dependency is a transitive relation defined by the following criteria:

•  If a type parameter T is used as a constraint for type parameter S then S depends on T.

•  If a type parameter S depends on a type parameter T and T depends on a type parameter U, then S depends on U.

Given this relation, it is a compile-time error for a type parameter to depend on itself (directly or indirectly).

Any constraints must be consistent among dependent type parameters. If type parameter S depends on type parameter T, then:

•  T must not have the value type constraint. Otherwise, T is effectively sealed so S would be forced to be the same type as T, eliminating the need for two type parameters.

•  If S has the value type constraint, then T must not have a class-type constraint.

•  If S has a class-type constraint A and T has a class-type constraint B, then there must be an identity conversion or implicit reference conversion from A to B or an implicit reference conversion from B to A.

•  If S also depends on type parameter U and U has a class-type constraint A and T has a class-type constraint B, then there must be an identity conversion or implicit reference conversion from A to B or an implicit reference conversion from B to A.

It is valid for S to have the value type constraint and T to have the reference type constraint. This scheme effectively limits T to the types System.Object, System.ValueType, System.Enum, and any interface type.

If the where clause for a type parameter includes a constructor constraint [that has the form new()], the new operator can be used to create instances of the type (§7.5.10.1). Any type argument used for a type parameter with a constructor constraint must have a public parameterless constructor (this constructor implicitly exists for any value type) or be a type parameter having the value type constraint or constructor constraint (see §10.1.5 for details).

The following are examples of constraints:

        interface IPrintable
        {
                void Print();
        }
        interface IComparable<T>
        {
                int CompareTo(T value);
        }
        interface IKeyProvider<T>
        {
                T GetKey();
        }
        class Printer<T> where T: IPrintable {…}
        class SortedList<T> where T: IComparable<T> {…}
        class Dictionary<K,V>
                where K: IComparable<K>
                where V: IPrintable, IKeyProvider<K>, new()
        {
                …
        }

The following example is in error because it causes a circularity in the dependency graph of the type parameters:

        class Circular<S,T>
                where S: T
                where T: S                        // Error, circularity in dependency graph
        {
                …
        }

The following examples illustrate additional invalid situations:

        class Sealed<S,T>
                where S: T
                where T: struct                        // Error, T is sealed
        {
                …
        }
        class A {…}
        class B {…}
        class Incompat<S,T>
                where S: A, T
                where T: B                                // Error, incompatible class-type constraints
        {
                …
        }
        class StructWithClass<S,T,U>
                where S: struct, T
                where T: U
                where U: A                                // Error, A incompatible with struct
        {
                …
        }

The effective base class of a type parameter T is defined as follows:

•  If T has no primary constraints or type parameter constraints, its effective base class is object.

•  If T has the value type constraint, its effective base class is System.ValueType.

•  If T has a class-type constraint C but no type-parameter constraints, its effective base class is C.

•  If T has no class-type constraint but has one or more type-parameter constraints, its effective base class is the most encompassed type (§6.4.2) in the set of effective base classes of its type-parameter constraints. The consistency rules ensure that such a most encompassed type exists.

•  If T has both a class-type constraint and one or more type-parameter constraints, its effective base class is the most encompassed type (§6.4.2) in the set consisting of the class-type constraint of T and the effective base classes of its type-parameter constraints. The consistency rules ensure that such a most encompassed type exists.

•  If T has the reference type constraint but no class-type constraints, its effective base class is object.

The effective interface set of a type parameter T is defined as follows:

•  If T has no secondary-constraints, its effective interface set is empty.

•  If T has interface-type constraints but no type-parameter constraints, its effective interface set is its set of interface-type constraints.

•  If T has no interface-type constraints but has type-parameter constraints, its effective interface set is the union of the effective interface sets of its type-parameter constraints.

•  If T has both interface-type constraints and type-parameter constraints, its effective interface set is the union of its set of interface-type constraints and the effective interface sets of its type-parameter constraints.

A type parameter is known to be a reference type if it has the reference type constraint or its effective base class is not object or System.ValueType.

Values of a constrained type parameter type can be used to access the instance members implied by the constraints. In the example

        interface IPrintable
        {
                void Print();
        }
        class Printer<T> where T: IPrintable
        {
                void PrintOne(T x) {
                        x.Print();
                }
        }

the methods of IPrintable can be invoked directly on x because T is constrained to always implement IPrintable.

10.1.6 Class Body

The class-body of a class defines the members of that class.

        class-body:
               { class-member-declarationsopt }

10.2 Partial Types

A type declaration can be split across multiple partial type declarations. The type declaration is constructed from its parts by following the rules in this section, whereupon it is treated as a single declaration during the remainder of the compile-time and runtime processing of the program.

A class-declaration, struct-declaration, or interface-declaration represents a partial type declaration if it includes a partial modifier. Note that partial is not a keyword, and it acts as a modifier only if it appears immediately before one of the keywords class, struct, or interface in a type declaration, or before the type void in a method declaration. In other contexts, it can be used as a normal identifier.

Each part of a partial type declaration must include a partial modifier. It must have the same name and be declared in the same namespace or type declaration as the other parts. The partial modifier indicates that additional parts of the type declaration may exist elsewhere, but the existence of such additional parts is not a requirement; it is valid for a type with a single declaration to include the partial modifier.

All parts of a partial type must be compiled together such that the parts can be merged at compile time into a single type declaration. Partial types specifically do not allow already compiled types to be extended.

Nested types may be declared in multiple parts by using the partial modifier. Typically, the containing type is declared using partial as well, and each part of the nested type is declared in a different part of the containing type.

The partial modifier is not permitted on delegate or enum declarations.

10.2.1 Attributes

The attributes of a partial type are determined by combining, in an unspecified order, the attributes of each of the parts. If an attribute is placed on multiple parts, it is equivalent to specifying the attribute multiple times on the type. For example, the two parts

        [Attr1, Attr2("hello")]
        partial class A {}
        [Attr3, Attr2("goodbye")]
        partial class A {}

are equivalent to a declaration such as

        [Attr1, Attr2("hello"), Attr3, Attr2("goodbye")]
        class A {}

Attributes on type parameters combine in a similar fashion.

10.2.2 Modifiers

When a partial type declaration includes an accessibility specification (the public, protected, internal, and private modifiers), it must agree with all other parts that include an accessibility specification. If no part of a partial type includes an accessibility specification, the type is given the appropriate default accessibility (§3.5.1).

If one or more partial declarations of a nested type include a new modifier, no warning is reported if the nested type hides an inherited member (§3.7.1.2).

If one or more partial declarations of a class include an abstract modifier, the class is considered abstract (§10.1.1.1). Otherwise, the class is considered nonabstract.

If one or more partial declarations of a class include a sealed modifier, the class is considered sealed (§10.1.1.2). Otherwise, the class is considered unsealed.

Note that a class cannot be both abstract and sealed.

When the unsafe modifier is used on a partial type declaration, only that particular part is considered an unsafe context (§18.1).

10.2.3 Type Parameters and Constraints

If a generic type is declared in multiple parts, each part must state the type parameters. Each part must have the same number of type parameters, and the same name for each type parameter, in order.

When a partial generic type declaration includes constraints (where clauses), the constraints must agree with all other parts that include constraints. Specifically, each part that includes constraints must have constraints for the same set of type parameters, and for each type parameter the sets of primary, secondary, and constructor constraints must be equivalent. Two sets of constraints are equivalent if they contain the same members. If no part of a partial generic type specifies type parameter constraints, the type parameters are considered unconstrained.

The example

        partial class Dictionary<K,V>
                where K: IComparable<K>
                where V: IKeyProvider<K>, IPersistable
        {
                …
        }
        partial class Dictionary<K,V>
                where V: IPersistable, IKeyProvider<K>
                where K: IComparable<K>
        {                …
        }
        partial class Dictionary<K,V>
        {
                …
        }

is correct because those parts that include constraints (the first two) effectively specify the same set of primary, secondary, and constructor constraints for the same set of type parameters, respectively.

10.2.4 Base Class

When a partial class declaration includes a base class specification, it must agree with all other parts that include a base class specification. If no part of a partial class includes a base class specification, the base class becomes System.Object10.1.4.1).

10.2.5 Base Interfaces

The set of base interfaces for a type declared in multiple parts is the union of the base interfaces specified on each part. A particular base interface may be named only once on each part, but it is permitted for multiple parts to name the same base interface(s). There must be only one implementation of the members of any given base interface.

In the example

        partial class C: IA, IB {…}
        partial class C: IC {…}
        partial class C: IA, IB {…}

the set of base interfaces for class C is IA, IB, and IC.

Typically, each part provides an implementation of the interface(s) declared on that part; however, this is not a requirement. A part may provide the implementation for an interface declared on a different part:

        partial class X
        {
                int IComparable.CompareTo(object o) {…}        }
        partial class X: IComparable
        {
        …
        }

10.2.6 Members

With the exception of partial methods (§10.2.7), the set of members of a type declared in multiple parts is simply the union of the set of members declared in each part. The bodies of all parts of the type declaration share the same declaration space (§3.3), and the scope of each member (§3.7) extends to the bodies of all the parts. The accessibility domain of any member always includes all the parts of the enclosing type; a private member declared in one part is freely accessible from another part. It is a compile-time error to declare the same member in more than one part of the type, unless that member is a type with the partial modifier.

        partial class A
        {
                int x;                                         // Error, cannot declare x more than once
                partial class Inner                 // Okay, Inner is a partial type
                {
                int y;
                }
        }
        partial class A
        {
                int x;                                        // Error, cannot declare x more than once
                partial class Inner                // Okay, Inner is a partial type
                {
                int z;
                }
        }

Although the ordering of members within a type is not significant to C# code, it may be significant when that code must interface with other languages and environments. In these cases, the ordering of members within a type declared in multiple parts is undefined.

10.2.7 Partial Methods

Partial methods can be defined in one part of a type declaration and implemented in another part. The implementation is optional; if no part implements the partial method, the partial method declaration and all calls to it are removed from the type declaration resulting from the combination of the parts.

Partial methods cannot define access modifiers, but are implicitly private. Their return type must be void, and their parameters cannot have the out modifier. The identifier partial is recognized as a special keyword in a method declaration only if it appears immediately before the void type; otherwise, it can be used as a normal identifier. A partial method cannot explicitly implement interface methods.

There are two kinds of partial method declarations: If the body of the method declaration is a semicolon, the declaration is said to be a defining partial method declaration. If the body is given as a block, the declaration is said to be an implementing partial method declaration. Across the parts of a type declaration, there can be only one defining partial method declaration with a given signature, and there can be only one implementing partial method declaration with a given signature. If an implementing partial method declaration is given, a corresponding defining partial method declaration must exist, and the declarations must match as specified here:

•  The declarations must have the same modifiers (although not necessarily in the same order), method name, number of type parameters, and number of parameters.

•  Corresponding parameters in the declarations must have the same modifiers (although not necessarily in the same order) and the same types (modulo differences in type parameter names).

•  Corresponding type parameters in the declarations must have the same constraints (modulo differences in type parameter names).

An implementing partial method declaration can appear in the same part as the corresponding defining partial method declaration.

Only a defining partial method participates in overload resolution. Thus, whether or not an implementing declaration is given, invocation expressions may resolve to invocations of the partial method. Because a partial method always returns void, such invocation expressions will always be expression statements. Furthermore, because a partial method is implicitly private, such statements will always occur within one of the parts of the type declaration within which the partial method is declared.

If no part of a partial type declaration contains an implementing declaration for a given partial method, any expression statement invoking it is simply removed from the combined type declaration. Thus the invocation expression, including any constituent expressions, has no effect at runtime. The partial method itself is also removed and will not be a member of the combined type declaration.

If an implementing declaration exists for a given partial method, the invocations of the partial methods are retained. The partial method gives rise to a method declaration similar to the implementing partial method declaration except for the following:

•  The partial modifier is not included.

•  The attributes in the resulting method declaration are the combined attributes of the defining and the implementing partial method declaration in unspecified order. Duplicates are not removed.

•  The attributes on the parameters of the resulting method declaration are the combined attributes of the corresponding parameters of the defining and the implementing partial method declaration in unspecified order. Duplicates are not removed.

If a defining declaration but not an implementing declaration is given for a partial method M, the following restrictions apply:

•  It is a compile-time error to create a delegate to the method (§7.5.10.5).

•  It is a compile-time error to refer to M inside an anonymous function that is converted to an expression tree type (§6.5.2).

•  Expressions occurring as part of an invocation of M do not affect the definite assignment state (§5.3), which can potentially lead to compile-time errors.

•  M cannot be the entry point for an application (§3.1).

Partial methods are useful for allowing one part of a type declaration to customize the behavior of another part—for example, one that is generated by a tool. Consider the following partial class declaration:

        partial class Customer
        {
                string name;
                public string Name {
                        get { return name; }
                        set {
                                OnNameChanging(value);
                                name = value;
                                OnNameChanged();
                        }
                }
                partial void OnNameChanging(string newName);
                partial void OnNameChanged();
        }

If this class is compiled without any other parts, the defining partial method declarations and their invocations will be removed, and the resulting combined class declaration will be equivalent to the following:

        class Customer
        {
                string name;
                public string Name {
                        get { return name; }
                        set { name = value; }
                }
        }

Assume that another part is given, however, which provides implementing declarations of the partial methods:

        partial class Customer
        {
                partial void OnNameChanging(string newName)
                {
                        Console.WriteLine("Changing " + name + " to " + newName);
                }
                partial void OnNameChanged()
                {
                        Console.WriteLine("Changed to " + name);
                }
        }

Then the resulting combined class declaration will be equivalent to the following:

        class Customer
        {
                string name;
                public string Name {
                        get { return name; }
                        set {
                                OnNameChanging(value);
                                name = value;
                        OnNameChanged();
                        }
                }
                void OnNameChanging(string newName)
                {
                 Console.WriteLine("Changing " + name + " to " + newName);
                }
                void OnNameChanged()
                {
                        Console.WriteLine("Changed to " + name);
                }
        }

10.2.8 Name Binding

Although each part of an extensible type must be declared within the same namespace, the parts are typically written within different namespace declarations. As a consequence, different using directives (§9.4) may be present for each part. When interpreting simple names (§7.5.2) within one part, only the using directives of the namespace declaration(s) enclosing that part are considered. This may result in the same identifier having different meanings in different parts:

        namespace N
        {
                using List = System.Collections.ArrayList;
                partial class A
                {
                        List x;                                // x has type System.Collections.ArrayList
                }
        }

        namespace N
        {
                using List = Widgets.LinkedList;
                partial class A
                {
                        List y;                                // y has type Widgets.LinkedList
                }
        }

10.3 Class Members

The members of a class consist of the members introduced by its class-member-declarations and the members inherited from the direct base class.

        class-member-declarations:
                class-member-declaration
                class-member-declarations class-member-declaration

        class-member-declaration:
                constant-declaration
                field-declaration
                method-declaration
                property-declaration
                event-declaration
                indexer-declaration
                operator-declaration
                constructor-declaration
                destructor-declaration
                static-constructor-declaration
                type-declaration

The members of a class type are classified into the following categories:

•  Constants, which represent constant values associated with the class (§10.4)

•  Fields, which are the variables of the class (§10.5)

•  Methods, which implement the computations and actions that can be performed by the class (§10.6)

•  Properties, which define named characteristics and the actions associated with reading and writing those characteristics (§10.7)

•  Events, which define notifications that can be generated by the class (§10.8)

•  Indexers, which permit instances of the class to be indexed in the same way (syntactically) as arrays (§10.9)

•  Operators, which define the expression operators that can be applied to instances of the class (§10.10)

•  Instance constructors, which implement the actions required to initialize instances of the class (§10.11)

•  Destructors, which implement the actions to be performed before instances of the class are permanently discarded (§10.13)

•  Static constructors, which implement the actions required to initialize the class itself (§10.12)

•  Types, which represent the types that are local to the class (§10.3.8)

Members that can contain executable code are collectively known as the function members of the class type. The function members of a class type are the methods, properties, events, indexers, operators, instance constructors, destructors, and static constructors of that class type.

A class-declaration creates a new declaration space (§3.3), and the class-member-declarations immediately contained by the class-declaration introduce new members into this declaration space. The following rules apply to class-member-declarations:

•  Instance constructors, destructors, and static constructors must have the same name as the immediately enclosing class. All other members must have names that differ from the name of the immediately enclosing class.

•  The name of a constant, field, property, event, or type must differ from the names of all other members declared in the same class.

•  The name of a method must differ from the names of all other nonmethods declared in the same class. In addition, the signature (§3.6) of a method must differ from the signatures of all other methods declared in the same class, and two methods declared in the same class may not have signatures that differ solely by ref and out.

•  The signature of an instance constructor must differ from the signatures of all other instance constructors declared in the same class, and two constructors declared in the same class may not have signatures that differ solely by ref and out.

•  The signature of an indexer must differ from the signatures of all other indexers declared in the same class.

•  The signature of an operator must differ from the signatures of all other operators declared in the same class.

The inherited members of a class type (§10.3.3) are not part of the declaration space of a class. Thus a derived class is allowed to declare a member with the same name or signature as an inherited member (which, in effect, hides the inherited member).

10.3.1 The Instance Type

Each class declaration has an associated bound type (§4.4.3), known as the instance type. For a generic class declaration, the instance type is formed by creating a constructed type (§4.4) from the type declaration, with each of the supplied type arguments being the corresponding type parameter. Because the instance type uses the type parameters, it can be used only where the type parameters are in scope—that is, inside the class declaration. The instance type is the type of this for code written inside the class declaration. For non-generic classes, the instance type is simply the declared class. Here are several class declarations along with their instance types:

        class A<T>                                        // Instance type: A<T>
        {
                class B {}                                  // Instance type: A<T>.
                class C<U> {}                          // Instance type: A<T>.C<U>
        }
        class D {}                                          // Instance type: D

10.3.2 Members of Constructed Types

The non-inherited members of a constructed type are obtained by substituting, for each type-parameter in the member declaration, the corresponding type-argument of the constructed type. The substitution process is based on the semantic meaning of type declarations, and is not simply textual substitution.

For example, given the generic class declaration

        class Gen<T,U>
        {
                public T[,] a;
                public void G(int i, T t, Gen<U,T> gt) {…}
                public U Prop { get {…} set {…} }
                public int H(double d) {…}
        }

the constructed type Gen<int[],IComparable<string>> has the following members:

        public int[,][] a;
        public void G(int i, int[] t, Gen<IComparable<string>,int[]> gt) {…}
        public IComparable<string> Prop { get {…} set {…} }
        public int H(double d) {…}

The type of the member a in the generic class declaration Gen is “two-dimensional array of T,” so the type of the member a in the constructed type given above is “two-dimensional array of one-dimensional array of int,” or int[,][].

Within instance function members, the type of this is the instance type (§10.3.1) of the containing declaration.

All members of a generic class can use type parameters from any enclosing class, either directly or as part of a constructed type. When a particular closed constructed type (§4.4.2) is used at runtime, each use of a type parameter is replaced with the actual type argument supplied to the constructed type. For example,

        class C<V>
        {
                public V f1;
                public C<V> f2 = null;
                public C(V x) {
                        this.f1 = x;
                        this.f2 = this;                }
        }
        class Application
        {
                static void Main() {
                        C<int> x1 = new C<int>(1);
                        Console.WriteLine(x1.f1);         // Prints 1
                C<double> x2 = new C<double>(3.1415);
                Console.WriteLine(x2.f1);                //Prints 3.1415
                }
        }

10.3.3 Inheritance

A class inherits the members of its direct base class type. Inheritance means that a class implicitly contains all members of its direct base class type, except for the instance constructors, destructors, and static constructors of the base class. Some important aspects of inheritance are outlined here:

•  Inheritance is transitive. If C is derived from B, and B is derived from A, then C inherits the members declared in B as well as the members declared in A.

•  A derived class extends its direct base class. A derived class can add new members to those it inherits, but it cannot remove the definition of an inherited member.

•  Instance constructors, destructors, and static constructors are not inherited, but all other members are, regardless of their declared accessibility (§3.5). However, depending on their declared accessibility, inherited members might not be accessible in a derived class.

•  A derived class can hide3.7.1.2) inherited members by declaring new members with the same name or signature. Hiding an inherited member does not remove that member, however—it merely makes that member inaccessible directly through the derived class.

•  An instance of a class contains a set of all instance fields declared in the class and its base classes, and an implicit conversion (§6.1.6) exists from a derived class type to any of its base class types. Thus a reference to an instance of some derived class can be treated as a reference to an instance of any of its base classes.

•  A class can declare virtual methods, properties, and indexers, and derived classes can override the implementation of these function members. This ability enables classes to exhibit polymorphic behavior wherein the actions performed by a function member invocation varies depending on the runtime type of the instance through which that function member is invoked.

The inherited members of a constructed class type are the members of the immediate base class type (§10.1.4.1), which is found by substituting the type arguments of the constructed type for each occurrence of the corresponding type parameters in the base-class-specification. These members, in turn, are transformed by substituting, for each type-parameter in the member declaration, the corresponding type-argument of the base-class-specification.

        class B<U>
        {
                public U F(long index) {…}
        }
        class D<T>: B<T[]>
        {
                public T G(string s) {…}
        }

In the preceding example, the constructed type D<int> has a non-inherited member public int G(string s) obtained by substituting the type argument int for the type parameter T. D<int> also has an inherited member from the class declaration B. This inherited member is determined by first determining the base class type B<int[]> of D<int> by substituting int for T in the base class specification B<T[]>. Then, as a type argument to B, int[] is substituted for U in public U F(long index), yielding the inherited member public int[] F(long index).

10.3.4 The new Modifier

A class-member-declaration is permitted to declare a member with the same name or signature as an inherited member. In this situation, the derived class member is said to hide the base class member. Hiding an inherited member is not considered an error, but it does cause the compiler to issue a warning. To suppress the warning, the declaration of the derived class member can include a new modifier to indicate that the derived member is intended to hide the base member. This topic is discussed further in §3.7.1.2.

If a new modifier is included in a declaration that doesn’t hide an inherited member, a warning to that effect is issued. This warning is suppressed by removing the new modifier.

10.3.5 Access Modifiers

A class-member-declaration can have any one of the five possible kinds of declared accessibility (§3.5.1): public, protected internal, protected, internal, or private. Except for the protected internal combination, it is a compile-time error to specify more than one access modifier. When a class-member-declaration does not include any access modifiers, private is assumed.

10.3.6 Constituent Types

Types that are used in the declaration of a member are called the constituent types of that member. Possible constituent types are the type of a constant, field, property, event, or indexer, the return type of a method or operator, and the parameter types of a method, indexer, operator, or instance constructor. The constituent types of a member must be at least as accessible as that member itself (§3.5.4).

10.3.7 Static and Instance Members

Members of a class are either static members or instance members. Generally speaking, it is useful to think of static members as belonging to class types and instance members as belonging to objects (instances of class types).

When a field, method, property, event, operator, or constructor declaration includes a static modifier, it declares a static member. In addition, a constant or type declaration implicitly declares a static member. Static members have the following characteristics:

•  When a static member M is referenced in a member-access7.5.4) of the form E.M, E must denote a type containing M. It is a compile-time error for E to denote an instance.

•  A static field identifies exactly one storage location to be shared by all instances of a given closed class type. No matter how many instances of a given closed class type are created, there is only ever one copy of a static field.

•  A static function member (method, property, event, operator, or constructor) does not operate on a specific instance, and it is a compile-time error to refer to this in such a function member.

When a field, method, property, event, indexer, constructor, or destructor declaration does not include a static modifier, it declares an instance member. (An instance member is sometimes called a nonstatic member.) Instance members have the following characteristics:

•  When an instance member M is referenced in a member-access7.5.4) of the form E.M, E must denote an instance of a type containing M. It is a compile-time error for E to denote a type.

•  Every instance of a class contains a separate set of all instance fields of the class.

•  An instance function member (method, property, indexer, instance constructor, or destructor) operates on a given instance of the class, and this instance can be accessed as this7.5.7).

The following example illustrates the rules for accessing static and instance members:

        class Test
        {
                int x;
                static int y;
                void F() {
                        x = 1;                        // Okay, same as this.x = 1
                        y = 1;                        // Okay, same as Test.y = 1
                }
                static void G() {
                        x = 1;                         // Error, cannot access this.x
                        y = 1;                         // Okay, same as Test.y = 1
                }
                static void Main() {
                        Test t = new Test();
                        t.x = 1;         // Okay
                        t.y = 1;         // Error, cannot access static member through instance
                        Test.x = 1; // Error, cannot access instance member through type
                        Test.y = 1; // Okay
                }
        }

The F method shows that in an instance function member, a simple-name7.5.2) can be used to access both instance members and static members. The G method shows that in a static function member, it is a compile-time error to access an instance member through a simple-name. The Main method shows that in a member-access7.5.4), instance members must be accessed through instances, and static members must be accessed through types.

10.3.8 Nested Types

A type declared within a class or struct declaration is called a nested type. A type that is declared within a compilation unit or namespace is called a non-nested type.

In the example

        using System;
        class A
        {
                class B
                {
                        static void F() {
                                Console.WriteLine("A.B.F");
                        }
                }
        }

class B is a nested type because it is declared within class A, and class A is a non-nested type because it is declared within a compilation unit.

10.3.8.1 Fully Qualified Name

The fully qualified name (§3.8.1) for a nested type is S.N, where S is the fully qualified name of the type in which type N is declared.

10.3.8.2 Declared Accessibility

Non-nested types can have public or internal declared accessibility; they have internal declared accessibility by default. Nested types can have these forms of declared accessibility as well, plus one or more additional forms of declared accessibility, depending on whether the containing type is a class or struct:

•  A nested type that is declared in a class can have any of five forms of declared accessibility (public, protected internal, protected, internal, or private) and, like other class members, defaults to private declared accessibility.

•  A nested type that is declared in a struct can have any of three forms of declared accessibility (public, internal, or private) and, like other struct members, defaults to private declared accessibility.

The example

        public class List
        {
                // Private data structure
                private class Node
                {
                        public object Data;
                        public Node Next;
                        public Node(object data, Node next) {
                                this.Data = data;
                        this.Next = next;
                        }
                }
                private Node first = null;
                private Node last = null;
                // Public interface
                public void AddToFront(object o) {…}
                public void AddToBack(object o) {…}
                public object RemoveFromFront() {…}
                public object RemoveFromBack() {…}
                public int Count { get {…} }
        }

declares a private nested class Node.

10.3.8.3 Hiding

A nested type may hide (§3.7.1) a base member. The new modifier is permitted on nested type declarations so that hiding can be expressed explicitly. The example

        using System;
        class Base
        {
                public static void M() {
                Console.WriteLine("Base.M");
                }
        }
        class Derived: Base
        {
                new public class M
                {
                        public static void F() {
                                Console.WriteLine("Derived.M.F");
                        }
                }
        }
        class Test
        {
                static void Main() {
                Derived.M.F();
                }
        }

shows a nested class M that hides the method M defined in Base.

10.3.8.4 this Access

A nested type and its containing type do not have a special relationship with regard to this-access7.5.7). Specifically, this within a nested type cannot be used to refer to instance members of the containing type. In cases where a nested type needs access to the instance members of its containing type, access can be provided by providing the this for the instance of the containing type as a constructor argument for the nested type. The following example shows this technique.

        using System;
        class C
        {
                int i = 123;
                public void F() {
                        Nested n = new Nested(this);
                        n.G();
                }
                public class Nested
                {
                        C this_c;
                        public Nested(C c) {
                                        this_c = c;
                        }
                        public void G() {
                                Console.WriteLine(this_c.i);
                        }
                }
        }
        class Test
        {
                static void Main() {
                        C c = new C();
                        c.F();
                }
        }

In this example, an instance of C creates an instance of Nested and passes its own this to Nested’s constructor to provide subsequent access to C’s instance members.

10.3.8.5 Access to Private and Protected Members of the Containing Type

A nested type has access to all of the members that are accessible to its containing type, including members of the containing type that have private and protected declared accessibility. The example

        using System;
        class C
        {
                private static void F() {
                        Console.WriteLine("C.F");
                }
                public class Nested
                {
                        public static void G() {
                                    F();
                        }
                }
        }

        class Test {
                static void Main() {
                 C.Nested.G();
                }
        }

shows a class C that contains a nested class Nested. Within Nested, the method G calls the static method F defined in C, and F has private declared accessibility.

A nested type also may access protected members defined in a base type of its containing type. In the example

        using System;
        class Base
        {
                protected void F() {
                        Console.WriteLine("Base.F");
                }
        }
        class Derived: Base
        {
                public class Nested
                {
                        public void G(){
                                Derived d = new Derived();
                                 d.F();                 // ok
                        }
                }
        }

        class Test
        {
                static void Main() {
                        Derived.Nested n = new Derived.Nested();
                        n.G();
                }
        }

the nested class Derived.Nested accesses the protected method F defined in Derived’s base class, Base, by calling through an instance of Derived.

10.3.8.6 Nested Types in Generic Classes

A generic class declaration can contain nested type declarations. The type parameters of the enclosing class can be used within the nested types. A nested type declaration can contain additional type parameters that apply only to the nested type.

Every type declaration contained within a generic class declaration is implicitly a generic type declaration. When writing a reference to a type nested within a generic type, the containing constructed type, including its type arguments, must be named. However, from within the outer class, the nested type can be used without qualification; the instance type of the outer class can be used implicitly when constructing the nested type.

The following example shows four different ways—the first three of them correct—to refer to a constructed type created from Inner; the first two are equivalent.

        class Outer<T>
        {
                class Inner<U>
                {
                        public static void F(T t, U u) {…}
                }
                static void F(T t) {
                        Outer<T>.Inner<string>.F(t, "abc");        // These two statements have
                        Inner<string>.F(t, "abc");                           // the same effect
                        Outer<int>.Inner<string>.F(3, "abc");    // This type is different
                        Outer.Inner<string>.F(t, "abc");               // Error, Outer needs type arg
                }
        }

Although it is bad programming style, a type parameter in a nested type can hide a member or type parameter declared in the outer type:

        class Outer<T>
        {
                class Inner<T>                // Valid, hides Outer's T
                {
                        public T t;                // Refers to Inner's T
                }
        }

10.3.9 Reserved Member Names

To facilitate the underlying C# runtime implementation, for each source member declaration that is a property, event, or indexer, the implementation must reserve two method signatures based on the kind of the member declaration, its name, and its type. It is a compile-time error for a program to declare a member whose signature matches one of these reserved signatures, even if the underlying runtime implementation does not make use of these reservations.

The reserved names do not introduce declarations, so they do not participate in member lookup. However, a declaration’s associated reserved method signatures do participate in inheritance (§10.3.3), and can be hidden with the new modifier (§10.3.4).

The reservation of these names serves three purposes:

•  To allow the underlying implementation to use an ordinary identifier as a method name for get or set access to the C# language feature

•  To allow other languages to interoperate using an ordinary identifier as a method name for get or set access to the C# language feature

•  To help ensure that the source accepted by one conforming compiler is accepted by another, by making the specifics of reserved member names consistent across all C# implementations

The declaration of a destructor (§10.13) also causes a signature to be reserved (§10.3.9.4).

10.3.9.1 Member Names Reserved for Properties

For a property P10.7) of type T, the following signatures are reserved:

        T get_P(); void
        set_P(T value);

Both signatures are reserved, even if the property is read-only or write-only.

In the example

        using System;
        class A
        {
                public int P {
                        get { return 123; }
                }
        }
        class B: A
        {
                new public int get_P() {
                        return 456;
                }
                new public void set_P(int value) {
                }
        }
        class Test
        {
                static void Main() {
                        B b = new B();
                        A a = b;
                        Console.WriteLine(a.P);
                        Console.WriteLine(b.P);
                         Console.WriteLine(b.get_P());
                }
        }

the class A defines a read-only property P, thus reserving signatures for get_P and set_P methods. The class B derives from A and hides both of these reserved signatures. This example produces the following output:

        123
        123
        456

10.3.9.2 Member Names Reserved for Events

For an event E10.8) of delegate type T, the following signatures are reserved:

        void add_E(T handler);
        void remove_E(T handler);

10.3.9.3 Member Names Reserved for Indexers

For an indexer (§10.9) of type T with a parameter list L, the following signatures are reserved:

        T get_Item(L);
        void set_Item(L, T value);

Both signatures are reserved, even if the indexer is read-only or write-only. Furthermore, the member name Item is reserved.

10.3.9.4 Member Names Reserved for Destructors

For a class containing a destructor (§10.13), the following signature is reserved:

        void Finalize();

10.4 Constants

A constant is a class member that represents a constant value: a value that can be computed at compile time. A constant-declaration introduces one or more constants of a given type.

        constant-declaration:
                attributesopt constant-modifiersopt const type constant-declarators ;

        constant-modifiers:
                constant-modifier
                constant-modifiers constant-modifier

        constant-modifier:
                new
                public
                protected internal
                private
        constant-declarators:
                 constant-declarator
                constant-declarators , constant-declarator

        constant-declarator:
                identifier = constant-expression

A constant-declaration may include a set of attributes (§17), a new modifier (§10.3.4), and a valid combination of the four access modifiers (§10.3.5). The attributes and modifiers apply to all of the members declared by the constant-declaration. Even though constants are considered static members, a constant-declaration neither requires nor allows a static modifier. It is an error for the same modifier to appear multiple times in a constant declaration.

The type of a constant-declaration specifies the type of the members introduced by the declaration. The type is followed by a list of constant-declarators, each of which introduces a new member. A constant-declarator consists of an identifier that names the member, followed by an “=” token, followed by a constant-expression7.18) that gives the value of the member.

The type specified in a constant declaration must be sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, string, an enum-type, or a reference-type. Each constant-expression must yield a value of the target type or of a type that can be converted to the target type by an implicit conversion (§6.1).

The type of a constant must be at least as accessible as the constant itself (§3.5.4).

The value of a constant is obtained in an expression using a simple-name7.5.2) or a member-access7.5.4).

A constant can itself participate in a constant-expression. Thus a constant may be used in any construct that requires a constant-expression. Examples of such constructs include case labels, goto case statements, enum member declarations, attributes, and other constant declarations.

As described in §7.18, a constant-expression is an expression that can be fully evaluated at compile time. Given that the only way to create a non-null value of a reference-type other than string is to apply the new operator, and because the new operator is not permitted in a constant-expression, the only possible value for constants of reference-types other than string is null.

When a symbolic name for a constant value is desired, but when the type of that value is not permitted in a constant declaration, or when the value cannot be computed at compile time by a constant-expression, a readonly field (§10.5.2) may be used instead.

A constant declaration that declares multiple constants is equivalent to multiple declarations of single constants with the same attributes, modifiers, and type. For example,

        class A
        {
                public const double X = 1.0, Y = 2.0, Z = 3.0;
        }

is equivalent to

        class A
        {
                public const double X = 1.0;
                public const double Y = 2.0;
                public const double Z = 3.0;
        }

Constants are permitted to depend on other constants within the same program as long as the dependencies are not of a circular nature. The compiler automatically arranges to evaluate the constant declarations in the appropriate order. In the example

        class A
        {
                public const int X = B.Z + 1;
                public const int Y = 10;
        }
        class B
        {
                public const int Z = A.Y + 1;
        }

the compiler first evaluates A.Y, then evaluates B.Z, and finally evaluates A.X, producing the values 10, 11, and 12. Constant declarations may depend on constants from other programs, but such dependencies are only possible in one direction. Referring to the preceding example, if A and B were declared in separate programs, it would be possible for A.X to depend on B.Z, but B.Z could then not simultaneously depend on A.Y.

10.5 Fields

A field is a member that represents a variable associated with an object or class. A field-declaration introduces one or more fields of a given type.

        field-declaration:
                attributesopt field-modifiersopt type variable-declarators ;

        field-modifiers:
                field-modifier
                field-modifiers field-modifier

        field-modifier:
                new
                public
                protected
                internal
                private
                static
                readonly
                volatile
        variable-declarators:
                variable-declarator
                variable-declarators , variable-declarator

        variable-declarator:
                identifier
                identifier = variable-initializer

        variable-initializer:
                expression
                array-initializer

A field-declaration may include a set of attributes (§17), a new modifier (§10.3.4), a valid combination of the four access modifiers (§10.3.5), and a static modifier (§10.5.1). In addition, a field-declaration may include a readonly modifier (§10.5.2) or a volatile modifier (§10.5.3) but not both. The attributes and modifiers apply to all of the members declared by the field-declaration. It is an error for the same modifier to appear multiple times in a field declaration.

The type of a field-declaration specifies the type of the members introduced by the declaration. The type is followed by a list of variable-declarators, each of which introduces a new member. A variable-declarator consists of an identifier that names that member, optionally followed by an “=” token and a variable-initializer10.5.5) that gives the initial value of that member.

The type of a field must be at least as accessible as the field itself (§3.5.4).

The value of a field is obtained in an expression using a simple-name7.5.2) or a member-access7.5.4). The value of a non-read-only field is modified using an assignment7.16). The value of a non-read-only field can be both obtained and modified using postfix increment and decrement operators (§7.5.9) and prefix increment and decrement operators (§7.6.5).

A field declaration that declares multiple fields is equivalent to multiple declarations of single fields with the same attributes, modifiers, and type. For example,

        class A
        {
                public static int X = 1, Y, Z = 100;
        }

is equivalent to

        class A
        {
                public static int X = 1;
                public static int Y;
                public static int Z = 100;
        }

10.5.1 Static and Instance Fields

When a field declaration includes a static modifier, the fields introduced by the declaration are static fields. When no static modifier is present, the fields introduced by the declaration are instance fields. Static fields and instance fields are two of the several kinds of variables (§5) supported by C#. Sometimes they are referred to as static variables and instance variables, respectively.

A static field is not part of a specific instance; instead, it is shared among all instances of a closed type (§4.4.2). No matter how many instances of a closed class type are created, there is only ever one copy of a static field for the associated application domain.

For example,

        class C<V>
        {
                static int count = 0;
                public C() {
                        count++;
                }
                public static int Count {
                        get { return count; }
                }
        }
        class Application
        {
                static void Main() {
                        C<int> x1 = new C<int>();
                        Console.WriteLine(C<int>.Count);                // Prints 1
                        C<double> x2 = new C<double>();
                        Console.WriteLine(C<int>.Count);                 // Prints 1
                        C<int> x3 = new C<int>();
                        Console.WriteLine(C<int>.Count);                 // Prints 2
                }
        }

An instance field belongs to an instance. Specifically, every instance of a class contains a separate set of all the instance fields of that class.

When a field is referenced in a member-access7.5.4) of the form E.M, if M is a static field, E must denote a type containing M; if M is an instance field, E must denote an instance of a type containing M.

The differences between static and instance members are discussed further in §10.3.7.

10.5.2 Read-Only Fields

When a field-declaration includes a readonly modifier, the fields introduced by the declaration are termed read-only fields. Direct assignments to read-only fields can occur only as part of that declaration or in an instance constructor or static constructor in the same class. (A read-only field can be assigned to multiple times in these contexts.) Specifically, direct assignments to a read-only field are permitted in the following contexts:

•  In the variable-declarator that introduces the field (by including a variable-initializer in the declaration).

•  For an instance field, in the instance constructors of the class that contains the field declaration; for a static field, in the static constructor of the class that contains the field declaration. These are also the only contexts in which it is valid to pass a read-only field as an out or ref parameter.

Attempting to assign to a read-only field or pass it as an out or ref parameter in any other context is a compile-time error.

10.5.2.1 Using Static Read-Only Fields for Constants

A field declared as static readonly is useful when a symbolic name for a constant value is desired, but when the type of the value is not permitted in a const declaration, or when the value cannot be computed at compile time. In the example

        public class Color
        {
                public static readonly Color Black = new Color(0, 0, 0);
                public static readonly Color White = new Color(255, 255, 255);
                public static readonly Color Red = new Color(255, 0, 0);
                public static readonly Color Green = new Color(0, 255, 0);
                public static readonly Color Blue = new Color(0, 0, 255);
                private byte red, green, blue;
                public Color(byte r, byte g, byte b) {
                        red = r;
                        green = g;
                        blue = b;
                }
        }

the Black, White, Red, Green, and Blue members cannot be declared as const members because their values cannot be computed at compile time. However, declaring them as static readonly instead has much the same effect.

10.5.2.2 Versioning of Constants and Static Read-Only Fields

Constants and read-only fields have different binary versioning semantics. When an expression references a constant, the value of the constant is obtained at compile time. By contrast, when an expression references a read-only field, the value of the field is not obtained until runtime.

Consider an application that consists of two separate programs:

        using System;
        namespace Program1
        {
                public class Utils
                {
                        public static readonly int X = 1;
                }
        }
        namespace Program2
        {
                class Test
                {
                        static void Main() {
                                Console.WriteLine(Program1.Utils.X);
                        }
                }
        }

The Program1 and Program2 namespaces denote two programs that are compiled separately. Because Program1.Utils.X is declared as static readonly, the value output by the Console.WriteLine statement is not known at compile time, but rather is obtained at runtime. Thus, if the value of X is changed and Program1 is recompiled, the Console.WriteLine statement will output the new value even if Program2 isn’t recompiled. However, had X been a constant, the value of X would have been obtained at the time Program2 was compiled, and it would remain unaffected by changes in Program1 until Program2 was recompiled.

10.5.3 Volatile Fields

When a field-declaration includes a volatile modifier, the fields introduced by that declaration are termed volatile fields.

For nonvolatile fields, optimization techniques that reorder instructions can lead to unexpected and unpredictable results in multithreaded programs that access fields without synchronization such as that provided by the lock-statement8.12). These optimizations can be performed by the compiler, by the runtime system, or by hardware. For volatile fields, such reordering optimizations are restricted:

•  A read of a volatile field is called a volatile read. A volatile read has “acquire semantics”; that is, it is guaranteed to occur prior to any references to memory that occur after it in the instruction sequence.

•  A write of a volatile field is called a volatile write. A volatile write has “release semantics”; that is, it is guaranteed to happen after any memory references prior to the write instruction in the instruction sequence.

These restrictions ensure that all threads will observe volatile writes performed by any other thread in the order in which they were performed. A conforming implementation is not required to provide a single total ordering of volatile writes as seen from all threads of execution. The type of a volatile field must be one of the following:

•  A reference-type

•  The type byte, sbyte, short, ushort, int, uint, char, float, bool, System.IntPtr, or System.UIntPtr

•  An enum-type having an enum base type of byte, sbyte, short, ushort, int, or uint

The example

        using System;
        using System.Threading;
        class Test
        {
                public static int result;
                public static volatile bool finished;
                static void Thread2() {
                        result = 143;
                        finished = true;
                }
                static void Main() {
                        finished = false;
                        // Run Thread2() in a new thread
                         new Thread(new ThreadStart(Thread2)).Start();
                        // Wait for Thread2 to signal that it has a result by setting
                        // finished to true
                        for (;;) {
                                 if (finished) {
                                        Console.WriteLine("result = {0}", result);
                                        return;
                                }
                        }
                }
        }

produces the following output:

        result = 143

In this example, the method Main starts a new thread that runs the method Thread2. This method stores a value into a nonvolatile field called result, and then stores true in the volatile field finished. The main thread waits for the field finished to be set to true; it then reads the field result. Because finished has been declared volatile, the main thread must read the value 143 from the field result. If the field finished had not been declared as volatile, then it would be permissible for the store to result to be visible to the main thread after the store to finished, and hence for the main thread to read the value 0 from the field result. Declaring finished as a volatile field prevents any such inconsistency.

10.5.4 Field Initialization

The initial value of a field—whether it be a static field or an instance field—is the default value (§5.2) of the field’s type. It is not possible to observe the value of a field before this default initialization has occurred; thus a field is never “uninitialized.” The example

        using System;
        class Test
        {
                static bool b;
                int i;
                static void Main() {
                        Test t = new Test();
                        Console.WriteLine("b = {0}, i = {1}", b, t.i);
                }
        }

produces the output

        b = False, i = 0

because b and i are both automatically initialized to default values.

10.5.5 Variable Initializers

Field declarations may include variable-initializers. For static fields, variable initializers correspond to assignment statements that are executed during class initialization. For instance fields, variable initializers correspond to assignment statements that are executed when an instance of the class is created.

The example

        using System;
        class Test
        {
                static double x = Math.Sqrt(2.0);
                int i = 100;
                string s = "Hello";
                static void Main() {
                        Test a = new Test();
                        Console.WriteLine("x = {0}, i = {1}, s = {2}", x, a.i, a.s);
                }
        }

produces the output

        x = 1.4142135623731, i = 100, s = Hello

because an assignment to x occurs when static field initializers execute and assignments to i and s occur when the instance field initializers execute.

The default value initialization described in §10.5.4 occurs for all fields, including fields that have variable initializers. Thus, when a class is initialized, all static fields in that class are first initialized to their default values, and then the static field initializers are executed in textual order. Likewise, when an instance of a class is created, all instance fields in that instance are first initialized to their default values, and then the instance field initializers are executed in textual order.

It is possible for static fields with variable initializers to be observed in their default value state. However, this practice is strongly discouraged as a matter of style. The example

        using System;
        class Test
        {
                static int a = b + 1;
                static int b = a + 1;
                static void Main() {
                        Console.WriteLine("a = {0}, b = {1}", a, b);
                }
        }

exhibits this behavior. Despite the circular definitions of a and b, the program is valid. It results in the output

        a = 1, b = 2

because the static fields a and b are initialized to 0 (the default value for int) before their initializers are executed. When the initializer for a runs, the value of b is zero, so a is initialized to 1. When the initializer for b runs, the value of a is already 1, so b is initialized to 2.

10.5.5.1 Static Field Initialization

The static field variable initializers of a class correspond to a sequence of assignments that are executed in the textual order in which they appear in the class declaration. If a static constructor (§10.12) exists in the class, execution of the static field initializers occurs immediately prior to executing that static constructor. Otherwise, the static field initializers are executed at an implementation-dependent time prior to the first use of a static field of that class. The example

        using System;
        class Test
        {
                static void Main() {
                        Console.WriteLine("{0} {1}", B.Y, A.X);
                }
                public static int F(string s) {
                        Console.WriteLine(s);
                        return 1;
                }
        }
        class A
        {
                public static int X = Test.F("Init A");
        }
        class B
        {
                public static int Y = Test.F("Init B");
        }

might produce either this output

        Init A
        Init B
        1 1

or this output

        Init B
        Init A
        1 1

because the execution of X’s initializer and Y’s initializer could occur in either order; they are only constrained to occur before the references to those fields. However, in the example,

        using System;
        class Test
        {
                static void Main() {
                        Console.WriteLine("{0} {1}", B.Y, A.X);
                }
                public static int F(string s) {
                        Console.WriteLine(s);
                        return 1;
                }
        }
        class A
        {
                static A() {}
                public static int X = Test.F("Init A");
        }
        class B
        {
                static B() {}
                public static int Y = Test.F("Init B");
        }

the output must be

        Init B
        Init A
        1 1

because the rules for when static constructors execute (as defined in §10.12) provide that B’s static constructor (and hence B’s static field initializers) must run before A’s static constructor and field initializers.

10.5.5.2 Instance Field Initialization

The instance field variable initializers of a class correspond to a sequence of assignments that are executed immediately upon entry to any one of the instance constructors (§10.11.1) of that class. The variable initializers are executed in the textual order in which they appear in the class declaration. The class instance creation and initialization process is described further in §10.11.

A variable initializer for an instance field cannot reference the instance being created. Thus it is a compile-time error to reference this in a variable initializer, because it is a compile-time error for a variable initializer to reference any instance member through a simple-name. In the example

        class A
        {
                int x = 1;
                int y = x + 1;                // Error, reference to instance member of this
        }

the variable initializer for y results in a compile-time error because it references a member of the instance being created.

10.6 Methods

A method is a member that implements a computation or action that can be performed by an object or class. Methods are declared using method-declarations:

        method-declaration:
                method-header method-body

        method-header:
                attributesopt method-modifiersopt partialopt return-type member-name
                        type-parameter-listopt ( formal-parameter-listopt )
                        type-parameter-constraints-clausesopt

        method-modifiers:
                method-modifier
                method-modifiers method-modifier
        method-modifier:
                new
                public
        protected
        internal
        private
        static
        virtual
        sealed
        override
        abstract
        extern

        return-type:
                type
                void

        member-name:
                identifier interface-type . identifier

        method-body:
                block
                ;

A method-declaration may include a set of attributes (§17); a valid combination of the four access modifiers (§10.3.5); and the new10.3.4), static10.6.2), virtual10.6.3), override10.6.4), sealed10.6.5), abstract10.6.6), and extern10.6.7) modifiers.

A declaration has a valid combination of modifiers if all of the following are true:

•  The declaration includes a valid combination of access modifiers (§10.3.5).

•  The declaration does not include the same modifier multiple times.

•  The declaration includes at most one of the following modifiers: static, virtual, and override.

•  The declaration includes at most one of the following modifiers: new and override.

•  If the declaration includes the abstract modifier, then the declaration does not include any of the following modifiers: static, virtual, sealed, or extern.

•  If the declaration includes the private modifier, then the declaration does not include any of the following modifiers: virtual, override, or abstract.

•  If the declaration includes the sealed modifier, then the declaration also includes the override modifier.

•  If the declaration includes the partial modifier, then it does not include any of the following modifiers: new, public, protected, internal, private, virtual, sealed, override, abstract, or extern.

The return-type of a method declaration specifies the type of the value computed and returned by the method. The return-type is void if the method does not return a value. If the declaration includes the partial modifier, then the return type must be void.

The member-name specifies the name of the method. Unless the method is an explicit interface member implementation (§13.4.1), the member-name is simply an identifier. For an explicit interface member implementation, the member-name consists of an interface-type followed by a “.” and an identifier.

The optional type-parameter-list specifies the type parameters of the method (§10.1.3). If a type-parameter-list is specified, the method is a generic method. If the method has an extern modifier, a type-parameter-list cannot be specified.

The optional formal-parameter-list specifies the parameters of the method (§10.6.1).

The optional type-parameter-constraints-clauses specify constraints on individual type parameters (§10.1.5) and may be specified only if a type-parameter-list is also supplied and the method does not have an override modifier.

The return-type and each of the types referenced in the formal-parameter-list of a method must be at least as accessible as the method itself (§3.5.4).

For abstract and extern methods, the method-body consists simply of a semicolon. For partial methods, the method-body may consist of either a semicolon or a block. For all other methods, the method-body consists of a block, which specifies the statements to execute when the method is invoked.

The name, the type parameter list, and the formal parameter list of a method define the signature (§3.6) of the method. Specifically, the signature of a method consists of its name, the number of type parameters, and the number, modifiers, and types of its formal parameters. For these purposes, any type parameter of the method that occurs in the type of a formal parameter is identified not by its name, but by its ordinal position in the type argument list of the method. The return type is not part of a method’s signature, nor are the names of the type parameters or the formal parameters.

The name of a method must differ from the names of all other nonmethods declared in the same class. In addition, the signature of a method must differ from the signatures of all other methods declared in the same class, and two methods declared in the same class may not have signatures that differ solely by ref and out.

The method’s type-parameters are in scope throughout the method-declaration. They can be used to form types throughout that scope in return-type, method-body, and type-parameter-constraints-clauses, but not in attributes.

All formal parameters and type parameters must have different names.

10.6.1 Method Parameters

The parameters of a method, if any, are declared by the method’s formal-parameter-list.

        formal-parameter-list:
                fixed-parameters
                fixed-parameters , parameter-array
                parameter-array

        fixed-parameters:
                fixed-parameter
                fixed-parameters , fixed-parameter

        fixed-parameter:
                attributesopt parameter-modifieropt type identifier

        parameter-modifier:
                ref
                out
                this

        parameter-array:
                attributesopt params array-type identifier

The formal parameter list consists of one or more comma-separated parameters, of which only the last may be a parameter-array.

A fixed-parameter consists of an optional set of attributes (§17); an optional ref, out, or this modifier; a type; and an identifier. Each fixed-parameter declares a parameter of the given type with the given name. The this modifier designates the method as an extension method and is allowed on only the first parameter of a static method. Extension methods are further described in §10.6.9.

A parameter-array consists of an optional set of attributes (§17), a params modifier, an array-type, and an identifier. A parameter array declares a single parameter of the given array type with the given name. The array-type of a parameter array must be a single-dimensional array type (§12.1). In a method invocation, a parameter array permits a single argument of the given array type to be specified or, alternatively, it permits zero or more arguments of the array element type to be specified. Parameter arrays are described further in §10.6.1.4.

A method declaration creates a separate declaration space for parameters, type parameters, and local variables. Names are introduced into this declaration space by the type parameter list and the formal parameter list of the method and by local variable declarations in the block of the method. It is an error for two members of a method declaration space to have the same name. It is an error for the method declaration space and the local variable declaration space of a nested declaration space to contain elements with the same name.

A method invocation (§7.5.5.1) creates a copy, specific to that invocation, of the formal parameters and local variables of the method, and the argument list of the invocation assigns values or variable references to the newly created formal parameters. Within the block of a method, formal parameters can be referenced by their identifiers in simple-name expressions (§7.5.2).

Four kinds of formal parameters are possible:

•  Value parameters, which are declared without any modifiers

•  Reference parameters, which are declared with the ref modifier

•  Output parameters, which are declared with the out modifier

•  Parameter arrays, which are declared with the params modifier

As described in §3.6, the ref and out modifiers are part of a method’s signature, but the params modifier is not.

10.6.1.1 Value Parameters

A parameter declared with no modifiers is a value parameter. A value parameter corresponds to a local variable that gets its initial value from the corresponding argument supplied in the method invocation.

When a formal parameter is a value parameter, the corresponding argument in a method invocation must be an expression of a type that is implicitly convertible (§6.1) to the formal parameter type.

A method is permitted to assign new values to a value parameter. Such assignments affect only the local storage location represented by the value parameter—they have no effect on the actual argument given in the method invocation.

10.6.1.2 Reference Parameters

A parameter declared with a ref modifier is a reference parameter. Unlike a value parameter, a reference parameter does not create a new storage location. Instead, it represents the same storage location as the variable given as the argument in the method invocation.

When a formal parameter is a reference parameter, the corresponding argument in a method invocation must consist of the keyword ref followed by a variable-reference5.3.3) of the same type as the formal parameter. A variable must be definitely assigned before it can be passed as a reference parameter.

Within a method, a reference parameter is always considered definitely assigned.

A method declared as an iterator (§10.14) cannot have reference parameters.

The example

        using System;
        class Test
        {
                static void Swap(ref int x, ref int y) {
                        int temp = x;
                        x = y;
                        y = temp;
                }
                static void Main() {
                        int i = 1, j = 2;
                        Swap(ref i, ref j);
                        Console.WriteLine("i = {0}, j = {1}", i, j);
                }
        }

produces the following output:

        i = 2, j = 1

For the invocation of Swap in Main, x represents i and y represents j. Thus the invocation has the effect of swapping the values of i and j.

In a method that takes reference parameters, it is possible for multiple names to represent the same storage location. In the example

        class A
        {
                string s;
                void F(ref string a, ref string b) {
                        s = "One";
                        a = "Two";
                        b = "Three";
                }
                void G() {
                        F(ref s, ref s);
                }
        }

the invocation of F in G passes a reference to s for both a and b. Thus, for that invocation, the names s, a, and b all refer to the same storage location, and the three assignments all modify the instance field s.

10.6.1.3 Output Parameters

A parameter declared with an out modifier is an output parameter. Similar to a reference parameter, an output parameter does not create a new storage location. Instead, an output parameter represents the same storage location as the variable given as the argument in the method invocation.

When a formal parameter is an output parameter, the corresponding argument in a method invocation must consist of the keyword out followed by a variable-reference5.3.3) of the same type as the formal parameter. A variable need not be definitely assigned before it can be passed as an output parameter. Following an invocation where a variable was passed as an output parameter, however, the variable is considered definitely assigned.

Within a method, just like a local variable, an output parameter is initially considered unassigned and must be definitely assigned before its value is used.

Every output parameter of a method must be definitely assigned before the method returns.

A method declared as a partial method (§10.2.7) or an iterator (§10.14) cannot have output parameters.

Output parameters are typically used in methods that produce multiple return values.

For example,

        using System;
        class Test
        {
                static void SplitPath(string path, out string dir, out string name) {
                        int i = path.Length;
                        while (i > 0) {
                                char ch = path[i - 1];
                                if (ch == '' || ch == '/' || ch == ':') break;
                                i--;
                        }
                        dir = path.Substring(0, i);
                        name = path.Substring(i);
                }
                static void Main() {
                        string dir, name;
                        SplitPath("c:\Windows\System\hello.txt", out dir, out name);
                        Console.WriteLine(dir);
                        Console.WriteLine(name);
                }
        }

produces the following output:

        c:WindowsSystem
        hello.txt

Note that the dir and name variables can be unassigned before they are passed to SplitPath, and that they are considered definitely assigned following the call.

10.6.1.4 Parameter Arrays

A parameter declared with a params modifier is a parameter array. If a formal parameter list includes a parameter array, it must appear as the last parameter in the list and must be of a single-dimensional array type. For example, the types string[] and string[][] can be used as the type of a parameter array, but the type string[,] cannot. It is not possible to combine the params modifier with the modifiers ref and out.

A parameter array permits arguments to be specified in one of two ways in a method invocation:

•  The argument given for a parameter array can 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.

•  Alternatively, the invocation can 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.

Except for allowing a variable number of arguments in an invocation, a parameter array is precisely equivalent to a value parameter (§10.6.1.1) of the same type.

The example

        using System;
        class Test
        {
                static void F(params int[] args) {
                        Console.Write("Array contains {0} elements:", args.Length);
                        foreach (int i in args)
                                Console.Write(" {0}", i);
                        Console.WriteLine();
                }
                static void Main() {
                        int[] arr = {1, 2, 3};
                        F(arr);
                        F(10, 20, 30, 40);
                        F();
                }
        }

produces the following output:

        Array contains 3 elements: 1 2 3
        Array contains 4 elements: 10 20 30 40
        Array contains 0 elements:

The first invocation of F simply passes the array a as a value parameter. The second invocation of F automatically creates a four-element int[] with the given element values and passes that array instance as a value parameter. Likewise, the third invocation of F creates a zero-element int[] and passes that instance as a value parameter. The second and third invocations are precisely equivalent to writing the following statements:

        F(new int[] {10, 20, 30, 40});
        F(new int[] {});

When performing overload resolution, a method with a parameter array may be applicable either in its normal form or in its expanded form (§7.4.3.1). The expanded form of a method is available only if the normal form of the method is not applicable and only if a method with the same signature as the expanded form is not already declared in the same type.

The example

        using System;
        class Test
        {
                static void F(params object[] a) {
                        Console.WriteLine("F(object[])");
                }
                static void F() {
                        Console.WriteLine("F()");
                }
                static void F(object a0, object a1) {
                        Console.WriteLine("F(object,object)");
                }
                static void Main() {
                        F();
                        F(1);
                        F(1, 2);
                        F(1, 2, 3);
                        F(1, 2, 3, 4);
                }
        }

produces the following output:

        F();
        F(object[]);
        F(object,object);
        F(object[]);
        F(object[]);

In the example, two of the possible expanded forms of the method with a parameter array are already included in the class as regular methods. These expanded forms are, therefore, not considered when performing overload resolution, so the first and third method invocations select the regular methods. When a class declares a method with a parameter array, it is not uncommon to include some of the expanded forms as regular methods as well. By doing so, it becomes possible to avoid the allocation of an array instance that occurs when an expanded form of a method with a parameter array is invoked.

When the type of a parameter array is object[], a potential ambiguity arises between the normal form of the method and the expended form for a single object parameter. The reason for the ambiguity is that an object[] is itself implicitly convertible to type object. The ambiguity presents no problem, however, because it can be resolved by inserting a cast if needed.

The example

        using System;
        class Test
        {
                static void F(params object[] args) {
                        foreach (object o in args) {
                                Console.Write(o.GetType().FullName);
                                Console.Write(" ");
                        }
                        Console.WriteLine();
                }
                static void Main() {
                        object[] a = {1, "Hello", 123.456};
                        object o = a;
                        F(a);
                        F((object)a);
                        F(o);
                        F((object[])o);
                }
        }

produces the following output:

        System.Int32 System.String System.Double
        System.Object[]
        System.Object[]
        System.Int32 System.String System.Double

In this example, in the first and last invocations of F, the normal form of F is applicable because an implicit conversion exists from the argument type to the parameter type (both are of type object[]). Thus overload resolution selects the normal form of F, and the argument is passed as a regular value parameter. In the second and third invocations, the normal form of F is not applicable because no implicit conversion exists from the argument type to the parameter type (type object cannot be implicitly converted to type object[]). However, the expanded form of F is applicable, so it is selected by overload resolution. As a result, a one-element object[] is created by the invocation, and the single element of the array is initialized with the given argument value (which itself is a reference to an object[]).

10.6.2 Static and Instance Methods

When a method declaration includes a static modifier, that method is said to be a static method. When no static modifier is present, the method is said to be an instance method.

A static method does not operate on a specific instance, and it is a compile-time error to refer to this in a static method.

An instance method operates on a given instance of a class, and that instance can be accessed as this7.5.7).

When a method is referenced in a member-access7.5.4) of the form E.M, if M is a static method, E must denote a type containing M; if M is an instance method, E must denote an instance of a type containing M.

The differences between static and instance members are discussed further in §10.3.7.

10.6.3 Virtual Methods

When an instance method declaration includes a virtual modifier, that method is said to be a virtual method. When no virtual modifier is present, the method is said to be a non-virtual method.

The implementation of a nonvirtual method is invariant: The implementation is the same whether the method is invoked on an instance of the class in which it is declared or on an instance of a derived class. In contrast, the implementation of a virtual method can be superseded by derived classes. The process of superseding the implementation of an inherited virtual method is known as overriding that method (§10.6.4).

In a virtual method invocation, the runtime type of the instance for which that invocation takes place determines which method implementation is actually invoked. In a nonvirtual method invocation, the compile-time type of the instance is the determining factor. In precise terms, when a method named N is invoked with an argument list A on an instance with a compile-time type C and a runtime type R (where R is either C or a class derived from C), the invocation is processed as follows:

•  First, overload resolution is applied to C, N, and A, to select a specific method M from the set of methods declared in and inherited by C. This is described in §7.5.5.1.

•  Then, if M is a nonvirtual method, M is invoked.

•  Otherwise, M is a virtual method, and the most derived implementation of M with respect to R is invoked.

For every virtual method declared in or inherited by a class, there exists a most derived implementation of the method with respect to that class. The most derived implementation of a virtual method M with respect to a class R is determined as follows:

•  If R contains the introducing virtual declaration of M, then this is the most derived implementation of M.

•  Otherwise, if R contains an override of M, then this is the most derived implementation of M.

•  Otherwise, the most derived implementation of M with respect to R is the same as the most derived implementation of M with respect to the direct base class of R.

The following example illustrates the differences between virtual and nonvirtual methods:

        using System;
        class A
        {
                public void F() { Console.WriteLine("A.F"); }
                public virtual void G() { Console.WriteLine("A.G"); }
        }
        class B: A
        {
                new public void F() { Console.WriteLine("B.F"); }
                public override void G() { Console.WriteLine("B.G"); }
        }
        class Test
        {
                static void Main() {
                        B b = new B();
                        A a = b;
                        a.F();
                        b.F();
                        a.G();
                        b.G();
                }
        }

In the example, A introduces a nonvirtual method F and a virtual method G. The class B introduces a new nonvirtual method F, thereby hiding the inherited F, and also overrides the inherited method G. The example produces the following output:

        A.F
        B.F
        B.G
        B.G

Notice that the statement a.G() invokes B.G, not A.G. Here the runtime type of the instance (which is B)—not the compile-time type of the instance (which is A)—determines the actual method implementation to invoke.

Because methods are allowed to hide inherited methods, it is possible for a class to contain several virtual methods with the same signature. This does not present an ambiguity problem, because all but the most derived method are hidden. In the example

        using System;
        class A
        {
                public virtual void F() { Console.WriteLine("A.F"); }
        }
        class B: A
        {
                public override void F() { Console.WriteLine("B.F"); }
        }
        class C: B
        {
                new public virtual void F() { Console.WriteLine("C.F"); }
        }
        class D: C
        {
                public override void F() { Console.WriteLine("D.F"); }
        }
        class Test
        {
                static void Main() {
                        D d = new D();
                        A a = d;
                        B b = d;
                        C c = d;
                        a.F();
                        b.F();
                        c.F();
                        d.F();
                }
        }

the C and D classes contain two virtual methods with the same signature: the one introduced by A and the one introduced by C. The method introduced by C hides the method inherited from A. Thus the override declaration in D overrides the method introduced by C, and it is not possible for D to override the method introduced by A. The example produces the following output:

        B.F
        B.F
        D.F
        D.F

Note that it is possible to invoke the hidden virtual method by accessing an instance of D through a less derived type in which the method is not hidden.

10.6.4 Override Methods

When an instance method declaration includes an override modifier, the method is said to be an override method. An override method overrides an inherited virtual method with the same signature. Whereas a virtual method declaration introduces a new method, an override method declaration specializes an existing inherited virtual method by providing a new implementation of that method.

The method overridden by an override declaration is known as the overridden base method. For an override method M declared in a class C, the overridden base method is determined by examining each base class type of C, starting with the direct base class type of C and continuing with each successive direct base class type, until in a given base class type at least one accessible method is located that has the same signature as M after substitution of type arguments. For the purposes of locating the overridden base method, a method is considered accessible if it is public, if it is protected, if it is protected internal, or if it is internal and declared in the same program as C.

A compile-time error occurs unless all of the following are true for an override declaration:

•  An overridden base method can be located as described above.

•  Exactly one such overridden base method exists. This restriction has effect only if the base class type is a constructed type where the substitution of type arguments makes the signature of two methods the same.

•  The overridden base method is a virtual, abstract, or override method. In other words, the overridden base method cannot be static or nonvirtual.

•  The overridden base method is not a sealed method.

•  The override method and the overridden base method have the same return type.

•  The override declaration and the overridden base method have the same declared accessibility. In other words, an override declaration cannot change the accessibility of the virtual method. However, if the overridden base method is protected internal and it is declared in a different assembly than the assembly containing the override method, then the override method’s declared accessibility must be protected.

•  The override declaration does not specify type-parameter-constraints-clauses. Instead, the constraints are inherited from the overridden base method.

The following example demonstrates how the overriding rules work for generic classes:

        abstract class C<T>
        {
                public virtual T F() {…}
                public virtual C<T> G() {…}
                public virtual void H(C<T> x) {…}
        }
        class D: C<string>
        {
                public override string F() {…}                                // Okay
                public override C<string> G() {…}                        // Okay
                public override void H(C<T> x) {…}                        // Error, should be C<string>
        }
        class E<T,U>: C<U>
        {
                public override U F() {…}                                            // Okay
                public override C<U> G() {…}                                    // Okay
                public override void H(C<T> x) {…}                          // Error, should be C<U>}

An override declaration can access the overridden base method using a base-access7.5.8). In the example

        class A
        {
                int x;
                public virtual void PrintFields() {
                        Console.WriteLine("x = {0}", x);
                }
        }
        class B: A
        {
                int y;
                public override void PrintFields() {
                        base.PrintFields();
                        Console.WriteLine("y = {0}", y);
                }
        }

the base.PrintFields() invocation in B invokes the PrintFields method declared in A. A base-access disables the virtual invocation mechanism and simply treats the base method as a nonvirtual method. Had the invocation in B been written ((A)this).PrintFields(), it would recursively invoke the PrintFields method declared in B—not the one declared in A—because PrintFields is virtual and the runtime type of ((A)this) is B.

Only by including an override modifier can a method override another method. In all other cases, a method with the same signature as an inherited method simply hides the inherited method. In the example

        class A
        {
                public virtual void F() {}
        }
        class B: A
        {
                public virtual void F() {}                // Warning, hiding inherited F()
        }

the F method in B does not include an override modifier and, therefore, does not override the F method in A. Rather, the F method in B hides the method in A, and a warning is reported because the declaration does not include a new modifier.

In the example

        class A
        {
                public virtual void F() {}
        }
        class B: A
        {
                new private void F() {}                // Hides A.F within body of B
        }
        class C: B
        {
                public override void F() {}           // Okay, overrides A.F
        }

the F method in B hides the virtual F method inherited from A. Because the new F in B has private access, its scope includes only the class body of B; its scope does not extend to C. As a consequence, the declaration of F in C is permitted to override the F inherited from A.

10.6.5 Sealed Methods

When an instance method declaration includes a sealed modifier, that method is said to be a sealed method. If an instance method declaration includes the sealed modifier, it must also include the override modifier. Use of the sealed modifier prevents a derived class from further overriding the method.

In the example

        using System;
        class A
        {
                public virtual void F() {
                        Console.WriteLine("A.F");
                }
                public virtual void G() {
                        Console.WriteLine("A.G");
                }
        }
        class B: A
        {
                sealed override public void F() {
                        Console.WriteLine("B.F");
                }
                override public void G() {
                        Console.WriteLine("B.G");
                }
        }
        class C: B
        {
                override public void G() {
                        Console.WriteLine("C.G");
                }
        }

the class B provides two override methods: an F method that has the sealed modifier and a G method that does not. B’s use of the sealed modifier prevents C from further overriding F.

10.6.6 Abstract Methods

When an instance method declaration includes an abstract modifier, that method is said to be an abstract method. Although an abstract method is implicitly a virtual method as well, it cannot be specified with the virtual modifier.

An abstract method declaration introduces a new virtual method but does not provide an implementation of that method. Instead, nonabstract derived classes are required to provide their own implementation by overriding that method. Because an abstract method provides no actual implementation, the method-body of an abstract method consists of just a semicolon.

Abstract method declarations are permitted only in abstract classes (§10.1.1.1).

In the example

        public abstract class Shape
        {
                public abstract void Paint(Graphics g, Rectangle r);
        }
        public class Ellipse: Shape
        {
                public override void Paint(Graphics g, Rectangle r)
        {
                        g.DrawEllipse(r);
                }
        }
        public class Box: Shape
        {
                public override void Paint(Graphics g, Rectangle r) {
                        g.DrawRect(r);
                }
        }

the Shape class defines the abstract notion of a geometrical shape object that can paint itself. The Paint method is abstract because no meaningful default implementation exists. The Ellipse and Box classes are concrete Shape implementations. Because these classes are nonabstract, they are required to override the Paint method and provide an actual implementation.

It is a compile-time error for a base-access7.5.8) to reference an abstract method. In the example

        abstract class A
        {
                public abstract void F();
        }
        class B: A
        {
                public override void F() {
                        base.F();                                                                // Error, base.F is abstract
                }
        }

a compile-time error is reported for the base.F() invocation because it references an abstract method.

An abstract method declaration is permitted to override a virtual method. This behavior allows an abstract class to force reimplementation of the method in derived classes, and it makes the original implementation of the method unavailable.

In the example

        using System;
        class A
        {
                public virtual void F() {
                        Console.WriteLine("A.F");
                }
        }
        abstract class B: A
        {
                public abstract override void F();
        }
        class C: B {
                public override void F() {
                        Console.WriteLine("C.F");
                }
        }

class A declares a virtual method, class B overrides this method with an abstract method, and class C overrides the abstract method to provide its own implementation.

10.6.7 External Methods

When a method declaration includes an extern modifier, that method is said to be an external method. External methods are implemented externally, typically using a language other than C#. Because an external method declaration provides no actual implementation, the method-body of an external method simply consists of a semicolon. An external method may not be generic.

The extern modifier is typically used in conjunction with a DllImport attribute (§17.5.1), allowing external methods to be implemented by DLLs (dynamic link libraries). The execution environment may support other mechanisms whereby implementations of external methods can be provided.

When an external method includes a DllImport attribute, the method declaration must also include a static modifier. The following example demonstrates the use of the extern modifier and the DllImport attribute:

        using System.Text;
        using System.Security.Permissions;
        using System.Runtime.InteropServices;
        class Path
        {
                [DllImport("kernel32", SetLastError=true)]
                static extern bool CreateDirectory(string name, SecurityAttribute sa);
                [DllImport("kernel32", SetLastError=true)]
                static extern bool RemoveDirectory(string name);
                [DllImport("kernel32", SetLastError=true)]
                static extern int GetCurrentDirectory(int bufSize, StringBuilder buf);
                [DllImport("kernel32", SetLastError=true)]
                static extern bool SetCurrentDirectory(string name);
        }

10.6.8 Partial Methods

When a method declaration includes a partial modifier, that method is said to be a partial method. Partial methods can only be declared as members of partial types (§10.2), and are subject to a number of restrictions. Partial methods are further described in §10.2.7.

10.6.9 Extension Methods

When the first parameter of a method includes the this modifier, that method is said to be an extension method. Extension methods can only be declared in nongeneric, non-nested static classes. The first parameter of an extension method can have no modifiers other than this, and the parameter type cannot be a pointer type.

In the following example, a static class declares two extension methods:

        public static class Extensions
        {
                public static int ToInt32(this string s) {
                        return Int32.Parse(s);
                }
                public static T[] Slice<T>(this T[] source, int index, int count) {
                        if (index < 0 || count < 0 || source.Length - index < count)
                               throw new ArgumentException();
                        T[] result = new T[count];
                        Array.Copy(source, index, result, 0, count);
                        return result;
                }
        }

An extension method is a regular static method. In addition, where its enclosing static class is in scope, an extension method can be invoked using instance method invocation syntax (§7.5.5.2), using the receiver expression as the first argument.

The following program uses the extension methods declared in the preceding example:

        static class Program
        {
                static void Main() {
                        string[] strings = { "1", "22", "333", "4444" };
                        foreach (string s in strings.Slice(1, 2)) {
                                Console.WriteLine(s.ToInt32());
                        }
                }
        }

The Slice method is available on the string[], and the ToInt32 method is available on string, because they have been declared as extension methods. The meaning of the program is the same as that of the following code, which uses ordinary static method calls:

        static class Program {
                static void Main() {
                        string[] strings = { "1", "22", "333", "4444" };
                        foreach (string s in Extensions.Slice(strings, 1, 2)) {
                                        Console.WriteLine(Extensions.ToInt32(s));                }                }
        }

10.6.10 Method Body

The method-body of a method declaration consists of either a block or a semicolon.

Abstract and external method declarations do not provide a method implementation, so their method bodies simply consist of a semicolon. For any other method, the method body is a block (§8.2) that contains the statements to execute when that method is invoked.

When the return type of a method is void, return statements (§8.9.4) in that method’s body are not permitted to specify an expression. If execution of the method body of a void method completes normally (that is, if control flows off the end of the method body), that method simply returns to its caller.

When the return type of a method is not void, each return statement in that method’s body must specify an expression of a type that is implicitly convertible to the return type. The end point of the method body of a value-returning method must not be reachable. In other words, in a value-returning method, control is not permitted to flow off the end of the method body.

In the example

        class A
        {
                public int F() {}                                // Error, return value required
                public int G() {
                        return 1;
                }
                public int H(bool b) {
                        if (b) {
                                return 1;
                        }
                        else {
                                return 0;
                        }
                }
        }

the value-returning F method results in a compile-time error because control can flow off the end of the method body. The G and H methods are correct because all possible execution paths end in a return statement that specifies a return value.

10.6.11 Method Overloading

The method overload resolution rules are described in §7.4.2.

10.7 Properties

A property is a member that provides access to a characteristic of an object or a class. Examples of properties include the length of a string, the size of a font, the caption of a window, the name of a customer, and so on. Properties are a natural extension of fields—both are named members with associated types, and the syntax for accessing fields and properties is the same. However, unlike fields, properties do not denote storage locations. Instead, properties have accessors that specify the statements to be executed when their values are read or written. In this way, properties provide a mechanism for associating actions with the reading and writing of an object’s attributes; furthermore, they permit such attributes to be computed.

Properties are declared using property-declarations:

        property-declaration:
                attributesopt property-modifiersopt type member-name { accessor-declarations }

        property-modifiers:
                property-modifier
                property-modifiers property-modifier

        property-modifier:
                new
                public
                protected
                internal
                private
                static
                virtual
                sealed
                override
                abstract
                extern
        member-name:
                identifier
                interface-type . identifier

A property-declaration may include a set of attributes (§17); a valid combination of the four access modifiers (§10.3.5); and the new10.3.4), static10.6.2), virtual10.6.3), override10.6.4), sealed10.6.5), abstract10.6.6), and extern10.6.7) modifiers.

Property declarations are subject to the same rules as method declarations (§10.6) with regard to valid combinations of modifiers.

The type of a property declaration specifies the type of the property introduced by the declaration, and the member-name specifies the name of the property. Unless the property is an explicit interface member implementation, the member-name is simply an identifier. For an explicit interface member implementation (§13.4.1), the member-name consists of an interface-type followed by a “.” and an identifier.

The type of a property must be at least as accessible as the property itself (§3.5.4).

The accessor-declarations, which must be enclosed in “{” and “}” tokens, declare the accessors (§10.7.2) of the property. The accessors specify the executable statements associated with reading and writing the property.

Even though the syntax for accessing a property is the same as that for accessing a field, a property is not classified as a variable. Thus it is not possible to pass a property as a ref or out argument.

When a property declaration includes an extern modifier, the property is said to be an external property. Because an external property declaration provides no actual implementation, each of its accessor-declarations consists of a semicolon.

10.7.1 Static and Instance Properties

When a property declaration includes a static modifier, the property is said to be a static property. When no static modifier is present, the property is said to be an instance property.

A static property is not associated with a specific instance, and it is a compile-time error to refer to this in the accessors of a static property.

An instance property is associated with a given instance of a class, and that instance can be accessed as this7.5.7) in the accessors of that property.

When a property is referenced in a member-access7.5.4) of the form E.M, if M is a static property, E must denote a type containing M; if M is an instance property, E must denote an instance of a type containing M.

The differences between static and instance members are discussed further in §10.3.7.

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

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