Classes

Classes

Classes are the irreplaceable ingredient of any .NET assembly. First, all executable code must be contained in a type, usually a class. Global functions and variables are not permitted in C#, preventing problematic dependences caused by disparate references to global entities. Second, classes published in the .NET Framework Class Library provide important services that are integral to any .NET application.

Classes are described in a class declaration. A class declaration consists of a class header and body. The class header includes attributes, modifiers, the class keyword, the class name, and the base class list. The class body encapsulates the members of the class, which are the data members and member functions. Here is the syntax of a class declaration:

  • attributes accessibility modifiers class classname : baselist { class body };

Attributes further describe the class. If a class is a noun, attributes are the adjectives. For example, the Serializable attribute identifies a class that can be serialized to storage. There is an assortment of predefined attributes. You can also define custom attributes. Attributes are optional, and there are no defaults. Further details on attributes are contained in Chapter 11, "Metadata and Reflections."

Accessibility is the visibility of the class. Public classes are visible in the current assembly and assemblies referencing that assembly. Internal classes are visible solely in the containing assembly. The default accessibility of a class is internal. Nested classes have additional accessibility options, which are described later in this chapter.

Modifiers refine the declaration of a class. For example, the abstract keyword prevents instances of the class from being created. Modifiers are optional, and there is no default. Table 2-1 elucidates the modifiers.

Table 2-1: Class Modifier Table

Modifier

Description

Abstract

Class is abstract; future instances of the class cannot be created.

Sealed

Class cannot be inherited and refined in a derived class.

static

Class contains only static members.

Unsafe

Class can contain unsafe constructs, such as a pointer; requires the unsafe compiler option.

A class can inherit the members of a single base class. Multiple inheritance is not supported in the Common Language Specification of .NET. However, a class can inherit and implement multiple interfaces. The baselist lists the class inherited and any interfaces to be implemented. By default, classes inherit the System.Object type. Inheritance and System.Object are expanded upon in Chapter 3, "Inheritance."

The class body encompasses the members of the class that entail the behavior and state of a class. The member functions are the behavior, whereas data members are the state. As a design goal, classes should expose an interface composed of the public functions of the class. The state of the class should be abstracted and described through the behavior of the class. You manage the state of an object through its public interface.

Classes do not require members. All members can be inherited. In addition, an empty class can function as a valuable marker or cookie at run time when ascertaining Run-time Type Information (RTTI).

The semicolon at the end of the class is optional.

XInt is a class and a thin wrapper for an integer:

internal sealed class XInt {
.....public int iField=0;
}

The XInt class has internal accessibility and visibility in the current assembly, but is not visible to an external assembly. The sealed modifier means that the class cannot be refined through inheritance.

The following code uses the new statement to create an instance of a class:

.....public void Func() {
.........XInt obj=new XInt();
.........obj.iField=5;
.....}

Class Members

Classes are composed of members: member functions and data members. Use the dot syntax to access members. The dot binds an instance member to an object or a static member to a class. In the following code, Fred.name accesses the name field of the Fred object:

using System;
namespace Donis.CSharpBook{
    public class Employee{
        public string name;
    }
    public class Personnel{
        static void Main(){
            Employee Jim=new Employee();
            Fred.name="Wilson, Jim";
        }
    }
}

Table 2-2 describes the list of potential class members.

Table 2-2: Type Members

Member

Description

Classes

Nested classes

Constants

Invariable data members

Constructor

Specialized methods that initializes a component or class for static constructors

Delegate

Type-safe containers of one or more function pointers

Destructor

Specialized method that performs cleanup of object resources upon garbage collection

Events

Callbacks to methods provided by a subscriber

Fields

Data members

Indexer

Specialized property that indexes the current object

Interfaces

Nested interface

Method

Reusable code sequence

Operators

Operator member functions that override implicit operator behavior

Properties

Get and set functions presented as a field

Structures

Nested structure within a class

When a member is declared, attributes can be applied and accessibility defined. Members are further described with attributes. Accessibility sets the visibility as class member.

Member Accessibility

Members defined in a class are scoped to that class. However, the visibility of the member is defined by accessibility keywords. The most common accessibility is public and private. Public members are visible inside and outside of the class. The visibility of private members is restricted to the containing class. Private is the default accessibility of a class member. Table 2-3 details the accessibility keywords:

Table 2-3: Accessibility

Accessibility

Description

