Previous : Namespaces - Index - Next : Structs

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.5) that declares a new class.

class-declaration:
attributesopt   class-modifiersopt  
class   identifier   class-baseopt   class-body   ;opt

A class-declaration consists of an optional set of attributes17), followed by an optional set of class-modifiers10.1.1), followed by the keyword class and an identifier that names the class, followed by an optional class-base specification (§10.1.2), followed by a class-body10.1.3), optionally followed by a semicolon.

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

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.2.2. 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 and sealed 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 non-abstract 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 non-abstract 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 non-abstract class is derived from an abstract class, the non-abstract 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 since it doesn’t provide an implementation of F, B must also be declared abstract. Class C overrides F and provides an actual implementation. Since there are no abstract members in C, C is permitted (but not required) to be non-abstract.

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 run-time 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 non-virtual invocations.

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

10.1.2.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.2.1.

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. Since A does not explicitly specify a direct base class, its direct base class is implicitly object.

The direct base class of a class type must be at least as accessible as the class type itself (§3.5.4). 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.Enum, or System.ValueType.

The base classes of a class 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 example above, the base classes of B are A and object.

Except for class object, every class 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 transitive closure of the directly depends on relationship.

The example

class A: B {}

class B: C {}

class C: A {}

is in error because the classes circularly depend on themselves. Likewise, 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 (since 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.2.2 Interface implementations

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

10.1.3 Class body

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

class-body:
{   class-member-declarationsopt   }

10.2 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 are divided into the following categories:

·         Constants, which represent constant values associated with the class (§10.3).

·         Fields, which are the variables of the class (§10.4).

·         Methods, which implement the computations and actions that can be performed by the class (§10.5).

·         Properties, which define named characteristics and the actions associated with reading and writing those characteristics (§10.6).

·         Events, which define notifications that can be generated by the class (§10.7).

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

·         Operators, which define the expression operators that can be applied to instances of the class (§10.9).

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

·         Destructors, which implement the actions to be performed before instances of the class are permanently discarded (§10.12).

·         Static constructors, which implement the actions required to initialize the class itself (§10.11).

·         Types, which represent the types that are local to the class (§9.5).

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

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 non-methods 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 (§10.2.1) 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.2.1 Inheritance

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

·         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. Note however that hiding an inherited member does not remove that member—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.4) 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 enables classes to exhibit polymorphic behavior wherein the actions performed by a function member invocation varies depending on the run-time type of the instance through which that function member is invoked.

10.2.2 The new modifier

A class-member-declaration is permitted to declare a member with the same name or signature as an inherited member. When this occurs, 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.2.3 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.2.4 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.2.5 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 classes and instance members as belonging to objects (instances of classes).

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. No matter how many instances of a class 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 non-static 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;         // Ok, same as this.x = 1
      y = 1;         // Ok, same as Test.y = 1
   }

   static void G() {
      x = 1;         // Error, cannot access this.x
      y = 1;         // Ok, same as Test.y = 1
   }

   static void Main() {
      Test t = new Test();
      t.x = 1;       // Ok
      t.y = 1;       // Error, cannot access static member through instance
      Test.x = 1;    // Error, cannot access instance member through type
      Test.y = 1;    // Ok
   }
}

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.2.6 Nested types

A type declared within a class or struct 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.2.6.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.2.6.2 Declared accessibility

Non-nested types can have public or internal declared accessibility and have internal declared accessibility by default. Nested types can have these forms of declared accessibility too, 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.2.6.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.2.6.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

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

shows this technique. An instance of C creates an instance of Nested and passes its own this to Nested’s constructor in order to provide subsequent access to C’s instance members.

10.2.6.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.2.7 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, thus they do not participate in member lookup. However, a declaration’s associated reserved method signatures do participate in inheritance (§10.2.1), and can be hidden with the new modifier (§10.2.2).

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.12) also causes a signature to be reserved (§10.2.7.4).

10.2.7.1 Member names reserved for properties

For a property P10.6) 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());
   }
}

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

123
123
456

10.2.7.2 Member names reserved for events

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

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

10.2.7.3 Member names reserved for indexers

For an indexer (§10.8) of type T with 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.

10.2.7.4 Member names reserved for destructors

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

void Finalize();

10.3 Constants

A constant is a class member that represents a constant value: a value that can be computed at