Classes

Summary

Class Declarations

A class declaration is a type-declaration and takes the following form:

[attributes] [modifiers] class identifier [:base-list]
{
    /* Class body */
} [;]

For example:

[Synchronized] public class MyClass : MyBaseClass, Interface1, Interface2, Interface3
{
    /* Class body */
};

[modifiers] can be either new, public, protected, internal, private, sealed, static (new in C# 2.0) or abstract. The new modifier is only permitted on nested classes and it specifies that the class hides an inherited member by the same name. It is a compile-time error for the new keyword to appear in a class declaration. The remaining modifiers control the accessibility of the class. Depending on the context in which the class was defined, some of these remaining access modifiers may not be permitted. For example, a class declared within a namespace must be wither public or internal. Finally, The [base-list] is usually zero or one base class and zero or more interfaces.

Abstract Classes

The abstract modifier is used to indicate that the class is incomplete and that it is intended to be used as a base-class only. Note some of the characteristics of abstract classes:

public abstract class AbstractClass1
{
    public abstract void foo();
    public void bar() { Trace.WriteLine("AbstractClass1.bar{}"); }
}

public class ConcreteClass1 : AbstractClass1
{
    // Must provide implementation of all abstract members of an abstract class.
    public override void foo()
    {
        Trace.WriteLine("ConcreteClass1.foo()");
    }
}

private void button1_Click(object sender, System.EventArgs e)
{
    // Instantiating abstract classes
    ConcreteClass1 ob1 = new ConcreteClass1();
    ob1.foo();             // ConcreteClass1.foo()
    ob1.bar();             // AbstractClass.bar()
}

An abstract class is closely related to an interface in the sense that both declare a 'contract' that must be adhered to by clients.  See interface for more info.

Sealed Classes

A sealed class is that a class that cannot be used as a base class. In other words, it cannot be derived from; it is at the end of the derivation chain. The sealed keyword is mostly used to prevent unintended derivation, but it also enables certain runtime optimizations; for example, because a sealed class is known to never have any derived classes, it is possible to transform virtual function member invocations on sealed classes into non-virtual invocations (Recall that virtual member method invocation involves looking up and invoking an entry from a vtable - which incurs an extra level of indirection.)

Base Classes

Note the following points about base classes:

// The direct base class is assumed to be object
public class A { ... }

// The direct base class is A
public class B : A { ... }

Static Classes (C# 2.0)

A class can be declared static indicating that it only contains static member. It is not possible to create instances of static classes with the new keyword. Static classes are loaded automatically by the .NET Framework when the program or the namespace containing the class is loaded. In fact a static class is similar to a normal class with static members and private constructors.

Static classes should be used to associate to contain methods that are not associated with a particular object. Utility classes are often static as they do not require an object instance to perform their work. The main features of static classes are:

  1. They only contain static members.

  2. They cannot be instantiated.

  3. They are sealed and cannot be inherited.

  4. They cannot contain instance constructors (although they can contains static constructors to assign values or set up some static state).

For example, a class to display computer info does not really need to be attached to a specific instance of the class. You can declare such a class static as follows:

 static class ComputerInfo
{
    // Data members
    static private string strComputerName;
    private int nCPUCount;          // error CS0708: 'NewFeatures.ComputerInfo.nCPUCount': cannot declare instance members in a static class

    // Constructors
    static ComputerInfo()
    { /* Initialize static members to a known state */ }

    public ComputerInfo() { }      // error CS0710: Static classes cannot have instance constructors

    // Methods
    static public string GetComputerName()
    { /* Your implementation */ }

    static public int GetTickCount()
    { /* Your implementation */ }

    static public string[] GetDrives()
    { /* Your implementation */ }

    public int GetCPUCount()      // error CS0708: 'GetCPUCount': cannot declare instance members in a static class
    { /* Your implementation */ }
}

Class Members

C# classes can contain the following categories of members:

public class Vehicle : ICloneable
{
    // Fields
    private int     nMoel;            // field
    private string  strManufacturer   // field

    // Constants
    const int WEIGHT = 1000; 

    // Constructors
    public Vehicle()             { ... }
    public Vehicle( int nModel ) { ... }

    // Methods
    void Start() { ... }

    // Properties
    int Mileage
    {
        get { ... }
        set { ... }
    }

    // ...
}

Inheritance

Inheritance means that a class implicitly contains all members of its direct base class, except for instance constructors, destructors, and static constructors of the base class.

A class is permitted to declare a member with the same name or signature as an inherited member. When this occurs, the derived class member is said to hide the base class member. Derived class can hide inherited members by declaring new members with the same name or signature. Note that hiding a member does not remove it - hiding only makes the hidden member inaccessible form the derived class.

Because only public inheritance is allowed in C#, the is-a relation ship always holds true. Therefore, if class Student derived from class Person, then public inheritance means that Student is-a Person, and hence any code that expects an instance of Student can be passed an instance of Person, because Student is-a Person.  This also implies that an instance of Student can always be treated as instance of Person.

Virtual methods, properties and indexers enable polymorphic behavior wherein actions performed by the overridden versions of these virtual members will vary depending on the run-time type of the instance through which the member function is invoked.

Note that unlike C++, C# classes,

Consider the following examples:

/* No inheritance */
public class MyClass { }

/* Single inheritance */
public class MyClass : SomeBaseClass { }

/* No inheritance, but implements two interfaces */
public class MyClass : IEnumerable, ICloneable { }

/* Single inheritance, and implements two interfaces */
public class MyClass : SomeBaseClass, IEnumerable, ICloneable { }

Access Modifiers

Class members may have any of five possible kinds of declared accessibility:

Static and Instance Members

Class members (fields, methods, indexers, etc.) can be either static members or instance members. It is useful to think of static members as belonging to the class itself, and instance members as belonging to objects (instances of classes).  Static members are declared using the static keyword. Static members have the following characteristics:

Nested Types

A nested type is a type declared within a class or a struct (a non-nested type is one that is declared within a compilation unit or namespace.)

// class A is a non-nested class
public class A
{
    ...

    // class B is a nested class. Default accessibility is private
    class B
    {
        ...
    }
}

A nested type declared with a class can have any of the five forms of declared accessibility: public, protected internalprotected, internal, private, and like other class members defaults to private. Recall that non-nested types can have public or internal declared accessibility only. However, a nested type declared within a struct can have only public, internal, or private declared accessibility, and like other struct members defaults to private.

A nested type may also use the new modifier to hide a base member of the containing class:

public class Base
{
    public void X() { Trace.WriteLine("Base.foo()"); }
}

public class Derived : Base
{
    new public class X
    {
        public void F() { Trace.WriteLine("MyNested.foo()"); }
    }
}

Derived d    = new Derived();
Derived.X dx = new Derived.X();
dx.F();

Also note that a nested type and its containing type do not have a special relationship with respect to this. Specifically, this within a nested type cannot be used to refer to an instance of the containing type. If the nested type needs to access instance members of its containing type, it can do so through by having a constructor or other property that takes the this variable of the containing class.

Constants

A constant is a class member that represents a constant value; i.e., a value that can be computed at compile-time. A constant declaration takes the following form:

[attributes] [modifiers] type identifier = constant-expression

For example:

public class A
{
    public const double PI = 3.14157, ZERO = 0.0;
    public const double ONE = ZERO + 1;
    ...
}

Note that a constant with a class is semantically equivalent to a static member in the sense that each class instance will get the same value for that constant. However, a constant neither requires nor allows the static keyword to be supplied.

A constant-expression is an expression that can be fully computed at compile-time. Note that because the new operator is not allowed in a constant-expression, and because the only way to create a non-null value of a reference type (other than string) is to use new, then the only possible values for constants of reference types (other than string) is null.

Constants are allowed to depend on other constants within the same program as long as the dependencies are not of a circular nature:

public class A
{
    public const int X = 1;
}

public class B
{
    public const int Y = A.X + 1;
}

Fields

A field is a class member that represents a variable associated with an object of a class. Declaring a field takes the following form:

[attributes] [modifiers] type identifier = variable-initializer

Where [modifiers] can be:

Note that initializing a field is optional:

public class A
{
    private int     m_nIndex;                    // m_nIndex defaults to 0
    private bool    m_bStatus;                   // m_bStatus defaults to false
    private double  m_dX = 1.0, m_dY = 2.0;
    private static  int m_nX = -1, m_nY = -1;

    private static bool m_bStaticBool;           // m_bStaticBool defaults to false
    private static bool m_bInstanceBool = true;  // m_bInstanceBool defaults to false

    private static int x = y    // Possible to initialize a static field with another static field that has not yet been initialized
    private static int y;       // Result of x = y is undefined until you explicitly assign a value
}

Static and instance fields 

A static field is not part of a specific instance; instead it identifies exactly one storage location no matter how many instances of the containing class are created. An instance field (not qualified with static) on the other hand belongs to an instance; there is a separate memory location in each class instance. See Static and Instance Members for more details.

Readonly fields

Fields declared with readonly keyword are ... read only! Direct assignment to readonly fields can only occur as follows:

Note that using an instance constructor (for read-only instance fields) or a static constructor (for read-only static fields) allows you to pass the read-only field as an out or ref parameter.:

public class C
{
    // readonly fields initialized during declaration
    private readonly        int N = 10;
    private static readonly int M = -10;

    // readonly fields initialized in constructors
    private readonly int O;
    private static readonly int P;

    public C( int o )
    {
        O = o;
    }

    static C()
    {
        P = 10;
    }
}

readonly instance fields are specific to each instance. However, static readonly fields are not specific to any instance as their value will be shared by all instances. static readonly fields are semantically similar to constants, so when do you use which?

In the example below, Brush_10, Black, and White cannot be declared as constants because their values cannot be computed at compile-time:

public class MyColor
{
    /* Data members */

    // The appropriate way to define 'constants' for a class type
    private static readonly MyBrush Brush_10 = new MyBrush( 10 );             // OK
    private static readonly MyColor Black    = new MyColor( 0, 0, 0 );        // OK
    private static readonly MyColor White    = new MyColor( 255, 255, 255 );  // OK

    // A 'const' declaration does not allow the new keyword or a class type to be used
    private const MyBrush Brush_1 = new MyBrush( 1 );         // The expression being assigned to 'MyColor.Brush_1' must be constant
    private const MyColor Red     = new MyColor( 255, 0, 0 ); // The expression being assigned to 'MyColor.Red' must be constant
    private const MyColor Green   = null;                     // OK
    //
    byte red, green, blue;

    // Constructors
    public MyColor( byte r, byte g, byte b )
    {
        red   = r;
        green = g;
        blue  = b;
    }
}

Volatile fields

A volatile field is one that be modified in the program by something such as the operating system, hardware, or a concurrently executing thread. Access to non-volatile fields in multithreaded programs can lead to unexpected results due to optimization techniques performed by the compiler. For volatile fields such optimizations are restricted:

These instructions ensure that all threads will observe volatile writes performed by any other thread in the order they were performed.

Methods

A class method is a class member that represents a computation action that can be performed by an object or class. Declaring a class member takes the following form:

[attributes] [modifiers] return-type identifier (formal-parameter-list)

Where [modifiers] can be:

For abstract and extern modifiers, the method body should consist of a semi-colon. For all others, the method body consist of a block which specifies the statements to be executed. The signature of a method consists of the method's name and type and kind (value, reference, output) of each of its format parameters.  Note that the signature of a method does not specifically include the return type not does it include the params modifier that may be specified for the right-most parameters.

Method Parameters

The format-parameters-list takes the form of one of the following:

where fixed parameters takes the form of :

[attributes] [ref|out] type identifier

and parameter array takes the form of:

[attributes] params array-type identifier

There are four kinds of formal parameters:

Value Parameters

A parameter declared with no modifiers is a value parameter. A value parameter corresponds to a variable local to the function that gets its initial value for the corresponding argument supplied in the method invocation. Any changes to value parameters affect the local storage area only and have no effect on the actual argument given to the method invocation. In other words, changes to value parameters within the function body are not reflected back to the caller.

Reference Parameters

A parameter declared with the ref keyword is a reference parameter. Unlike a value parameters, a reference parameter does not create a new local storage locationInstead, a reference parameter has the same storage location as the variable given as the argument in the method invocation. This means that any changes to the parameter in the method will be reflected back in the caller.

Variables representing reference parameters must always be assigned before using them in a method invocation. The method invocation must also supply the ref keyword for any variable representing a reference parameter. Also note that an overload will result if two methods differ only in their use of ref.

class MethodParameterTest
{
    public void Swap( ref int x, ref int y)
    {
        int nTemp = x;
        x = y;
        y = nTemp;
    }
}

MethodParameterTest obMPT = new MethodParameterTest();
int x = 10;
int y = 20;
obMPT.Swap( ref x, ref y );        // x = 20, y = 10

Out Parameters

A parameter declared with the out keyword is output parameter. Similar to a reference parameters, an output parameter has the same storage location as the variable given as the argument in the method invocation. This means that any changes to the parameter in the method will be reflected back in the caller. out parameters are particularly useful when you want a method to have multiple return values, as any given method can have zero, one, or more out parameters.

Variables representing out parameters do not need to be assigned before using them in a method invocation. The method invocation must also supply the out keyword for any variable representing an output parameter. Within the method, an output parameter is considered unassigned and must be assigned before its value can be used. Also note that an overload will result if two methods differ only in their use of out.

class MethodParameterTest
{
    public void GetTwoRandomNumbers( out int x, out int y)
    {
        x = 100;        // out parameters must be assigned a value before function returns
        y = 200;        // out parameters must be assigned a value before function returns
    }
}

int x;
int y;
obMPT.GetTwoRandomNumbers( out x, out y );    // x = 100; y = 200

Parameter Arrays

A parameter declared with the params keyword is a parameter array. Parameter arrays are used to pass a variable number of parameters, similar to the ... syntax used in a C++ function. If a format parameter list includes a parameter array, it must the right-most parameter and it must be a single-dimensional array. You cannot combine the out or ref modifiers with the params modifier. The following example illustrates how a declare a method with a params parameter and how to call it:

class MethodParameterTest
{
    public void PassParamterArray( float fNumber, params string[] aParams )
    {
        foreach(string s in aParams)
        {
            Trace.WriteLine( s );
        }
    }
}

MethodParameterTest obMPT = new MethodParameterTest();

// Method 1: pass an array. Here the argument for the array is a single expression of a type that can be
// implicitly converted to the parameter array type

string[] aStrings = {"One", "Two", "Three" };
obMPT.PassParamterArray( 12.34F, aStrings );

// Method 2:Pass a variable number of arguments. Here the compiler will create an array whose type and length
// corresponding to the given arguments., initializes the array with the given argument values and uses the newly
// created array instance as the actual argument.

obMPT.PassParamterArray( 56.78F, "One", "Two", "Three", "Four" );    // array length will be 4
obMPT.PassParamterArray( 90.00F );                                   // array length will be 0

Passing Method Parameters

Recall the following:

Therefore, there are four cases for passing method parameters in C#':

Also recall the following:

Now note the following:

The following table summarizes the points above::

Case Keyword Note
Passing a value type by- value none
  • A new local storage location is created in the method, hence a copy of the data is used. 
  • Changes to this value in the method are not seen by the caller.
Passing a value type by- reference out or ref
  • The function uses the same storage location as used by the original caller.
  • All changes to the variable will be seen by the caller.
Passing a reference type by- value none
  • A new local storage location is created in the method, hence a copy of the reference is used. 
  • Changes in the method to the data pointed-to by the reference variable are seen by the caller.
  • Changes to the reference itself (i.e., re-assigning or changing the memory location) are not reflected back to the caller.
Passing a reference type by- reference out or ref
  • The function uses the same storage location as used by the original caller.
  • All changes to the variable will be seen by the caller. This means that the underlying data as well as the memory location itself can be re-assigned/changed.

Examples:

public class PassingParemters
{
    // Changes not seen by caller
    public void PassValueTypeByValue( int n )
    {
        n =10;
    }

    // Changes seen by caller
    public void PassValueTypeByReference( ref int n )
    {
        n = 10;
    }

    // Changes not seen by caller
    public void PassReferenceTypeByValue( string str )
    {
        str = "World";
    }

    // Changes seen by caller
    public void PassReferenceTypeByReference( ref string str )
    {
        str = "World";
    }

    // Changes not seen by caller
    public void PassReferenceTypeByValue( ArrayList al )
    {
        al = new ArrayList();
        al.Add( "hello");
        al.Add( "world" );
    }

    // Changes seen by caller
    public void PassReferenceTypeByReference( out ArrayList al )
    {
        al = new ArrayList();
        al.Add( "hello");
        al.Add( "world" );
    }
}

// Passing parameters test
PassingParemters obPP = new PassingParemters();


// Passing a value-type by-value
int n = 0;
obPP.PassValueTypeByValue( n );                   // n = 0

// Passing a value-type by-reference
int m = 0;
obPP.PassValueTypeByReference( ref m );           // m = 10

// Passing a reference-type by-value
string str1 = "Hello";
obPP.PassReferenceTypeByValue( str1 );            // str1 = "Hello"

// Passing a reference-type by-reference
string str2 = "Hello";
obPP.PassReferenceTypeByReference( ref str2 );    // str2 = "World"

// Passing a reference-type by-value

ArrayList al1 = null;
obPP.PassReferenceTypeByValue( al1 );             // al1 remains null because it is passed by value

// Passing a reference-type by-reference
ArrayList al2;
obPP.PassReferenceTypeByReference( out al2 );     // al2 is properly allocated with a count of 2 items

Static and Instance Methods

When a method declaration includes the static keyword, the method is said to be a static method.  When no static modifier is present, the method is said to be an instance method.

Note that a static method does not operate on an instance and hence it is a compile-time error to use the this keyword inside a static function. However, an instance method operates on a specific instance and accessing the this keyword is allowed.

Virtual Methods

A virtual method is a method declared with the virtual keyword. 

The implementation of a non-virtual method (a method without the virtual keyword) is invariant: the implementation will be the same whether the method is invoked on an instance of the class on which it is declared, or on an instance of a derived class. In contrast, the implementation of a virtual method can be superseded in derived classes. The process of superseding the implementation of an inherited virtual method is known as overriding

Note the following important differences between virtual and non-virtual method invocation:

To help differentiate between compile-time vs. run-time type consider the following:

public class A
{
    void A1() { ... }
    virtual void A2() { ... }
}

public class B : A
{
    override void A2() { ... }
}

A obA = new A();        // obA has a compile-time type of A and a run-time type of A
B obB = new A();        // obB has a compile-time type of B and a run-time type of A 

obA.A1();              // Invokes A.A1()
obA.A2();              // Invokes A.A2()

obB.A1();              // Invokes A.A1()
obB.A2();              // Invokes B.A2()

When a method declaration contains the override keyword, the method is said to be overridden. Use the override keyword to override (supercede or specialize) the implementation of an inherited virtual method. Note that while a virtual method declaration introduces a new method, an override method declaration specializes an existing inherited virtual method by providing a new implementation. An override declaration can access the overridden base method using the base keyword:

void override foo()
{
    // Call the overridden base method.
    base.foo();

    ...
}

Note that only by introducing the override keyword can a method override another method. In all other cases, a method with the same signature as an inherited method simply hides the inherited method. In the example below, Derived1.f() hides Base1.f():

public class Base1
{
    public virtual void f()
    {
        Trace.WriteLine("Base1.f()");
    }
}

public class Derived1 : Base1
{
    // Hides Base1.f() because 'override' was not specified
    public virtual void f()
    {
        Trace.WriteLine("Derived1.f()");
    }
}

public class Derived2 : Derived1
{
    // Overrides Derived1.f()
    public override void f() 
    {
        Trace.WriteLine("Derived2.f()"); 

        // Call base method
        base.f();
    }
}

// Calls Base1.f() because Derived1 did not override Base1.f()
Base1 ob1 = new Derived1(); 
ob1.f();

// Calls Base1.f() because Derived2 did not override Base1.f(). Derived2 overrode Derived1.f()
Base1 ob2 = new Derived2();
ob2.f();

// Calls Derived2.f() because Derived2 overrides Derived1().f()
Derived1 ob3 = new Derived2();
ob3.f();

// Calls Derived2.f() because Derived2 overrides Derived1().f()
Derived2 ob4 = new Derived2();
ob4.f();

Note also that the sealed keyword can be used when overriding an inherited virtual method to prevent a derived class from further overriding the method. Note the following code which extends the pervious example by marking Derived2.f() as a sealed override:

public class Derived2 : Derived1
{
    public sealed override void f() 
    {
        Trace.WriteLine("Derived2.f()"); 

        // Call base method
        base.f();
    }
}

public class Derived3 : Derived2
{
    // error CS0239: 'Primer.Derived3.f()' : cannot override inherited member 'Primer.Derived2.f()' because it is sealed
    public override void f()
    {
        Trace.WriteLine("Derived3.f()"); 

        // Call base method
        base.f();
    }
}

Derived3 attempts to further specialize f() which was inherited from Derived2. However, Derived2 declared f() as a sealed override, and hence Derived3 attempts to override a sealed method.

Abstract Methods

An abstract method is a method declared with the abstract modifier. An abstract method is an implicitly virtual method except that it cannot have the virtual modifier. An abstract method declaration differs from a virtual method declaration in the following areas:

Therefore, an abstract method declaration introduces a new virtual function and forces its derived classes to provide their own implementation. To use abstract methods, they must be declared in abstract classes and overridden in derived classes:

public abstract class Shape
{
    public abstract void Paint();  // Abstract methods have no body and must be contained in abstract classes
}

public class Rectangle : Shape
{
    public override void Paint()
    {
        Trace.WriteLine( "Rectangle.Paint()" );
    }
}

Shape obShape = new Rectangle();
obShape.Paint();                    // Calls Rectangle.Paint()

The abstract keyword can also be used on a virtual method to force the re-implementation of the method in derived classes. This can be though as a way to branch the virtual method into a new implementation.  In the following example, class declares A virtual method, class B overrides this method with an abstract method, and class C then overrides the abstract method to provide its own implementation.  However, class D overrides the method in class A. This results in two branches:

public class A
{
    public virtual void f() { Trace.WriteLine("A.f()"); }
}

abstract public class B : A
{
    public abstract override void f();                      // Branches implementation of f()
}

public class C : B
{
    public override void f() { Trace.WriteLine("C.f()"); }    // overrides the abstract B.f()
}

public class D : A
{
    public override void f() { Trace.WriteLine("D.f()"); }    // overrides the virtual A.f()
}

A obC = new C();
obC.f();                // Calls C.f()

A obD = new D();
obD.f();                // Calls D.f()

External Methods

An external method in C# is a method declared with the extern modifier and implemented externally perhaps in another language like C++ or VB.NET. Therefore, an external method declared in C# has no implementation body.  The extern modifier is typically used with the DllImport attribute allowing external methods to be implemented in DLLs (note that when a method declaration includes a DllImport attribute, the method declaration must also include a static modifier):

[DllImport("kernel32", setLastError=true)]
static extern bool SetCurrentDirectory( string strCurrent );    // No implementation body (just like abstract methods)

Properties

A property is a class member that provides access to a characteristic of an object or class. Examples of properties include size of a window, ID of a student, length of a string and so on. Properties are a natural extension of fields in that they provide a simple way for getting/setting property values, and at the same time, provide for a mechanism that associates actions/computations when getting/setting values. 

Declaring a property takes the following form:

[attributes] [modifiers] type identifier { accessor-declarations }

Where [modifiers] can be:

Even though the syntax for accessing a property is the same as accessing a variable, a property does not denote a variable. Therefore, it has no memory location and parameter modifiers out and ref cannot be used to pass properties as function arguments.

Similar to fields and methods, a static property includes the static modifier and is not associated with a specific instance, while an instance property does not include the static modifier and is associated with a specific object instance.

Accessors

The accessor-declarations consist of a get-accessor-declaration only for read-only access, or a set-accessor declaration only for write-only access, or both for read and write access. The following example shows a very basic and simple property with both a get-accessor-declaration and set-accessor declaration:

public class Student
{
    // Fields
    private string m_strName;

    ...

    // Properties
    string Name
    {
        get { return m_strName; }
        set { m_strName = value; }    // 'value' is the implicit parameter of the set accessor
    }
}

Note that for extern and abstract properties, each of its access-declarations should consist of a semi-colon with no body

// Abstract and external property have not get / set body
abstract int Color { get; set; }
extern   int Size  { get; set; }

When a derived class declares a property by the same name as an inherited property, the derived property hides the inherited property with respect to both reading and writing:

public class Base
{
    int ID
    {
        get { ... }
    }
}

public class Derived : Base
{
    new int ID
    {
        set { ... }
    }
}

Derived obD = new Derived();
int nID = obD.ID;        // Compile-time error because property ID is write-only

It is considered as a bad programming practice for get accessors to have observable side effects. This is because invoking a get accessor is conceptually equivalent to reading a value:

public class Shape
{
    private int m_nX;

    public int X
    {
         return m_nX++;        // Bad programming style. Should not increment
    }
}

This "no-side effect" convention does not mean that the get accessor should always be written to return values stored in fields. In fact, get accessors often compute the value of a property by invoking multiple methods and accessing multiple fields. For example, a TransactionCount property on a data access object can be used to query the database for number of currently active transactions!

Properties are often used to delay initialization of a field until the moment it is first used:

public class Console
{
    private static TextReader reader;

    public static TextReader In
    {
        if (reader == null)
            reader = new StreamReader( ... );

        return reader;
    }

}

// Main application
string strLine = Console.In.ReadLine();    // TextReader created if not already created by a previous call

Finally, note that exposing fields through properties is not any less efficient than exposing fields directly. In fact, when a property is non-virtual and contains a small amount of code, the execution environment may replace calls to accessors with the actual code of the accessors. This is known as in-lining and makes property access as efficient as field access.

Asymmetric Accessor Accessibility (C# 2.0)

Prior to C# 2.0, by default the get and set accessors have the same visibility or access level. In other words, a set property cannot be protected for example while the get property is public. However, sometimes it is quite useful if access is restricted to only of them, typically restricting access to the set accessor while keeping the get accessor public. For example:

// Asymmetric property
public string Name
{
    get                 // The get accessor gets the accessibility level of the property itself (public)
    {
        return strName;
    }

    protected set     // The set accessor is explicitly restricted by another access modifier
    {
        strName = value;
    }
}

Note the following restrictions on access modifiers on accessors:

  1. As known from interfaces, you cannot apply access modifiers on interface declaration or an explicit interface member implementation.

  2. You can use asymmetric accessors only if the property or indexer has both set and get accessors. In this case, the modifier is allowed on only one of the two accessors.

  3. If the property or indexer is an override, the accessor modifier must match the accessor of the overridden accessor, if any.

  4. The accessibility level on the accessor must be more restrictive than the accessibility level on the property or indexer itself.

The following code illustrates some of the points above:

class Accessors
{
    private string strName;
    private int nID;
    protected int nQuantity;

    // Assymetic property
    public string Name
    {
        // The get accessor gets the accessibility level of the property itself (public).
        // If you give the get access an accessibility level, you get error CS0274: Cannot
        // specify accessibility modifiers for both accessors of the property or indexer
        // 'NewFeatures.Accessors.Name'

        get
        {
            return strName;
        }

        // The set accessor is explicitly restricted by another access modifier
        protected set
        {
            strName = value;
        }
    }

    public int ID
    {
        get
        {
            return nID;
        }

        // If accessibility level is made public, you get error CS0273: The accessibility
        // modifier of the 'NewFeatures.Accessors.ID.set' accessor must be more restrictive
        // than the property or indexer 'NewFeatures.Accessors.ID'

        private set
        {
            nID = value;
        }
    }

    // Read notes for the overridden Quantity property
    public virtual int Quantity
    {
        // Because the set accessor is assigned an accessibility level, no accessibility
        // level can be specified for the get accessor

        get
        {
            return nQuantity;
        }

        // Notice the accessor accessibility level
        protected set
        {
            nQuantity = value;
        }
    }
}

class DerivedAccessor : Accessors
{
    public override int Quantity
    {
        // Still cannot use an access modifier here
        get
        {
            return nQuantity;
        }

        // Must use the same accessibility level as in the virtual property
        protected set
        {
            nQuantity = value;
        }
    }
}

Example for implementing an interface with with asymmetric access modifiers:

interface IMyInterface
{
    // As per interface declaration rules, no access modifier can be applied to the Name property.
    // Also no access modifier can be applied to the get accessor.

    string Name { get; }
}

public class MyClass : IMyInterface
{
    private string strName;

    // Interface implementation. Name property must be public because it is an interface member
   
public string Name
    {
        // Cannot use an access modifier here because this is an interface implementation
        get
        {
            return strName;
        }

        // Accaccess modifier is allowed here because interface property Name did not specify
        // a set accessor

        protected set
        {
            strName = value;
        }
    }
}

Another example which illustrates how a property in a derived class can be hidden when using a more restrictive access modifier:

// A class with two normal public properties
public class BaseProperties
{
    // Member variables
   
private string name = "BaseProperties_name";
    private int id = -1;

    // Properties
    public string Name
    {
        get { return name; }
        set { name = value;}
    }

    public int ID
    {
        get { return id; }
        set { id = value;}
    }
}

// Specialzes BaseProperites by modifying the inherited properties
public class DerivedProperties : BaseProperties
{
    // Member variables
    private string name = "DerivedProperties_name";
    private int id = 100;

    // Properties

    // Hides the inherited Name property
    public new string Name
    {
        get { return name; }
        set { name = value; }
    }

    // Hides the inherited ID property and makes it inaccessible in the main program
    // In Pre .Net 2.0, supplying private on the property ID would have given
    // a compile time error

    new private int ID
    {
        get { return id; }
        set { id = value; }
    }
}

 private static void TestAsymmetricProperties()
{
    BaseProperties obB = new BaseProperties();
    obB.Name = "X";         // Accesses base class instance
    obB.ID = 1;             // Accesses base class instance

    DerivedProperties obD = new DerivedProperties();
    obD.Name = "Y";         // Accesses derived class instance
    obD.ID = 10;             // Accesses base class instance
}

Output:

   

Virtual, abstract, override and sealed properties

Note the following:

For example,

public abstract class A
{
    // Data members
    int m_nY, m_nZ;

    // Properties
    public abstract int X
    {
        get;
    }

    public virtual int Y
    {
        set { m_nY = value; }
    }

    public virtual int Z
    {
        get { return m_nZ; }
        set { m_nZ = value; }
    }
}

public class B : A
{
    // Overridden properties
    public override int X
    {
        get { /* provide another implementation */ }
        set { ... }        // error CS0546: 'B.X.set': cannot override because 'A.X' does not have an overridable set accessor
    }

    public override int Y
    {
        get { ... }        // error CS0545: 'B.Y.get': cannot override because 'A.Y' does not have an overridable get accessor
        set { /* provide another implementation */ }        
    }

    public override int Z
    {
        get { /* provide another implementation */ }
        set { /* provide another implementation */ }
    }
}

Events

An event is a class member that allows an object or class to provide notifications. Declaring an event takes the following form:

[attributes] [modifiers] event type variable-declarators;
[attributes] [modifiers] event type member-name { event-accessor-declarations }

Where [modifiers] can be:

type is the delegate to which you want to associate this event and access-declarators are used to add and remove event handlers in client code. Note that just like methods, properties, and fields, an event can either be a static event (not associated with a specific instance), or it can be an instance event (associated with a specific instance).

When working with events, you typically work with two classes and three components:

The event keyword allows you to specify a delegate that will be called upon the occurrence of some event in your code. The delegate can have one or more methods associated with it that will be called when the event for that delegate fires. The easiest way to declare an event is to put the event keyword in front of a delegate:

class Broadcaster
{
    public event EventHandler PriceChanged;
}

Code within Broadcaster class has full access to the PriceChanged event and can treat it as a delegate. Code outside Broadcaster can only perform += and -= operations on the PriceChanged event.

Three things happen under the covers when an event like PriceChanged is declared:

  1. The compiler translates the event declaration to the following (more on this later in this section)

    private EventHandler _priceChanged;
    public event EventHandler PriceChanged
    {
        // add and remove keywords denote explicit event accessors
        add { _priceChanged += value; }
        remove { _priceChanged -= value; }
    }
  2. The compiler looks within the Broadcaster class for references to PriceChanged that perform operations other than += or -= and redirects them to the underlying _priceChanged delegate field.

  3. The compiler translates += and -= operations on the event to calls to the event's add and remove accessors.

Standard Event Pattern

There is a standard pattern for writing .NET events:

  1. Derive from System.EventArgs to convey information about the event.
    System.EventArgs class is a base class for conveying information about the event. The subclass typically exposes data as properties or read-only fields.
     

  2. Define a delegate using System.EventHandler<>. This class observes three important rules:

    1. The delegate must have a void return type.

    2. The delegate must accept two arguments. The first argument is an object which indicates the event broadcaster, and the second argument is a subclass of EventArgs (the one defined in step 1 above)

    3. The delegate name must end with EventHandler.

  3. Define an event based on System.EventHandler<> delegate defined in step 2.

  4. Write a protected virtual method that fires the event. The name of the method must match the event name prefixed by On and accept a single argument derived from EventArgs.

Here's a complete example:

class PriceChangedEventArgs : EventArgs
{
    public decimal LastPrice { getprivate set; }
    public decimal NewPrice { getprivate set; }
 
    public PriceChangedEventArgs(decimal lastPrice, decimal newPrice)
    {
        LastPrice = lastPrice;
        NewPrice = newPrice;
    }
}
 
class StockPriceBroadcaster
{
    // Event-related declarations
    public event EventHandler<PriceChangedEventArgs> PriceChanged;
    protected virtual void OnPriceChanged(PriceChangedEventArgs args)
    {
        if (PriceChanged != null)
            PriceChanged(this, args);
    }
 
    private decimal _price;
    public decimal Price
    {
        get { return _price; }
        set
        {
            if (_price == valuereturn;
 
            // Price changed. Fire event
            OnPriceChanged( new PriceChangedEventArgs( _price, value));
            _price = value;
        }
    }
}
public void TestPriceChangedEvent()
{
    var spb = new StockPriceBroadcaster();
    spb.Price = 10.00M;
 
    // Register the price changed event
    spb.PriceChanged += (sender, args) =>
                            {
                                if (Math.Abs( args.NewPrice - args.LastPrice) / args.LastPrice > 0.1M)
                                    Trace.WriteLine("At least 10% price change!");
                            };
 
    // Simulate a price change. The event handler above is called
    spb.Price = 20.00M;
}

Note the use of the generic EventHandler<T> class. The non-generic EventHandler can be used when an event does not carry extra information.

Event Accessors

Event declaration typically omit event-accessor-declarations as in the example above. A class that declares events can include event-accessor-declarations when the class wants to have a private mechanism for storing the list of event handlers. The event-accessor-declarations consist of an add and a remove block used to add / remove handlers associated with the event. The add block specifies statements to execute when adding an event handler, while the remove block specifies statements to execute when removing an event handler. The following example illustrates how to use event accessors. All other code remains the same

class StockPriceBroadcaster2
{
    // Event-related declarations
    private EventHandler<PriceChangedEventArgs> priceChanged;
    public event EventHandler<PriceChangedEventArgs> PriceChanged
    {
        add { priceChanged += value; }
        remove { priceChanged -= value; }
    }
 
    protected virtual void OnPriceChanged(PriceChangedEventArgs args)
    {
        if (priceChanged != null)
            priceChanged(this, args);
    }
 
    private decimal _price;
    public decimal Price
    {
        get { return _price; }
        set
        {
            if (_price == valuereturn;
 
            // Price changed. Fire event
            OnPriceChanged(new PriceChangedEventArgs(_price, value));
            _price = value;
        }
    }
}

With explicit event accessors, you can apply more complex strategies to the storage and access of the underlying delegate. There are three scenarios where this is useful:

  1. When the event accessors are merely relays for another class that is broadcasting the event.

  2. When the class exposes a large number of events, where most of the time very few subscribers exist. In this case, it is better to store the subscriber's delegate instance in a dictionary for better performance.

  3. When explicitly implementing an interface that declares an event.

Indexers

An indexer is a class member that allows an object to be indexed in the same way as an array. An indexer is usually added to a class when the class contains an internal collection and you wish to access this internal collection using array-like syntax. Declaring an indexer takes the following form:

[attributes] [modifiers] type this [parameter-list]

Where [modifiers] can be:

Note that the static modifier is not permitted. The following shows how to declare and use a simple indexer:

public class Grid
{
    // Declare the 2-D array that the indexer of this class will access
    int[,] aGrid = new int[10,20];

    // Declare an indexer to access any element in the 2-dimensional array
    public int this[int nRow, int nColumn]
    {
        get
        {
            return aGrid[nRow, nColumn];
        }
        set
        {
            aGrid[nRow, nColumn] = value;
        }
    }
}

// Create a grid object and set its grid array using the indexers
Grid ob = new Grid();
ob[0,0] = 1;
ob[0,1] = 10;
ob[0,2] = 100;

// Retireve grid values using the indexer
int nVal = ob[0,1];

Note the following points about indexers:

public class Grid
{
    // Declare the 2-D array that the indexer of this class will access
    int[,] aGrid = new int[10,20];

    // Declare an indexer to access any element in the 2-dimensional array
    public virtual int this[int nRow, int nColumn]
    {
        get
        {
            return aGrid[nRow, nColumn];
        }
        set
        {
            aGrid[nRow, nColumn] = value;
        }
    }
}

public class DiagonalGrid : Grid
{
    // If row and column are equal, then use base implementation, else throw an exception
    public override int this[int nRow, int nColumn]
    {
        get
        {
            if (nRow == nColumn)
                return base[nRow, nColumn];
            else
                throw new ArgumentException( "Row and Columns indices are different" );
        }
        set
        {
            if (nRow == nColumn)
                base[nRow, nColumn] = value;
            else
                throw new ArgumentException( "Row and Columns indices are different" );
        }
    }
}

try
{
    // Create a new instance
    DiagonalGrid obDG = new DiagonalGrid();

    // Assign values
    obDG[0,0] = 10;        // OK. Assigns value
    obDG[0,1] = 100;       // Throws an exception because row != column
}
catch( Exception ex)
{
    Trace.WriteLine( ex.Message );
}

Operators

An operator is a class member that defines what it means to apply an expression operator to an instance of a class. For example, if ob1 and ob2 are two objects, what does ob1 + ob2 mean? Or what does ob1++ mean? The definition of these operators (binary + in the first case, and unary increment in the second case) is defined by class operators.

Declaring an operator takes the following forms:

[attributes] [modifiers] type operator overloadable-unary-operator  (type operand)                 operator-body
[attributes] [modifiers] type operator overloadable-binary-operator (type operand1, type operand2) operator-body
[attributes] [modifiers] implicit|explicit operator conv_type_out (conv_type_in operand)         operator-body

Where [modifiers] can be:

and overloadable-unary-operator can be: 

and overloadable-binary-operator can be:

Note the following points about operators:

From the above definition that there are three categories of overloadable operators (assume T is the type of the containing class):

1. Unary operators

  1. Operator definition for ++ and --  must take a single parameter of type T and return T.
  2. Operator definition for +,-, ! or ~ must take a single parameter of type T and return any type.
  3. Operator definition for true and false must take a single parameter of type T and return bool.

2. Binary operators

A binary operator declaration must take two parameters one of which must be the type of the containing class. A binary operator can return any type. Certain binary operators require pair-wise declaration (if one is defined, then the other must be defined as well:

3. Conversion operators

A conversion operator is used to convert from a source type S to a target type T. The source type S is indicated by the parameter type of the operator declaration and the target type T is indicated by the return type of the operator declaration. A class or a struct is allows to declare a conversion operator if all the following are true:

A conversion operator declaration introduces a user-define conversion.

Note that by eliminating unnecessary casts, implicit conversion can improve source code readability. However, because implicit conversions can occur without the programmer specifying them, care must be taken to prevent unpleasant surprises in the form of exception. Therefore, implicit conversions should never throw exceptions and should never lose information so that they can be used safely without the programmer worrying about it. If a conversion operator cannot meet these criteria, then it should be an explicit conversion operator.

Conversion operators are not allowed to redefine a pre-defined conversion. Thus, a conversion operator is not allowed to convert from / to object because implicit and explicit conversion operators already exist between object and all other types.

Conversion operators are also not allowed from or to an interface type. This ensures that a conversion to an interface type succeeds only in object being converted actually implements the interface type.

The signature of a conversion operator consists of the source type and the target type only (this is the only class member for which the return type is part of the signature).  This also means that implicit and explicit keywords are not part of the conversion operator signature.

The following example illustrates most of the above concepts:

public class Point
{
    /* Data members */
    private int nX, nY;

    /* Constructors */
    public Point()
    {
        nX = nY = 0;
    }

    public Point( int x, int y )
    {
        nX = x;
        nY =y;
    }

    /* Unary Operators */

    // Unary operators ++ and --: Operator declaration must take and return the
    // type of the containing class
 
   public static Point operator++(Point pt)
    {
        // Increment values
        pt.nX++;
        pt.nY++;

        // Now return new result
        return pt;
    }

    public static Point operator--(Point pt)
    {
        // Decrement values
        pt.nX--;
        pt.nY--;

        // Now return new result
        return pt;
    }

    // Unary operators true and false: Operator declaration must take the type
    // of the containing class and return bool. Note that operator definition
    // for true and false must be pair-wise. If one is defined, then the other
    // must tbe defined as
    public static bool operator true(Point pt)
    {
        // It is up to us to define what operator true means for a Point class.
        // In this case, the criteria is that neither nX nor nY must be zero
        if ((pt.nX > 0) && (pt.nY > 0))
            return true;
        else
            return false;
    }

    public static bool operator false(Point pt)
    {
        // Delegate to operator true
        if (pt)
            return true;
        else
            return false;
    }

    /* Binary Operators */

    // Binary operator + and -: operator declaration must take two parameters of which
    // at least one must be the type of the containing class. The declaration can return
    // any type
    public static Point operator+( Point ptLHS, Point ptRHS ) // LHS: Left-hand-source. RHS:Right-hande-source
    {
        // Create and initialize a new point
        Point pt = new Point();
        pt.nX = ptLHS.nX + ptRHS.nX;
        pt.nY = ptLHS.nY + ptRHS.nX;

        // return results
        return pt;
    }

    public static Point operator-( Point ptLHS, Point ptRHS ) // LHS: Left-hand-source. RHS:Right-hande-source
    {
        // Create and initialize a new point
        Point pt = new Point();
        pt.nX = ptLHS.nX - ptRHS.nX;
        pt.nY = ptLHS.nY - ptRHS.nX;

        // return results
        return pt;
    }

    /* Conversion Operators */

    // Conversion operator: convert implicitly (no cast required) form PointF to Point
    public static implicit operator Point (PointF ptF)
    {
        return new Point( (int)ptF.fX, (int)ptF.fY );
    }

    // Conversion operator: convert explicitly (cast required) from PointD to Point
    public static explicit operator Point (PointD ptD)
    {
        return new Point( (int)ptD.dX, (int)ptD.dY );
    }
}

/* These classes are used to illustrate conversion operators for Point class */
public class PointF
{
    public float fX, fY;
    public PointF( float x, float y ) { fX = x; fY = y; }
}

public class PointD
{
    public double dX, dY;
    public PointD( double x, double y ) { dX = x; dY = y; }
}

// Create a new instance
Point pt = new Point(1,2);

// Test unary operators
pt++;                              // Invokes operator++
pt--;                              // Invokes operator--
if (pt)                            // Invokes operator true
Trace.WriteLine( "pt is true" );

// Test binary operators
Point pt1 = new Point( 10, 10 );
Point pt2 = new Point( 20, 20 );
Point pt3 = pt1 + pt2;             // Invokes operator+

// Test conversion operators
PointF ptF = new PointF( 10.0F, 20.0F );
Point pt4 = ptF;                  // Invokes operator Point (PointF ptF)

PointD ptD = new PointD( 10.0D, 10.0D );
Point pt5 = (Point)ptD;            // Invokes operator Point (PointD ptD)

Constructors

There are two types of constructors in C#:

Instance Constructors

An instance constructor is a class member that implements actions required to initialized an instance of a class. Declaring a constructor takes the following forms:

[attributes] [modifiers] class-name (formal-parameter-list) : base (argument-list) { ... }
[attributes] [modifiers] class-name (formal-parameter-list) : this (argument-list) { ... }

Where [modifiers] can be:

Instance constructors are not inherited. Hence, a class has no instance constructors other than those defined in the class.  If a class contains no instance constructor declaration, a default instructor (one that takes no parameter) is provided automatically.

Constructor Initializers

In the constructor declaration form, base( argument-list) and this(argument-list) are known as constructor initializers. Constructors initializers are used to invoke other instance constructors immediately before the constructor body as follows:

Note that constructor initializers are only allowed to access the parameters passed to the instance constructor. The following example illustrates:

public class BaseLevel1
{
    public BaseLevel1()
    {
        Trace.WriteLine("BaseLevel1.BaseLevel1()");
    }

    public BaseLevel1(int n)
    {
        Trace.WriteLine("BaseLevel1.BaseLevel1(int n)");
    }
}

public class DerivedLevel1 : BaseLevel1
{
    public DerivedLevel1()                                   // Implicitly calls base()
    {
        Trace.WriteLine( "DerivedLevel1.DerivedLevel1()");
    }
    public DerivedLevel1( double d )                         // Implicitly calls base()
    {
        Trace.WriteLine( "DerivedLevel1.DerivedLevel1(double)");    
    }
    public DerivedLevel1( int n ) : base(n)               // Calls and passes parameter to base class constructor 
    {
        Trace.WriteLine( "DerivedLevel1.DerivedLevel1(int)");
    }
    public DerivedLevel1( int n, string s ) : this(n)      // Calls and passes parameter to an instance constructor in this class  
    {
        Trace.WriteLine( "DerivedLevel1.DerivedLevel1(int, string)");
    }
}

// Test2 constructor initializers
Trace.WriteLine( "Calling constructor with no argument" );
DerivedLevel1 ob1 = new DerivedLevel1();

Trace.WriteLine( "\nCalling constructor with an int argument" );
DerivedLevel1 ob2 = new DerivedLevel1( 10 );

Trace.WriteLine( "\nCalling constructor with an int argument and a string argument" );
DerivedLevel1 ob3 = new DerivedLevel1( 20, "hello" );

       

In the example above, the this() form of a constructor initializer in commonly used in conjunction with operator overloading to implement optional instance constructor parameters:

/* Optional constructor parameters */
public class Person
{
    // Data members
    private int nAge;
    private string strName;

    // Main constructor
    public Person ( int age, string name )
    {
        nAge = age;
        strName = name;
    }

    // Overloaded constructors that delegate to the main constructor
    public Person()              : this( 0, "" )   {}
    public Person( int age )     : this( age, "" ) {}
    public Person( string name ) : this( 0, name ) {}
}

Constructor Execution

Upon entry to the instance constructor and before the implicit invocation of the direct-base class constructor, variable initializers of the instance fields are executed (in the order in which they appear in the class). In other words, variable initializers are transformed into assignment statements and these assignment statements are executed before the invocation of the base class instance constructor. For example

public class Base
{
    public Base( int n )
    {
        ...
    }
}

public class Derived : Base
{
    // Data members
    int nX = (int)Math.Sqrt( 100 );
    int nY;

    // Constructors
    public Derived( int n ) : base(n)
    {
        ...
    }

}

Derived ob = new Derived( 100 );

When object ob is created:

  1. nY is initialized to zero
  2. nX is assigned a value of 10 (square root of 100)
  3. The base constructor is called.
  4. The derived constructor is called.
Private Constructors

When a class declares private or protected constructors only it means that:

For example,

public class BaseWithPC
{
    private BaseWithPC()
    {
        Trace.WriteLine( "BaseWithPC" );
    }
}

public class DerivedFromBaseWithPC : BaseWithPC
{
    public DerivedFromBaseWithPC()
    {
        Trace.WriteLine( "DerivedFromBaseWithPC" );
    }
}

// BaseWithPC constructor is private
BaseWithPC ob5 = new BaseWithPC();                        // 'Primer.BaseWithPC.BaseWithPC()' is inaccessible due to its protection level
DerivedFromBaseWithPC ob6 = new DerivedFromBaseWithPC();  // 'Primer.BaseWithPC.BaseWithPC()' is inaccessible due to its protection level

If BaseWithPC constructor was declared protected instead or private, then the object cannot still be created, but derived classes can derive from it (since the base constructor is now accessible). In other words:

// BaseWithPC constructor is protected
BaseWithPC ob5 = new BaseWithPC();                        // 'Primer.BaseWithPC.BaseWithPC()' is inaccessible due to its protection level
DerivedFromBaseWithPC ob6 = new DerivedFromBaseWithPC();  // OK

Static Constructors

Recall that an instance constructor is a class member that implements actions required to initialized an instance of a class. A static constructor on the other hand is a class member that implements actions required to initialized a class. In other words, instance constructors initialize instances and static constructors initialize classes

Declaring a static constructor takes the following forms:

[attributes] [extern] class-name () { ... }        // Note that access modifiers and parameters are not permitted

The body of a static constructor specified all the statements required to initialize the class. Note that static constructors are:

The exact timing of when a static constructor is called is implementation-dependent. However, it is subject to the following rules:

The following example illustrates:

public class MyClass
{
    // Data members
    static int     nNum = 10;
    static string  strName;
    double         dNum;

    static MyClass()            // Cannot specify access modifiers on a static constructor
    {
        Trace.WriteLine( "public static MyClass()" );
        strName = "MyClass";
    }
    public MyClass()
    {
        Trace.WriteLine( "public MyClass()" );
        dNum = 99.99;
    }
}

MyClass ob = new MyClass();        // static constructor called before instance constructor

Destructors

A destructor is class member that implements actions required to destroy an instance of a class. Declaring a static constructor takes the following forms:

[attributes] [extern] class-name () { ... }        // Note that access modifiers and parameters are not permitted

Destructors are similar to static constructors in two areas:

Destructors cannot be invoked directly. It is up to the garbage collector to decide when to invoke the destructor.

What happens if an exception is thrown and not caught in a destructor? Destructor execution is terminated and the destructor of the base class is called. If there is no base class, the exception is discarded.