internal

Visible in containing assembly

internal protected

Visible in containing assembly or descendants of the current class

private

Visible inside current class

protected

Visible inside current class and any descendants

public

Visible in containing assembly and assemblies referencing that assembly

Member Attributes

Attributes are usually the first element of the member declaration and further describe a member and extend the metadata. The Obsolete attribute exemplifies an attribute and marks a function as deprecated. Attributes are optional. By default, a member has no attributes.

Member Modifiers

Modifiers refine the definition of the applicable member. Modifiers are optional and there are no defaults. Some modifiers are reserved for classification of members. For example, the override modifier is applicable to member functions, not data members. Table 2-4 lists the available modifiers.

Table 2-4: Modifiers

Modifier

Description

abstract

A member function has no implementation and is described through inheritance.

Extern

Implemented in a foreign dynamic-link library (DLL).

New

Hides a similar member or members in the base class.

Override

Indicates that a function in a derived class overrides a virtual method in the base class.

Readonly

Read-only fields are initialized at declaration or in a constructor.

Sealed

The member function cannot be further refined through inheritance.

Static

Member belongs to a class and not an instance.

Virtual

Virtual functions are overridable in a derived class.

Volatile

Volatile fields are modifiable by the environment, a separate thread, or hardware.

Instance and Static Members

Members belong either to an instance of a class or to the class itself. Members bound to a class are considered static. Except for constants, class members default to instance members and are bound to an object via the this object. Constant members are implicitly static. Static members are classwise and have no implied this context. Not all members can be static or instance members; destructors and operator member functions are representative of this. Destructors cannot be static, whereas operator member functions cannot be instance members.

Instance members are qualified by the object name. Here is the syntax of a fully qualified instance member:

  • objectname.instancemember

Static members are prefixed with this class name:

  • classname.staticmember

The lifetime of static members is closely linked to the lifetime of the application. Instance members are inexorably linked to an instance and are accessible from the point of instantiation. Access to instance members ceases when the instance variable is removed. Therefore, the lifetime of an instance member is a subset of the lifetime of an application. Static members are essentially always available, whereas instance members are not. Static members are similar to global functions and variables but have the benefit of encapsulation. Static members can be private.

Your design analysis should determine which members are instance versus static members. For example, in a personnel application for small businesses, there is an Employee class. The employee number is an instance member of the class. Each employee is assigned a unique identifier. However, the company name member is static because all employees work for the same company. The company name does not belong to a single employee, but to the classification.

Static class A static class is a class that contains static members and no instance members. Because static classes have no instance data, a static class cannot be instantiated. This is a static class:

public static class ZClass {
   public static void MethodA() {}
   public static void MethodB() {}
}

this Object

The this reference refers to the current object. Instance members functions are passed a this reference as the first and hidden parameter of the function. In an instance function, the this reference is automatically applied to other instance members. This assures that within an instance member function, you implicitly refer to members of the same object. In this code, GetEmployeeInfo is an instance member function referring to other instance members (the this reference is implied):

public void GetEmployeeInfo(){
   PrintEmployeeName();
   PrintEmployeeAddress();
   PrintEmployeeId();
}

Here is the same code, except that the this pointer is explicit (the behavior of the function is the same):

public void GetEmployeeInfo(){
   this.PrintEmployeeName();
   this.PrintEmployeeAddress();
   this.PrintEmployeeId();
}

In the preceding code, the this reference was not required. The this reference is sometimes required as a function return or parameter. In addition, the this reference can improve code readability and provide IntelliSense for class members when editing the code in Microsoft Visual Studio.

Static member functions are not passed a this reference as a hidden parameter. For this reason, a static member cannot directly access nonstatic members of the class. For example, you cannot call an instance method from a static member function. Static members are essentially limited to accessing other static members. Instance members have access to both instance and static members of the class.

The this reference is a read-only reference. As part of error handling, setting a this reference to null would sometimes be beneficial. Alas, it is not allowed.

Data and Function Members

Members are broadly grouped into data and function members. This chapter reviews constants, fields, and nested types, which are data members. Methods, constructors, destructors, and properties are member functions (and are covered in this chapter). The remaining members, such as events and delegates, are discussed in Chapter 8, "Delegates and Events."

