Classes

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:

  

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)

Fields

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

Field initialization is optional. An uninitialized field has a default value (0, , null, false). Field initializers run before constructors:

	string name = "anonymous";

Declaring multiple fields together

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;

Methods

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

Overloading methods

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

Pass-by-value versus pass-by-reference

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

Instance Constructors

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

Overloading constructors

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.

Implicit parameterless constructors

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.

Constructor and field initialization order

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.

Nonpublic constructors

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, which can be used to return an object from a pool rather than necessarily creating a new object, or return various subclasses based on input arguments.

Object Initializers (C# 3.0)

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

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

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.

Note

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.

Read-only and calculated properties

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

Automatic properties (C# 3.0)

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.

get and set accessibility

The get and set accessors are permitted to have different access levels. The typical use case 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;  }
	  internal set { x = value; }
	}

Indexers

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.

Note

Indexers allow the same modifiers as properties (see the previous section on property modifiers).

Implementing an indexer

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 { ... }
	}

Constants

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;
	  ...
	}

Static Constructors

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.

Nondeterminism of static constructors

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.

Static Classes

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

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 and Methods

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; 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).

Partial methods (C# 3.0)

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.

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

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