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 | Attributes and
class modifiers. The non-nested class
modifiers are |
Following | 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.
For classes, the C# compiler automatically generates a parameterless constructor if and only if you do not define any constructors. However, as soon as you define at least one constructor, the parameterless constructor is no longer automatically generated.
For structs, a parameterless constructor is intrinsic to the struct; therefore, you cannot define your own. The role of a struct’s implicit parameterless constructor is to initialize each field with default values.
Constructors do not need to be public. A common reason to have a nonpublic constructor is to control instance creation via a static method call. The static method could be used to return an object from a pool rather than creating a new object, or return a specialized subclass chosen based on input arguments.
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.
Throughout this book, we use public fields to keep the examples free of distraction. In a real application, you would typically favor public properties over public fields to promote encapsulation.
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.
The most common implementation for a property is a getter and/or setter that simply reads and writes to a private field of the same type as the property. An automatic property declaration instructs the compiler to provide this implementation. We can redeclare the first example in this section as follows:
public class Stock { public decimal CurrentPrice { get; set; } }
The compiler automatically generates a private backing field of
a compiler-generated name that cannot be referred to. The set
accessor can be marked private
if you want to expose the property
as read-only to other types.
The get
and set
accessors can have different access levels. The typical
use case for this is to have a public
property with an internal
or private
access modifier on the
setter:
private decimal x;
public decimal X
{
get { return x; }
private
set { x = Math.Round (value, 2); }
}
Notice that you declare the property itself with the more
permissive access level (public
, in
this case), and add the modifier to the accessor you want to be
less accessible.
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.
To write an indexer, define a property called this
, specifying the arguments in square brackets. For instance:
class Sentence { string[] words = "The quick brown fox".Split(); public string this [int wordNum] // indexer { get { return words [wordNum]; } set { words [wordNum] = value; } } }
Here’s how we could use this indexer:
Sentence s = new Sentence(); Console.WriteLine (s[3]); // fox s[3] = "kangaroo"; Console.WriteLine (s[3]); // kangaroo
A type may declare multiple indexers, each with parameters of different types. An indexer can also take more than one parameter:
public string this [int arg1, string arg2] { get { ... } set { ... } }
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.
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.
A partial type may contain partial methods. These let an auto-generated partial type provide customizable hooks for manual authoring. For example:
partial class PaymentForm // In auto-generated file { partial void ValidatePayment (decimal amount); } partial class PaymentForm // In hand-authored file { partial void ValidatePayment (decimal amount) { if (amount > 100) Console.Write ("Expensive!"); } }
A partial method consists of two parts: a
definition and an
implementation. The definition is typically
written by a code generator, and the implementation is typically
manually authored. If an implementation is not provided, the
definition of the partial method
is compiled away. This allows auto-generated code to be liberal in
providing hooks, without having to worry about code bloat. Partial
methods must be void
and are implicitly private
.