Data members are typically private to enforce encapsulation and the principles of data hiding and abstraction. Abstraction abstracts the details of a class and restricts collaboration to the public interface. The developer responsible for the class obtains implementation independence and has the freedom to modify the implementation when required as long as the interface is unchanged. The interface is immutable—like a contract between the class and any client. As a best practice, data members should be private and described fully through the public interface. The class interface consists of the public member functions. Do not make every member function public; internal functionality should remain private.

Except for constructors and destructors, members should not have the same name as the containing class. As a policy, class names should differ from the surrounding namespace.

Constants

Constants are data members. They are initialized at compile time using a constant expression and cannot be modified at run time. Constants have a type designation and must be used within the context of that type. This makes a constant type-safe. Constants are usually a primitive type, such as integer or double. A constant can be a more complex type. However, classes and structures are initialized at run time, which is prohibited with a constant. Therefore, constant class and structures must be assigned null, which limits their usefulness. Following is the syntax of a constant member:

const syntax:

  • accessibility modifier const constname=initialization;

The only modifier available is new, which hides inherited members of the same name. Constants are tacitly static. Do not use the static keyword explicitly. The initialization is accomplished with a constant expression. You can declare and initialize several constants simultaneously.

The following code presents various permutations of how to declare and initialize a constant data member:

public class ZClass {
    public const int fielda=5, fieldb=15;
    public const int fieldc=fieldd+10;  // Error

    public static int fieldd=15;
}

The assignment to fieldc causes a compile error. The fieldd member is a nonconstant member variable and is evaluated at run time. Thus, fieldd cannot be used in a constant expression, as shown in the preceding code. Constant expressions are limited to literals and constant types.

Fields

As a data member, fields are the most prevalent. Instance fields hold state information for a specific object. The state information is copied into the managed memory created for the object. Static fields are data owned by the class. A single instance of static data is created for the class. It is not replicated for each instance of the class. Fields can be reference or value types. Here is the syntax for declaring a field:

  • accessibility modifier type fieldname=initialization;

Fields support the full assortment of accessibility. Valid modifiers for a field include new, static, read-only, and volatile. Initialization is optional but recommended. Uninitialized fields are assigned a default value of zero or null. Alternatively, fields can be initialized in constructor functions. Like constants, fields of the same type are declarable individually or in aggregate in the initialization list.

Initialization is performed in the textual order in which the fields appear in the class, which is top-down. This process is demonstrated in the following code:

using System;

namespace Donis.CSharpBook{
    internal class ZClass {
        public int iField1=FuncA();
        public int iField2=FuncC();
        public int iField3=FuncB();

        public static int FuncA() {
            Console.WriteLine("ZClass.FuncA");
            return 0;
        }

        public static int FuncB() {
            Console.WriteLine("ZClass.FuncB");
            return 1;
        }

        public static int FuncC() {
            Console.WriteLine("ZClass.FuncC");
            return 2;
        }
    }

    public class Starter{
       public static void Main(){
        ZClass obj=new ZClass();
        }
    }
}

Running this code confirms that FuncA, FuncC, and FuncB are called in sequence the textual order of the initialization. Avoid writing code dependent on the initialization sequence—writing code that way makes the program harder to maintain without clear documentation.

Private fields exposed through accessor functions, a get and set method, are read-write. Eliminate the get or set method to make the field read-only or write-only, respectively. However, this relies on programmer discipline. The read-only keyword is safer.

A read-only field is enforced by the run time. These fields are different from constants. Although constants can be initialized only at compile time, read-only fields can also be initialized in a constructor. Alternatively, read-only fields can be initialized with nonconstant expressions. These are subtle but important nuances and provide considerable more flexibility to a read-only field. In addition, read-only fields can be instance or static members, whereas constant are only static.

This code highlights read-only fields:

public class ZClass {
    public ZClass() { // constructor
        fieldc=10;
    }

    public static int fielda=5;
    public readonly int fieldb=fielda+10;
    public int fieldc;
};

Volatile Fields

Because of the intricacy of program execution, writes and reads to a field might not be immediate. This latency can cause problems, particularly in a multithreaded application. Reads and writes to volatile fields are essentially immediate. Volatile makes automatic what locks do programmatically. Locks are explained in Chapter 9, "Threading."

Member Functions

Member functions contain the behavior of the class. Methods, properties, and indexers are the member functions. Methods are straightforward functions that accept parameters as input and return the result of an operation. Properties are accessor methods, a get and set method, masked as a field. An indexer is a specialized property. Indexers apply get and set methods to the this reference, which refers to the current object. The discussion of indexers is deferred to Chapter 6, "Arrays and Collections."

