A class is the most common kind of reference type. The simplest possible class declaration is as follows:
class YourClassName { }
A more complex class optionally has:
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.
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).
A type may overload methods (have many methods with the same name), as long as the signatures are different. For example, the following methods can all coexist in the same type:
void Foo (int x); void Foo (double x); void Foo (int x, float y); void Foo (float x, int y);
However, the following pairs of methods cannot coexist in the same type, as the
return type and the params
modifier are not part of a
method’s signature:
void Foo (int x); float Foo (int x); // Compile error void Goo (int[] x); void Goo (params int[] x); // Compile error
Whether a parameter is pass-by-value or pass-by-reference is also part of the signature. For example, Foo(int)
can coexist with either Foo(ref
int)
or Foo(out int)
. However, Foo(ref int)
and Foo(out
int)
cannot coexist:
void Foo (int x); void Foo (ref int x); // OK so far void Foo (out int x); // Compile error
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 // (set up field) } } ... Panda p = new Panda ("Petey"); // Call constructor
A class or struct may overload constructors. To avoid code duplication, one
constructor may call another, using the this
keyword:
using System;
public class Wine
{
public decimal Price;
public int Year;
public Wine (decimal price) { Price = price; }
public Wine (decimal price, int year)
: this (price)
{ Year = year; }
}
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.
We saw previously that fields can be initialized with default values in their declaration:
class Player { int shields = 50; // Initialized first int health = 100; // Initialized second }
Field initializations occur before the constructor is executed, and in the declaration order of the fields.
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; public bool LikesHumans; public Bunny () { } public Bunny (string n) { Name = n; } }
Using object initializers, you can instantiate Bunny
objects as follows:
// Note parameterless constructors can omit // empty parenthesis 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 argument 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 act like methods
on the inside. 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 is run 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 extensively 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, a property can also be computed from other data, for example:
decimal currentPrice, sharesOwned; public decimal Worth { get { return currentPrice * sharesOwned; } }
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.
Indexers provide a natural syntax for accessing elements in a
class or struct that encapsulates 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 of using arrays, when the index is an integer type.
Indexers allow the same modifiers as properties (see the previous section on property modifiers).
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 its value is literally substituted by the
compiler 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.
For example:
public static double Circumference (double radius) { return 2 * System.Math.PI * radius; }
is compiled to:
public static double Circumference (double radius) { return 6.2831853071795862 * radius; }
It makes sense for PI
to be a constant, as it can
never change. In contrast, a static readonly
field can
have a different value per application.
Constants can also be declared local to a method. For example:
static void Main( ) { const double twoPI = 2 * System.Math.PI; ... }
A static constructor executes once per type—rather than once per instance. A static constructor executes before any instances of the type are created, and before any other static members are accessed. 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.WriteLine ("Type Initialized"); } }
The only modifiers allowed by static constructors are unsafe
and extern
.
Any static field assignments are performed before the static constructor is called, in the declaration order in which the fields appear.
A static constructor is always invoked indirectly by the runtime—it cannot be called explicitly. The runtime guarantees to invoke a type’s static constructor at some point prior to the type being used; it doesn’t commit, though, to exactly when. For example, a subclass’s static constructor can execute before or after that of its base class. The runtime may also choose to invoke static constructors unnecessarily early, from a programmer’s perspective.
A class can be marked static
, indicating that it
must be comprised 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 just 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( ) { ... } }
This is actually C# syntax for overriding Object's
Finalize
method, and it is expanded by the compiler into the following method
declaration:
protected override void Finalize( ) { ... base.Finalize( ); }
We describe the implications of finalizers in detail in Chapter 12 of C# 3.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-generatedpartial
class PaymentForm { ... } // PaymentForm.cs — hand-authoredpartial
class PaymentForm { ... }
Each participant must have the partial
declaration;
the following is illegal:
partial class PaymentForm { } class PaymentForm { }
Participants cannot have conflicting members. A constructor with the same arguments, 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.
You can specify a base class on one or more partial participants—as long as there’s no disagreement in the base class name. Base classes are used for inheritance (see the upcoming “Inheritance” section).
A partial type may contain partial methods. Partial methods 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) throw new ArgumentException ("Too 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
.