Classes

A class is the most common kind of reference type. The simplest possible class declaration is as follows:

class Foo
{
}

Optionally, a more complex class has the following:

Preceding the keyword class

Attributes and class modifiers. The non-nested class modifiers are public, internal, abstract, sealed, static, unsafe, and partial.

Following YourClassName

Generic type parameters, a base class, and interfaces.

Within the braces

Class members (these are methods, properties, indexers, events, fields, constructors, operator functions, nested types, and a finalizer).

A field is a variable that is a member of a class or struct. For example:

class Octopus
{
  string name;
  public int Age = 10;
}

A field may have the readonly modifier to prevent it from being modified after construction. A read-only field can be assigned only in its declaration or within the enclosing type’s constructor.

Field initialization is optional. An uninitialized field has a default value (0, \0, null, false). Field initializers run before constructors, in the order in which they appear.

For convenience, you may declare multiple fields of the same type in a comma-separated list. This is a convenient way for all the fields to share the same attributes and field modifiers. For example:

static readonly int legs = 8, eyes = 1;

A method performs an action in a series of statements. A method can receive input data from the caller by specifying parameters and output data back to the caller by specifying a return type. A method can specify a void return type, indicating that it doesn’t return any value to its caller. A method can also output data back to the caller via ref/out parameters.

A method’s signature must be unique within the type. A method’s signature comprises its name and parameter types (but not the parameter names, nor the return type).

Constructors run initialization code on a class or struct. A constructor is defined like a method, except the method name and return type are reduced to the name of the enclosing type:

public class Panda
{
  string name;              // Define field
  public Panda (string n)   // Define constructor
  {
    name = n;               // Initialization code
  }
}
...
Panda p = new Panda ("Petey");   // Call constructor

A class or struct may overload constructors. One overload may call another, using the this keyword:

public class Wine
{
  public Wine (decimal price) {...}

  public Wine (decimal price, int year)
               : this (price) {...}
}

When one constructor calls another, the called constructor executes first.

You can pass an expression into another constructor as follows:

public Wine (decimal price, DateTime year)
             : this (price, year.Year) {...}

The expression itself cannot make use of the this reference, for example, to call an instance method. It can, however, call static methods.

To simplify object initialization, the accessible fields or properties of an object can be initialized in a single statement directly after construction. For example, consider the following class:

public class Bunny
{
  public string Name;
  public bool LikesCarrots, LikesHumans;

  public Bunny () {}
  public Bunny (string n) { Name = n; }
}

Using object initializers, you can instantiate Bunny objects as follows:

Bunny b1 = new Bunny {
                       Name="Bo",
                       LikesCarrots = true,
                       LikesHumans = false
                     };

Bunny b2 = new Bunny ("Bo") {
                              LikesCarrots = true,
                              LikesHumans = false
                            };

The this reference refers to the instance itself. In the following example, the Marry method uses this to set the partner’s mate field:

public class Panda
{
  public Panda Mate;

  public void Marry (Panda partner)
  {
    Mate = partner;
    partner.Mate = this;
  }
}

The this reference also disambiguates a local variable or parameter from a field. For example:

public class Test
{
  string name;
  public Test (string name) { this.name = name; }
}

The this reference is valid only within nonstatic members of a class or struct.

Properties look like fields from the outside, but internally they contain logic, like methods do. For example, you can’t tell by looking at the following code whether CurrentPrice is a field or a property:

Stock msft = new Stock();
msft.CurrentPrice = 30;
msft.CurrentPrice −= 3;
Console.WriteLine (msft.CurrentPrice);

A property is declared like a field, but with a get/set block added. Here’s how to implement CurrentPrice as a property:

public class Stock
{
  decimal currentPrice;  // The private "backing" field

  public decimal CurrentPrice    // The public property
  {
     get { return currentPrice; }
     set { currentPrice = value; }
  }
}

get and set denote property accessors. The get accessor runs when the property is read. It must return a value of the property’s type. The set accessor runs when the property is assigned. It has an implicit parameter named value of the property’s type that you typically assign to a private field (in this case, currentPrice).

Although properties are accessed in the same way as fields, they differ in that they give the implementer complete control over getting and setting its value. This control enables the implementer to choose whatever internal representation is needed, without exposing the internal details to the user of the property. In this example, the set method could throw an exception if value was outside a valid range of values.

A property is read-only if it specifies only a get accessor, and it is write-only if it specifies only a set accessor. Write-only properties are rarely used. A property typically has a dedicated backing field to store the underlying data. However, it need not—it may instead return a value computed from other data.

Indexers provide a natural syntax for accessing elements in a class or struct that encapsulate a list or dictionary of values. Indexers are similar to properties, but are accessed via an index argument rather than a property name. The string class has an indexer that lets you access each of its char values via an int index:

string s = "hello";
Console.WriteLine (s[0]); // 'h'
Console.WriteLine (s[3]); // 'l'

The syntax for using indexers is like that for using arrays when the index is an integer type.

A constant is a field whose value can never change. A constant is evaluated statically at compile time and the compiler literally substitutes its value whenever used, rather like a macro in C++. A constant can be any of the built-in numeric types, bool, char, string, or an enum type.

A constant is declared with the const keyword and must be initialized with a value. For example:

public class Test
{
  public const string Message = "Hello World";
}

A constant is much more restrictive than a static readonly field—both in the types you can use and in field initialization semantics. A constant also differs from a static readonly field in that the evaluation of the constant occurs at compile time. Constants can also be declared local to a method:

static void Main()
{
  const double twoPI = 2 * System.Math.PI;
  ...
}

A static constructor executes once per type, rather than once per instance. A type can define only one static constructor, and it must be parameterless and have the same name as the type:

class Test
{
  static Test() { Console.Write ("Type Initialized"); }
}

The runtime automatically invokes a static constructor just prior to the type being used. Two things trigger this: instantiating the type, and accessing a static member in the type.

Warning

If a static constructor throws an unhandled exception, that type becomes unusable for the life of the application.

Static field initializers run just before the static constructor is called. If a type has no static constructor, field initializers will execute just prior to the type being used—or anytime earlier at the whim of the runtime. (This means that the presence of a static constructor may cause field initializers to execute later in the program than they would otherwise.)

A class can be marked static, indicating that it must be composed solely of static members and cannot be subclassed. The System.Console and System.Math classes are good examples of static classes.

Finalizers are class-only methods that execute before the garbage collector reclaims the memory for an unreferenced object. The syntax for a finalizer is the name of the class prefixed with the ~ symbol:

class Class1
{
  ~Class1() { ... }
}

C# translates a finalizer into a method that overrides the Finalize method in the object class. We discuss garbage collection and finalizers fully in Chapter 12 of C# 4.0 in a Nutshell (O’Reilly).

Partial types allow a type definition to be split—typically across multiple files. A common scenario is for a partial class to be auto-generated from some other source (e.g., an XSD), and for that class to be augmented with additional hand-authored methods. For example:

// PaymentFormGen.cs - auto-generated
partial class PaymentForm { ... }

// PaymentForm.cs - hand-authored
partial class PaymentForm { ... }

Each participant must have the partial declaration.

Participants cannot have conflicting members. A constructor with the same parameters, for instance, cannot be repeated. Partial types are resolved entirely by the compiler, which means that each participant must be available at compile time and must reside in the same assembly.

A base class may be specified on a single participant or on all participants. In addition, each participant can independently specify interfaces to implement. We cover base classes and interfaces in the sections Inheritance and Interfaces.