Function Code

Functions encapsulate a sequence of code and define the behavior of the class. A function is not necessarily executed in sequential order and sometimes contains iterative and conditional transfer control statements. Return statements provide an orderly exit to a function. Void functions can simply fall through the end of the function. The compiler extrapolates the code paths of a function and uncovers any unreachable code, which is subsequently flagged by the compiler. All paths must conclude with an orderly exit. The following code includes both unreachable code and a code path without an orderly exit:

public static int Main(){
    if(true) {
        Console.WriteLine("true");
    }

    else {
        Console.WriteLine("false");
    }
}

The if statement is always true. Therefore, the else code is unreachable. Main returns int. However, the method has no return. For these reasons, the application generates the following errors when compiled:

unreachable.cs(10,17): warning CS0162: Unreachable code detected
unreachable.cs(5,27): error CS0161: ' Donis.CSharpBook.Starter.Main()': not all code paths
return a value

Keep functions relatively short. Extended functions are modestly harder to debug and test. A class comprised of several functions is preferable to class of a few convoluted functions. Remoted components are the exception: A narrower interface is preferred to minimize client calls and optimize network traffic. Comparatively, local invocations are relatively quick and efficient.

Methods own a code sequence, local variables, and parameters. The local variables and parameters represent the private state of the method.

Local Variables

Local variables and parameters represent the private state of the function. Local variables are reference or value types. Declare a local variable anywhere in a method. However, a local variable must be defined before use. Optionally, initialize local variables at declaration. There are competing philosophies as to when to declare local variables: Some developers prefer to declare local variables at the beginning of a method or block, where they are readily identifiable; other developers like to declare and initialize local variables immediately prior to use. They feel that local variables are more maintainable when situated near the affected code. Local variables are not assigned a default value prior to initialization. For this reason, using an unassigned local variable in an expression causes a compile error. This is the syntax of a local variable:

  • modifier type variablename=initialization;

The const modifier is the only modifier that is applicable. Variables that are const must be initialized at compile time.

The scope of a local variable or function parameter is the entire function. Therefore, local variables and function parameters must have unique names. Local variables of the same type can be declared individually or in aggregate.

Scope Local variables are declared at the top level or in a nested block within the method. Although the scope of a local variable is the entire function, the visibility of a local variable starts where the local variable is declared. Visibility ends when the block that the local variable is declared is exited. Local variables are visible in descendant blocks, but not ascendant blocks. Because local variables maintain scope throughout a function, names of local variable cannot be reused—even in a nested block. Figure 2-1 illustrates the relationship between scope and visibility of local variables.

Image from book
Figure 2-1: Figure diagramming the scope and visibility of a local variable

Local variables of a value type are removed from the stack when a function is exited. Local references are also removed from the stack at that time, whereas the objects they referred to become candidates for garbage collection. This assumes that no other references to the object exist at that time. If other outstanding references exist, the object associated with the reference remains uncollectible. When a reference is no longer needed, assign null to the reference. This is a hint to the garbage collector that the related object is no longer needed.

Local variables are private to the current function. Functions cannot access private data of another function. Identically named local variables in different functions are distinct and separate entities, which are stored at different memory addresses. To share data between functions, declare a field.

Methods

Methods are the most common function member. Methods accept parameters as input, perform an operation, and return the result of the operation. Both parameters and the return result are optional. Methods are described through the method header, and implemented in the method body. Here is the syntax of a method:

Method syntax:

  • attributes accessibility modifiers returntype methodname(parameter list) { method body };

Any attribute that targets a method can prefix the method. The accessibility of methods included in the class interface is public. Methods not included in the interface are typically protected or private. Methods can be assigned the sealed, abstract, new, and other modifiers, as explained in Chapter 3. The return is the result of the method. Methods that return nothing stipulate a void returntype. The parameter list, which consists of zero or more parameters, is the input of the method. The method body contains the statements of the method.

Method Return Execution of a function starts at the beginning and ends at a return. The return keyword provides the result of function or an error code. A value or reference type can be returned from a method as defined by the return type. Functions can contain multiple returns, each on a separate code path. More than one return along a single code path results in unreachable code, which causes compiler warnings.

Functions with a void return type can be exited explicitly or implicitly. An empty return statement explicitly exits a function of a void return type. For an implicit exit, execution is allowed to exit the function without a return statement. At the end of the function block, the function simply exits. Implicit returns are reserved for functions that return void. A function can have multiple explicit returns but only one implicit return. The end of a function must be reached for an implicit return, whereas an explicit return can exit the function prematurely. In the following code, Main has both an explicit and implicit exit:

static void Main(string [] arg){
    if(arg.Length>0) {
        // do something
         return;
    }

    // implicit return
}

A function that returns void cannot be used in an assignment. This type of method evaluates to nothing and is thereby not assignable. This prevents a function that returns void from being used as a left- or right-value of an assignment. Other functions are usable in assignments and more generally in expressions. A function evaluates to the return value. When a reference type is returned, a function is available as a left- or right-value of an assignment. Functions that return a value type are restricted to right-values of an assignment. The following code demonstrates the use of methods in expressions:

public class ZClass {
    public int MethodA() {
        return 5;
    }

    public void MethodB() {
        return;
    }

    public int MethodC(){
        int value1=10;
        value1=5+MethodA()+value1;  // Valid
        MethodB();                  // Valid
        value1=MethodB();           // Invalid
        return value1;
    }
}

At the calling site, the return of a function is temporarily copied to the stack. The return is discarded after that statement. To preserve the return, assign the method return to something. Returns are copy by value. When returning a value type, a copy of the result is returned. For reference types, a copy of the reference is returned. This creates an alias to an object in the calling function. Look at the following code:

using System;

namespace Donis.CSharpBook{

    public class XInt {
        public int iField=0;
    }

    public class Starter{

        public static XInt MethodA() {
            XInt inner=new XInt();
            inner.iField=5;
            return inner;
        }

        public static void Main(){
            XInt outer=MethodA();
            Console.WriteLine(outer.iField);
        }
    }
}

In the preceding code, MethodA creates an instance of XInt, which is subsequently returned from the method. The scope of the reference called inner, which is a local variable, is MethodA. After the return, outer is an alias and refers to the inner object, which prevents the object from becoming a candidate for garbage collection, even thought the inner reference is no longer valid.

A method returns a single item. What if you want to return multiple values? The solution is to return a structure containing a data member for each value. Structures are lightweight and appropriate for copying by value on the stack.

Function parameters Functions have zero or more parameters, where an empty function call operator indicates zero parameters. Parameter lists can be fixed or variable length. Use the param keyword to construct a variable length parameter list, which is discussed in Chapter 6. Parameters default to pass by value. When the parameter is a value type, changes to the parameter are discarded when the method is exited and the value is removed from the stack. In the following code, changes made in the function are discarded when the function returns:

using System;

namespace Donis.CSharpBook{
    public class Starter{

        public static void MethodA(int parameter) {
            parameter=parameter+5;
        }    //  change discarded

        public static void Main(){
            int local=2;
            MethodA(local);
            Console.WriteLine(local); // 2 outputted
        }
    }
}

As a parameter, reference types are also passed by value. A copy of the reference, which is the location of the object, is passed by value. Therefore, the function now has an alias to the actual object, which can be used to change the object. Those changes will persist when the function exits.

This code revises the preceding code. An integer wrapper class is passed as a parameter instead of an integer value. Changes made to the object in MethodA are not discarded later.

using System;

namespace Donis.CSharpBook{

    public class XInt {
        public int iField=2;
    }

    public class Starter{

        public static void MethodA(XInt alias) {
            alias.iField+=5;
        }

        public static void Main(){
            XInt obj=new XInt();
            MethodA(obj);
            Console.WriteLine(obj.iField); // 7
        }
    }
}

Pass a parameter by reference using the ref modifier. The location of the parameter is passed into the function. The ref attribute must be applied to the parameter in the function signature and at the call site. The function now possesses the location of the actual parameter and can change the parameter directly. The following code has been updated to include the ref modifier. Unlike the first revision of this code, changes made in MethodA are not discarded.

using System;

namespace Donis.CSharpBook{
    public class Starter{

        public static void MethodA(ref int parameter) {
            parameter=parameter+5;
        }

        public static void Main(){
            int var=2;
            MethodA(ref var);
            Console.WriteLine(var); // 7
        }
    }
}

What happens when a reference type is passed by reference? The function receives the location of the reference. Because a reference contains a location to an object, the location of the location is passed into the function. In the following code, a reference type is passed by value, which creates an alias. Because the reference is passed by value, changes to the alias are discarded when the function exits.

using System;

namespace Donis.CSharpBook{

    public class XInt {
        public int iField=2;
    }

    public class Starter{

        public static void MethodA(XInt alias) {
            XInt inner=new XInt();
            inner.iField=5;
            alias=inner;
        } // reference change lost

        public static void Main(){
            XInt obj=new XInt();
            MethodA(obj);
            Console.WriteLine(obj.iField); // 2
        }
    }
}

Next, the code is slightly modified to include the ref attribute. The assignment in MethodA is not discarded when the method exits. Therefore, obj is updated to reference the object created in the method.

using System;

namespace Donis.CSharpBook{

    public class XInt {
        public int iField=2;
    }

    public class Starter{

        public static void MethodA(ref XInt alias) {
            XInt inner=new XInt();
            inner.iField=5;
            alias=inner;
        }

        public static void Main(){
            XInt obj=new XInt();
            MethodA(ref obj);
            Console.WriteLine(obj.iField); // 5
        }
    }
}

Local variables must be initialized before use. The following code is in error. The obj reference is unassigned but initialized in the method. However, the compiler is unaware of this and presents an error for using an unassigned variable. The out parameter is a hint to the compiler that the parameter is being set in the function, which prevents the compiler error. The out parameter is not required to be initialized before the method call. These parameters are often used to return multiple values from a function.

using System;

namespace Donis.CSharpBook{

    public class XInt {
        public int iField=5;
    }

    public class Starter{

        public static void MethodA(ref XInt alias) {
            XInt inner=new XInt();
            alias=inner;
        }

        public static void Main(){
            XInt obj;
            MethodA(ref obj); // Error
        }
    }
}

In the following code, the parameter modifier is changed to out. The program compiles and runs successfully when the attribute is changed to out:

using System;

namespace Donis.CSharpBook{

    class XInt {

        public int iField=5;
    }

    class Starter{

        public static void MethodA(out XInt alias) {
            XInt inner=new XInt();
            alias=inner;
        }

        public static void Main(){
            XInt obj;
            MethodA(out obj);
            Console.WriteLine(obj.iField); // 5
        }
    }
}

Function Overloading

Function overloading permits multiple implementations of the same function in a class, which promotes a consistent interface for related behavior. The SetName method would be overloaded in the Employee class to accept variations of an employee name. Overloaded methods share the same name but have unique signatures. Parameter attributes, such as out, are considered part of the signature. (The signature is the function header minus the return type.) Because they are excluded from the signature, functions cannot be overloaded on the basis of a different return type. The number of parameters or the type of parameters must be different. With the exception of constructors, you cannot overload a function based on whether a member is a static or instance member.

The process of selecting a function from a set of overloaded methods is called function resolution, which occurs at the call site. The closest match to a specific function is called as determined by the number of parameters and the types of parameters given at function invocation. Sometimes a function call matches more than one function in the overloaded set. When function resolution returns two or more methods, the call is considered ambiguous, and an error occurs. Conversely, if no method is returned, an error is also manifested.

This sample code overloads the SetName method:

using System;

namespace Donis.CSharpBook{

    public class Employee {

        public string name;

        public void SetName(string last) {
            name=last;
        }

        public void SetName(string first, string last) {
            name=first+" "+last;
        }

        public void SetName(string saluation, string first, string last) {
            name=saluation+" "+first+" "+last;
        }
    }

    public class Personnel {
        public static void Main(){
            Employee obj=new Employee();
            obj.SetName("Bob", "Jones");

        }
    }
}

Functions with variable-length parameter lists can be included in the set of overloaded functions. The function is first evaluated with a fixed-length parameter list. If function resolution does not yield a match, the function is evaluated with variable-length parameters.

Constructors

A constructor is a specialized function for initializing the state of an object. Constructors have the same name of the class. The main purpose of a constructor is to initialize the fields and guarantee that object is in a known state for the lifetime of the object. Constructors are invoked with the new operator. Here is the syntax of a constructor:

Constructor syntax:

  • accessibility modifier typename(parameterlist)

The accessibility of a constructor determines where new instances of the reference type can be created. For example, a public constructor allows an object to be created in the current assembly and a referencing assembly. A private constructor prevents an instance from being created outside the class. extern is the only modifier for constructors.

The parameter list of constructors has zero or more parameters.

This is the constructor for the Employee class:

using System;

namespace Donis.CSharpBook{

     public class Employee {

        public Employee(params string []_name) {
             switch(_name.Length) {

               case 1:
                   name=_name[0];
                   break;
               case 2:
                   name=_name[0]+" "+_name[1];
                   break;
               default:
                   // Error handler
                   break;
            }

        }

        public void GetName() {
            Console.WriteLine(name);
        }

        private string name="";
    }

    public class Personnel{

        public static void Main(){
            Employee bob=new Employee("Bob", "Wilson");
            bob.GetName();
        }
    }
}

Classes with no constructors are given a default constructor, which is a parameterless constructor. Default constructors assign default values to fields. An empty new statement invokes a default constructor. You can replace a default constructor with a custom parameterless constructor. The following new statement calls the default constructor of a class:

Employee empl=new Employee();

Constructors can be overloaded. The function resolution of overloaded constructors is determined by the parameters of the new statement. This Employee class has overloaded constructors:

using System;

namespace Donis.CSharpBook{

    public class Employee {

        public Employee(string _name) {
            name=_name;
        }

        public Employee(string _first, string _last) {
            name=_first+" "+_last;
        }

        private string name="";
    }

    public class Personnel {
        public static void Main(){
            Employee bob=new Employee("Jim", "Wilson"); // 2 arg c'tor
        }
    }
}

In the preceding code, this would cause a compile error:

Employee jim=new Employee();

This is the error message:

overloading.cs(20,26): error CS1501: No overload for method 'Employee' takes '0' arguments

Why? Although the statement is syntactically correct, the Employee class has lost the default constructor. Adding a custom constructor to a class removes the default constructor. If desired, also add a custom parameterless constructor to preserve that behavior.

A constructor can call another constructor using the this reference, which is useful for reducing redundant code. A constructor cannot call another constructor in the method body. Instead, constructors call other constructors using the colon syntax, which is affixed to the constructor header. This syntax can be used only with constructors and not available with normal member functions. In this code, all constructors delegate to the one argument constructor:

class Employee {

    public Employee()
        : this(""){
    }

    public Employee(string _name) {
        name=_name;
    }

    public Employee(string _first, string _last)
        : this(_first+" "+_last){
    }

    public void GetName() {
        Console.WriteLine(name);
    }

    private string name="";
}

Constructors can also be static. Create a static constructor to initialize static fields. The static constructor is invoked before the class is accessed in any manner. There are limitations to static constructors, as follows:

  • Static constructors cannot be called explicitly.

  • Accessibility defaults to static, which cannot be set explicitly.

  • Static constructors are parameterless.

  • Static constructors cannot be overloaded.

  • Static constructors are not inheritable.

  • The return type cannot set explicitly. Constructors always return void.

As mentioned, static constructors are called before a static member or an instance of the classes is accessed. Static constructors are not called explicitly with the new statement. In the following code, ZClass has a static constructor. The static constructor is stubbed to output a message. In Main, the time is displayed, execution is paused for about five seconds, and then the ZClass is accessed. ZClass.GetField is called to access the class and trigger the static constructor. The static constructor is called immediately before the access, which displays the time. It is about five seconds later.

using System;
using System.Threading;

namespace Donis.CSharpBook{

    public class ZClass{

        static private int fielda;
        static ZClass() {
            Console.WriteLine(DateTime.Now.ToLongTimeString());
             fielda=42;
        }

        static public void GetField() {
            Console.WriteLine(fielda);
        }
    }

    public class Starter{
        public static void Main(){
            Console.WriteLine(DateTime.Now.ToLongTimeString());
            Thread.Sleep(5000);
            ZClass.GetField();
        }
    }
}

Singleton Singletons provide an excellent example of private and static constructors. A singleton is an object that appears once in the problem domain. Singletons are limited to one instance, but that instance is required. This requirement is enforced in the implementation of the singleton. A complete explanation of the singleton pattern is found at http://www.microsoft.com/patterns.

The singleton presented in this chapter has two constructors. The private constructor means that an instance cannot be created outside the class. That would require calling the constructor publicly. However, an instance can be created inside the class. The single instance of the class is exposed as a static member, which is initialized in the static constructor. The instance members of the class are accessible through the static instance. Because the static constructor is called automatically, one instance—the singleton—always exists.

A chess game is played with a single board—no more or less. This is the singleton for a chess board:

using System;

namespace Donis.CSharpBook{

    public class Chessboard {
        private Chessboard() {
        }

        static Chessboard() {
            board=new Chessboard();
            board.start=DateTime.Now.ToShortTimeString();
        }

        public static Chessboard board=null;
        public string player1;
        public string player2;
        public string start;
    }

    public class Game{
        public static void Main(){
            Chessboard game=Chessboard.board;
            game.player1="Sally";
            game.player2="Bob";
            Console.WriteLine("{0} played {1} at {2}",
                game.player1, game.player2,
        game.start);
        }
    }
}

In Main, game is an alias for the ChessBoard.board singleton. The local variable is not another instance of Chessboard. The alias is simply a convenience. I preferred game.player1 to Chess-Board.board.player1.

Destructors

An application can clean up unmanaged resources of an object in the destructor method. Destructors are not directly callable and called by the run time prior to garbage collection removing the object from the managed heap. Garbage collection is nondeterministic. A destructor is invoked at an undetermined moment in the future. Like a constructor, the destructor has the same as the class, except a destructor is prefixed with a tilde (~). The destructor is called by the Finalize method, which is inherited from System.Object. In C#, the Finalize method cannot be used directly. You must use a destructor for the cleanup of object resources. For deterministic garbage collection, inherit the IDisposable interface and implement IDisposable.Dispose. Call dispose to relinquish managed or unmanaged resources associated with the object. Implementing a disposable pattern is one of the topics found in Chapter 16.

Destructors differ from other methods in the following ways:

  • Destructors cannot be overloaded.

  • Destructors are parameterless.

  • Destructors are not inherited.

  • Accessibility cannot be applied to destructors.

  • Extern is the sole destructor modifier.

  • The return type cannot be set explicitly. Destructors return void.

Here is the syntax of a destructor.

Destructor syntax:

  • modifier ~typename()

Destructors have performance and efficiency implications. Understand the ramifications of destructors before inserting a destructor in a class. These topics are also covered in Chapter 16.

The WriteToFile class is a wrapper for a StreamWriter object. The constructor initializes the internal object. The Dispose method closes the file resource. The destructor delegates to the Dispose method. The Main method tests the class.

using System;
using System.IO;

namespace Donis.CSharpBook{

    public class WriteToFile: IDisposable {

        public WriteToFile(string _file, string _text) {
            file=new StreamWriter(_file, true);
            text=_text;
        }

        public void WriteText() {
            file.WriteLine(text);
        }

        public void Dispose() {
            file.Close();
        }

        ~WriteToFile() {
            Dispose();
        }

        private StreamWriter file;
        private string text;
    }

    public class Writer{
        public static void Main(){
            WriteToFile sample=new WriteToFile("sample.txt", "My text file");
            sample.WriteText();
            sample.Dispose();
        }
    }
}

Properties

Properties are get and set methods exposed as properties. Public fields do not adhere to encapsulation. Private fields are good policy where the fields are exposed through accessor methods. A property combines the convenience of a public field and the safety of access methods. For clients of the public interface of a class, properties are indistinguishable from fields. The client appears to have direct access to the state of the class or object. There several benefits of properties:

  • Properties are safer than public fields. Use the set method to validate the data.

  • Properties can be computed, which adds flexibility. Fields represent a data store and never a calculation.

  • Lazy initialization can be used with properties. Some data is expensive to obtain. A large dataset is an ideal example. Deferring that cost until necessary is a benefit.

  • Write- and read-only properties are supported. Public fields are fully accessible.

  • Properties are valid members of an interface. As part of the interface contract, a class can be required to publish a property. Interfaces do not include fields.

A property is a get and set method. Neither method is called directly. Depending on the context of the property, the correct method is called implicitly. As a left-value of an assignment, the set method of the property is called. When used as a right-value, the get method of the property is called. When a property is the target of an assignment, the set method of the property is invoked. The get method is called if a property is used within an expression. Here is the syntax of a property:

Property syntax:

  • accessibility modifier type propertyname { attributes get {getbody} attributes set {setbody} }

The accessibility and any modifier included in the property header apply to both the get and set method. However, the set or get method can override the defaults of the property. Type is the underlying type of the property. Neither the get nor set method has a return type or parameter list. Both are inferred. The get method returns the property type and has no parameters. The set method returns void and has a single parameter, whi