You are on page 1of 48

C# MODIFIERS

SUMMARY
• Introduction
• Access Modifiers
• abstract
• const
• extern
• override
• partial (C# 2.0)
• readonly
• sealed
• unsafe
• virtual
• volatile

INTRODUCTION
Modifiers are C# keywords used to modify declarations of types (class, struct, interface,
enum) and type members (fields, properties, methods, indexers, ...). The remaining sections
explain these modifiers.

ACCESS MODIFIERS
Access modifiers are keywords used to specify the declared accessibility of types and type
members. The four access modifiers are discussed in the table below:

Access
Meaning
Modifier
public is an access modifier for types
and type members. This is the most
public permissive access level as there are no
restrictions on accessing a public type or
type member.
internal is an access modifier for types
and type members. internal members are
accessible only within file of the same
assembly. It is an error to reference an
internal type or internal type member
outside the assembly within which it was
declared.
internal
A common use of internal access is in
component-based development because it
enables a group of components to interact in
a private matter without being exposed to the
outer world. For example, a Data Access
Layer could have several classes with
internal members that are only used by the
DAL.
protected is an access modifier for type
members only. A protected member is
protected only accessible within the body of the
containing type and from within any classes
derived from the containing type.
private private is an access modifier for type
members only. private access is the least
accessible and such members are only
accessible within the body of the containing
type.

Note that nested types within the same


containing body can also access those
private members.

Accessibility Levels
The four access modifiers listed above result in five accessibility levels shown below. Note how
certain accessibility levels are not permitted depending on the context in which an access
modifiers was used:

Accessibility Applies to Applies to Applies to


Meaning
Level namespaces Types Type members
public Access is not restricted. Yes Yes Yes
internal Access is limited to the current assembly (project). No Yes Yes
protected Access is limited to the containing class, and No No Yes
classes derived from the containing class
internal protected Access is limited to the current assembly (project), No No Yes
the containing class, and classes derived from the
containing class
private Access is limited to the containing type. No No Yes

The following table illustrates accessibility levels for namespaces, types, and type members. Note
that namespace elements (i.e., classes, structs, interfaces and enum) can only have public or
internal declared accessibility. Access modifiers private, protected, and protected
internal are not allowed on namespace members.

Allowed
Allowed Default
Default Declared
Context Declared Member
Accessibility Accessibility
Accessibility Accessibility
On Members
namespace public None Internal public
internal
class internal (if top public private public
level) internal protected
internal
internal
protected
private
interface internal (if top public public None
level) internal
struct internal (if top public private public
level) internal internal
private
enum internal (if top public pubic None
level) internal

ABSTRACT

Applies To: Classes, methods, and properties.

The meaning of the keyword abstract depends on the context in which it is used. The
abstract keyword can be used in three contexts:
• classes
• methods
• properties

Abstract Classes
An abstract class means that the class is intended to be used as a base class only. Note the
following features of abstract classes:

• An abstract class cannot be instantiated.


• An abstract class may contain abstract methods and properties.
• A non-abstract class that derives from an abstract class must implement all inherited
abstract methods and properties. (if any).
• An abstract class must provide implementation for all interface members.

public abstract class Person


{
...
}

Abstract Methods and Properties


An abstract method or property means that the method (or property) does not have an
implementation. Note the following features of abstract methods and properties:

• An abstract method/property is implicitly virtual.


• Abstract method/property declarations are only permitted in abstract classes.
• An abstract method/property has no implementation body.
• The implementation of an abstract method/property must be provided by an overriding
method that is a member of class deriving from the abstract class containing the abstract
method.
• It is an error to use static, virtrual, or override in an abstract method declaration.

The following class illustrates the above points:

public interface IFile


{
void Open();
void Close();
}

public abstract class Folder : IFile


{
// Data members
long lSize;

// Interface members must be implemented, even if the


containing class is abstract
public void Open() { Trace.WriteLine( "IFile.Open" ); }
public void Close() { Trace.WriteLine( "IFile.Close" ); }

// Abstract members and properties


public abstract void RenameFolder();
public abstract string FolderName
{
get;
set;
}

// Non-abstract methods/properties
public long FolderSize
{
get { return 0; }
set { lSize = value; }
}
}
public class WindowsFolder : Folder
{
// Data members
string strFolderName;

// Implement inherited abstract methods


public override void RenameFolder()
{
Trace.WriteLine( "WindowsFolder.RenameFolder" );
}

public override string FolderName


{
get { return strFolderName; }
set { strFolderName = value; }
}
}

CONST

Applies To: fields and member variables.

The keyword const is used to modify the declaration of a field or local variables. It indicates that
the value of the field or local variable cannot be modified. Note the following two points for using
const:

• The constant expression used to initialize the constant must yield a value of the target
type, or a value of a type that can be implicitly converted to the target type.
• The constant expression used to initialize the constant must be one that can be fully
evaluated at compile time. Therefore, the only allowable value for a reference type is
either a string or null.
• static is not allowed on a constant expression.

public class ClassWithConstants


{
// const is applied to fields.
private const double PI = 3.14; // A
const
private const double CM_To_Inch = 2.54; // A
const
private const double Inch_To_CM = 1 / CM_To_Inch; // A
const can participate in a const expression
private const string USER = null; //
reference types must be initialized either to null or to a
string

public void ConvertFromEnglishToMetric()


{
// const can also be applied to local variables
const double Meter_To_Inch = CM_To_Inch * 100;

...
}
}

EXTERN
Applies To: Method, property, event, indexer, operator, constructor, and destructor

The extern modifier is used in a class member declaration (method, property, event,
indexer, operator, constructor, and destructor ) to indicate that the method is implemented
somewhere else outside the C# code (externally). extern is usually used with the
DllImport attribute. And because an extern method should not provide an implementation,
there is no method body. For example:

public class MyClass


{
[DllImport("User32.dll")]
pblic static extern GetDirectoryName( out string strDirName );

public void ShowDirectoryName()


{
string sName;
GetDirectoryName( out sName ); // Use the extern
method just like any other C# method
return sName;
}
}

See External Methods in Classes section.

It is an error to use extern and abstract modifiers to modify the same member. abstract
modifier means that the method implementation is not provided in the containing class but should
be provided in derived class, whereas extern means that the method is implemented outside
the C# code.

External Assembly Alias (New in C# 2.0)


Sometimes you may have to use two or more versions of the same assembly in the same
application. In other words, you need to reference two versions of the same assembly that have
the same fully-qualified name. By using an external assembly alias, the namespaces of each
assembly can be wrapped inside root-level namespaces named by the alias, allowing them to be
used in the same file.

External assembly aliases are accomplished with the alias sub-option of the /Reference
compiler option. The main procedure is as follows: Suppose you have a Data Access Layer
project called DAL written in VB. You compile this project to produce DAL_VB.DLL. This DLL is in
turn used by a business objects layer DLL called BOL.DLL. You then rewrite DAL in C# but only
manage to partially finish it. This partially finished code is now in a DLL called DAL_CS.DLL. Both
DAL_VB.DLL and DAL_CS.DLL refer to the code base which has the same namespaces. In your
BOL.DLL you still would like to reference both DLLs - DAL_CS.DLL for the new and improved
functionality, and DAL_VB.DLL for the remaining yet-to-translate functionality.

You then compile your BOL.DLL with the /Reference option as follows:

/Reference DALVB=DAL_VB.DLL /Reference DALCS=DAL_CS.DLL

This will setup two external references called DALVB and DALCS, which you can use in the BOL
code via an extern statement as follows:

extern DALVB;
extern DALCS;
// You can now refer to the same class from both DLLs
DALVB.DAL.ExecuteNonQuery( ... ); // Old DLL
DALCS.DAL.ExecuteNonQuery( ... ); // New DLL
OVERRIDE

Applies To : Method, property, event, indexer.

An override class member provides a new implementation for a member derived from a base
class. Note the following points:

• The overridden method in the derived class must have the same exact signature as the
inherited class member.
• The inherited class member to be overridden must be abstract, virtual, or
override.
• You cannot override a static or non-virtual method.

See Virtual Methods in Classes section for a full example.

PARTIAL (C# 2.0)


Applies to: classes, structs and interfaces. Does not apply to delegate and enums.

partial keyword is used to split the definition of a class or struct or interface across
multiple source files. Each source file would contain a section of the class definition and all
parts are combined when the application is compiled. The following example illustrates:

// File1.cs
public partial class Order
{
private GetOrderDetails() { ... }
}
// File2.cs
public partial class Order
{
public RetrieveOrder()
{
GetOrderDetails();
...
}
}

Using partial is desirable in the following situations.

• In large projects spreading a class definition over multiple source files allows multiple
programmers to work on it simulataneously.
• When working with automatically generated source code, your code can be added to the
automatically generated source code (in a separate class using partial keyword)
without having to edit the automatically generated source code. Visual Studio uses this
functionality when creating Windows Forms (and other file types). Consider this code
generated by VS.NET 2005 for a Windows Forms class:

// MyForm.cs. Your code goes into this class

public partial class MyForm : Form


{
public MyForm()
{
InitializeComponent();
}

// Your code would be added here ...


}
// MyForm.Designer.cs. Automatically generated code maintained
by VS.NET as you add/remove controls

partial class MyForm


{
private System.ComponentModel.IContainer components = null;

protected override void Dispose(bool disposing)


{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Windows Form Designer generated code


...
#endregion
}

Note the following points about the partial keyword:

• All the parts of a partial definition must use the partial keyword, and all the parts must
be available at compile time to form the final type. Obviously, all parts must have the
same declared accessibility ( a partial part cannot be public while another is
internal).
• All parts of a partial definition must be defined in the same assembly.
• If any of the parts is declared abstract, then the entire type is abstract. Likewise, if any
of the parts is declared sealed, then the entire type is sealed.
• If any of the parts inherits from a base class, then the entire type inherits from that class.
• Each partial part my inherit from a different interface, but the final type must
implement all the interfaces listed by the partial class definitions.
• Any class, struct, or interface members declared in a partial definition are
available to all the other parts.
• Nested types can be partial, even if the containing type is not partial

public class MyClass


{
partial class MyPartialClass
{
...
}

...

partial class MyPartialClass


{
...
}
}

• At compile-time, attributes of partial-type definitions are merged together.

[Synchronized]
public partial class MyClass { ... }
[Serializable]
public partial class MyClass { ... }
// Above two code fragments are equivalent to
[Synchronized]
[Serializable]
public partial class MyClass { ... }

• The following are merged together for all partial class definitions: XML comments,
interfaces, generic-type parameter attributes, class attributes, and members. For
example:

public partial class Car : IEngine, IBody


{ ... }
public partial class Car : Vehicle
{ ... }
// Above two code fragments are equivalent to
public partial class Car : Vehicle, IEnging, IBody
{ ... }

READONLY

Applies To: fields

readonly is a modifier that can only be applied to class fields. A readonly field cannot be
assigned a value during program execution except as part of its declaration or in a constructor of
the same class. See readonly in Classes section for a full example.

SEALED

Applies To: class methods, indexers, properties, and events

A sealed class is one that cannot be inherited. It is typically used to prevent accidental
inheritance of the class. struct are implicitly sealed and thus cannot be inherited. See Sealed
Classes in Classes section.

STATIC

Applies To: classes (new in C# 2.0), fields, methods, properties, events, operators, and
constructors.

A static type or class member belongs to the class itself rather than to a specific object.
Therefore, there is exactly only one copy of each static field in a class. Note the following:

• A constant is implicitly a static member.


• A static member cannot be referenced through an instance.
• You cannot use the this keyword with a static class member.

UNSAFE

Applies To: Any callable class member (methods, indexer, property, constructor and so on - but
not for static constructors)

The unsafe keyword is required for any block of code that uses pointers. Note that the scope of
the unsafe keyword extends from the parameter list to the end of the class member. For example:

unsafe public void Square( int *pN )


{
*pN = (*pN) * (*pN);
}
To compile .NET code that contains an unsafe code you must use the /unsafe compiler
option. See Unsafe chapter for more details.

VIRTUAL

Applies To: methods, properties, events, and indexers.

The virtual keyword is used to indicate that the implementation of the affected class member
depends on the runtime-type of the object being invoked. So when a virtual method is
invoked, the runtime-type of the invoked object is checked for an overriding member. The
overriding member in the most-derived class is then called.

Note the following points:

• By default, class members are non-virtual. You cannot override a non-virtual method.
• The virtual modifier cannot be used on declarations with static, abstract or
override.

See Virtual Methods in Classes section

VOLATILE

Applies To: fields.

The volatile keyword indicates that a filed can be modified in the program by the operating
system, hardware, or another concurrently executing thread. A volatile field is usually used for
fields that will be accessed by multiple threads without using the lock statement to synchronize
access. Using the volatile modifier ensures that one thread retrieves the most-up-to-date
value written by another field.

See Volatile fields in Classes section.

CLASSES

SUMMARY
• Class Declarations
• Class Members
• Constants
• Fields
• Methods
• Properties
• Events
• Indexers
• Operators
• Constructors
• Destructors

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:

• An abstract class is one that cannot be instantiated directly via new.


• An abstract class can contain member methods that can be either abstract or non-
abstract.
• When a non-abstract class inherits from an abstract base class, the non-abstract class
must implement all of the inherited abstract members of the abstract base class by
overriding them.
• An abstract class cannot be sealed.

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:

• If a class declaration has no base class, or if the class only derives from interfaces, then
the direct base class is assumed to be object.

// The direct base class is assumed to be object


public class A { ... }

// The direct base class is A


public class B : A { ... }

• The set of base classes of a class type is the transitive closure of the direct base class
relationship. In the above example, the base classes of class type B is A and object.
• The direct base class must be at least as accessible as the class type itself:
• The direct base class of a class type must not be any of the following: System.Array,
Sysem.Delegate, System.Enum, or System.ValueType.
• Every class has exactly one direct base class. Except the object class which has no
direct base class and is the ultimate base class of all other classes.
• A class directly depends on its direct base class, and directly depends on a class within
which it is immediately nested (if any). Therefore, the complete set of classes upon
which a class depends is the transitive closure of the "directly depends on" relationships.

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:

• Constants
Represent constant values within the class.
• Fields
Represent variables of the class.
• Methods
Represent computations and actions performed by the class.
• Properties
Represent named characteristics used to read/write those characteristics.
• Events
Represent notifications that can be generated by the class.
• Indexers
Permit instances of classes to be indexed in the same way as arrays.
• Operators
Define which expression operators can be applied to instances of classes.
• Constructors
Instance constructors implement actions required to initialize instances of the class,
where static constructors implement actions required to initialize the class itself (typically
used to initialize static members)
• Destructors
Implement clean-up actions before an instance is permanently discarded from memory.
• Types
Represent types that are local to a class.

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,

• Only support public inheritance.


• Can inherit from one base class only. However, a C# class is free to inherit from more
than one interface.
• Can only be created using the new operator.

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:
• public
• protected
• private (default if none was provided)
• internal
• protected internal

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:

• A static field identifies only one storage location. No matter how many instances of a
class are created, there will always be one copy.
• A static function member does not operate on a specific instance. Therefore, it is a
compile-time error to refer to this in such a function.

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 internal, protected, 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:

• new
• public
• protected
• internal
• private (default)
• static
• readonly
• volatile

Note that initializing a field is optional:

• If a field is not initialized, the initial value of a field whether it is a static field or an instance
field is the default value of the field's type. This initialization happens when the program is
run by having the memory manager initialize field memory to all-bits-zero before it is
allocated for use.
• If a field was initialized, then:
• for static fields this initialization correspond to an assignment statement that is
executed at an implementation-dependent time prior to the static constructor of
the class (if any) and prior to the first use of a static field of that class.
• for instance fields this initialization correspond to an assignment statement that is
executed during class instantiation using any of the instance constructors.

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:

• Non-static fields
These fields can be initialized during declaration or in an instance constructor.
• Static-fields
These fields can be initialized during declaration or in an static constructor.

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?

• A static readonly field is useful when a symbolic name for a constant value is
required, but when the type of the value is not permitted in a const declaration.
• A static readonly field is useful when the value cannot be computed at compile-
time.

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:

• A read of a volatile field is called a volatile read - it is guaranteed to occur prior to any
references to memory that occur after it in the instruction sequence.
• A write of a volatile field is called a volatile write - it is guaranteed to occur after any
memory references prior to the write instruction in the instruction sequence.

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:

• new
• public
• protected
• internal
• private (default)
• static
• virtual
• sealed
• override
• abstract
• extern

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:

• fixed parameters
• fixed parameters, parameter-array
• parameter-array

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.
• Reference Parameters.
• Out Parameters.
• Parameter Arrays.

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 location. Instead, 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:

• .NET types can be either value-types or reference-types (ignore the pointer types in
unsafe code as they are irrelevant to this discussion)
• .NET types can either be passed by-value or by-reference.

Therefore, there are four cases for passing method parameters in C#':
• Passing a value type by- value.
• Passing a value type by- reference. Use the ref or out keywords.
• Passing a reference type by- value.
• Passing a reference type by- reference. Use the ref or out keywords.

Also recall the following:

• Passing any type by-value to a function member means that the the function member will
be working with a copy, and hence changes to the variable will not be seen by the caller.
• Passing any type by-reference to a function member means that the the function member
will be working on the same location as the passed variable, and hence changes to the
variable will be seen by the caller.

Now note the following:

• The default for passing value-types is by-value. This means that a copy of the actual data
will be passed and changes to the copy will not be seen by the caller.
• The default for passing reference-types is also by-value. This means that a copy of the
reference will be passed. This further means that changes to the underlying data pointed
to by the reference will be seen by the caller, however, changes to the reference itself,
i.e., re-assigning the parameter to a different memory location works only inside the
method and does not affect the original variable.
• Passing a reference type by-reference means that the actual reference itself will be
passed. This implies two things: Changes to the underlying data will be seen by the
caller, and changes to the reference (i.e., re-allocating the variable) will also be seen by
the caller.

The following table summarizes the points above::

Case Keyword Note


Passing a none • A new local storage location
value type is created in the method,
by- value hence a copy of the data is
used.

• Changes to this value in the


method are not seen by the
caller.
Passing a out or • The function uses the same
value type ref storage location as used by
by- the original caller.
reference
• All changes to the variable
will be seen by the caller.
Passing a none • A new local storage location
reference is created in the method,
type by- hence a copy of the
value 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 out or • The function uses the same
reference ref storage location as used by
type by- the original caller.
reference
• 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:

• In a virtual method invocation, it is the run-time type of the instance that determines
which method to invoke.
• In a non-virtual method invocation, it is the compile-time type of the instance that
determines which method to invoke.

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:

• An abstract method declaration declaration introduces a new virtual method, but unlike a
virtual method declaration, an abstract method declaration does not provide an
implementation.
• Non-abstract derived classes are required to provide their own implementation by
overriding the inherited abstract method. Whereas a a virtual method declaration does
not require its derived classes to provide their own implementation since the derived
classes inherit the default implementation of the base class virtual method.

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:

• new
• public
• protected
• internal
• private (default)
• static
• vritual
• sealed
• override
• abstract
• extern

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:

• A virtual property is one that is declared with the virtual keyword. The virtual
keyword specifies that the current implementations of the accessors are virtual and that
derived classes are free to specialize them using the override keyword.
• An abstract property on the other hand is one declared with the abstract keyword but
does not provide an actual implementation of the accessors. Because abstract property
declarations are only permitted on abstract classes, it is therefore, a requirement for non-
abstract derived classes to provide their own implementation. This logic is the same as it
is for virtual and abstract methods.
• A property declaration that includes both abstract and override modifiers specifies
that the property is abstract and overrides a base virtual property. This results in a branch
situation which was discussed in the methods section.
• When overriding properties, if the inherited property had a single accessor (i.e., read-only
or write-only), the overriding property must only override that accessor. If the inherited
property included both accessors, the overriding property can override either a single
accessor or both of them.
• An overriding property may include the sealed keyword to indicate that this property
may not be further specialized by derived classes.

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:

• new
• public
• protected
• internal
• private (default)
• static
• vritual
• sealed
• override
• abstract
• extern

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.

When working with events, you typically work with three components - an event, a delegate and
an event handler. An event is fired by a class in certain cases by simply calling the event. A
delegate represents the signature of a callable entity (i.e., a function) that will be called by the
event. An event handle is a client-side function whose signature must match that of the delegate
and that will be called when the event fires.

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. To create C# events:

1. Create or identify a delegate


If you are defining your own event, ensure that there is a delegate to use with the event.
2. Create a class that defines the event
This class contains:
• An event created from the delegate.
• An optional method that verifies that an instance of the delegate declared with
the event keyword exists.
• Methods that fire (call) the event.
3. Define one or more classes that connect methods (event handlers) to the event
Each of these classes will include the definition of the methods that will be associated
with the event
4. Use the event
Use the event by creating objects of the class that contains the event declaration and
firing the event in specific cases

For example,

// Step 1: Create a delegate to represent the function(s) that


will be called by the event.
// Event handlers must follow this signature
public delegate void ProcessFault(string str);
// Step 2: Create a class to declare the event and call it
public class MyMachine
{
// Declare an event that when fired will call functions
whose signature match that of 'ProcessFault'
public event ProcessFault Break;

public void Start()


{
// Code to start the machine
...

// If some parameter is below threshold, fire the Break


event. Because the 'Break' event is associated
// with a delegate whose signature takes a string as a
parameter, the event must supply that string,
// which in turn will be passed to the event handler(s).
The following line will therefore (internally)
// call all methods (i.e., event handlers whose
signature must match that of the delegate) associated
// with the event
Break( "machine stopping" );
}
}
// Create a simple machine object
MyMachine ob = new MyMachine();

// Wire up the machine's events. This is how we tell the Break


event which functions it should
// call when the event is fired. Note that PreFaultHandler and
PostFaultHandler are helper functions
// whose signature must match that of event's delegate. Note
that the compiler provides the
// underlying implementation for the += and -= operators.
ob.Break += new ProcessFault( PreFaultHandler );
ob.Break += new ProcessFault( PostFaultHandler );

// Start the machine. It may fire events


ob.Start();
// These are the event handlers
private void PreFaultHandler( string strMessage )
{
Trace.WriteLine( "PreFaulHandler - " + strMessage );
}

private void PostFaultHandler( string strMessage )


{
Trace.WriteLine( "PostFaultHandler- " + strMessage );
}

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. In this code segment from the example above:

// Wire up the machine's events. This is how we tell the Break


event which functions it should
// call when the event is fired
ob.Break += new ProcessFault( Pre FaultHandler );
The += assignment will call the add code block associated with the Break event to add the event
handler to some storage location (perhaps a hash table maintained by the event class), and
conversely, the -= assignment will call the remove code block to remove the event handler from
the same storage location. In the following example, event-accessor-declarations are added -
note how the event is fired:

public delegate void ProcessFault2(string str);


public class MyMachine2
{
// Declare storage location for delegates associated with
events
private ProcessFault2 handler;

// This is the event. Note how now it has a body to handle


adding/removing delegates
public event ProcessFault2 Break
{
add
{
handler += value;
}
remove
{
handler -= value;
}
}

public void Start()


{
// Code to start the machine

// If some parameter is below threshold, fire an the


Break event
handler( "machine stopping" );
}
}
// Test event where add/remove have explicitly specified
MyMachine2 ob2 = new MyMachine2();
ob2.Break += new ProcessFault2( FaultHandler );
ob2.Start();

// This is the event handler


private void FaultHandler( string strMessage )
{
Trace.WriteLine( "FaulHandler - " + strMessage );
}

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

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:


• new
• public
• protected
• internal
• private (default)
• virtual
• sealed
• override
• abstract
• extern

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:

• Unlike other class members, indexers do not have user-defined names.


• For the parameter-list, at least one parameter must be specified and that ref and out
parameters are not permitted/
• Even though the syntax for accessing an indexer element is the same as accessing an
array variable, an indexer element is not specified as a variable, and therefore, it is not
possible to pass an indexer element as a ref or out variable.
• The signature of an indexer consists of the number and type of its formal elements. The
names of the formal parameter and type of the underlying element is not part of the
indexer signature. And because indexers do not have user-defined names, indexers are
only identified through their signature.
• An indexer is always an instance member and can never be static.
• When overriding a virtual indexer, the inherited indexer implementation can be accessed
using the base keyword. The following example illustrates:
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:

• public
• static
• extern

and overloadable-unary-operator can be:

• +
• -
• !
• ~
• ++
• --
• true
• false (true and false require pair-wise declaration - if one is defined the other must
be defined also.)

and overloadable-binary-operator can be:

• +
• -
• *
• %
• /
• &
• |
• and many others (review MSDN).

Note the following points about operators:

• An operator declaration must include either extern and hence no function body, or both
public and static modifiers.
• The parameters of an operator must all be value parameters. It is a compile-time error for
an operator declaration to use ref or out parameters.
• It is not possible for an operator declared in a derived class to hide an operator declared
in a base class, Therefore, the new keyword is never permitted on an operator. This is
because operator declarations always require class to participate in the signature of the
operator.

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:

• operator== and operator!=


• operator > and operator <
• operator >= and operator <=

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:

• S and T are different types.


• Either S or T is the class or struct type in which the conversion operator is declared.
• Neither S nor T is object or an interface-type.
• T is not a base-class of S, and S is not a base-class of T.

A conversion operator declaration introduces a user-define conversion.

• A conversion operator declaration that includes implicit introduces a user-defined


implicit conversion
Implicit conversion occur in a variety of situation including function member invocation,
cast expressions and assignments. Implicit conversion occur implicitly without having to
specify a cast.
• A conversion operator declaration that includes explicit introduces a user-defined
explicit conversion
Explicit conversion must occur in cast expressions, else a compile-time error is
generated. Again, only use explicit conversion if the conversion can cause information to
be lost or can throw an exception.

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
• Static Constructors

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:

• public
• protected
• internal
• private
• extern

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:

• base( argument-list): causes the appropriate instance constructor of the direct-


base-class to be invoked.
• this(argument-list) : causes the appropriate instance constructor from the
containing class to be invoked.
• If an instance constructor has no constructor initializers, an instance constructor initializer
of the form base() is automatically provided. This ensures that base class constructors
are called as appropriate.

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:

• The class cannot be instantiated by clients.


• The class cannot be derived from.

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:

• Cannot inherited.
• Cannot be called directly.
• Are parameterless (hence cannot be overloaded.)

The exact timing of when a static constructor is called is implementation-dependent. However, it


is subject to the following rules:

• The static constructor of a class executes before any instance of the class is created.
• The static constructor of a class executes before any of the static members of the class
(if any) are referenced.
• The static constructor of a class executes after the static field initializers (if any) for the
class.
• The static constructor of a class executes at most one time during the lifetime of a
program.

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 are not inherited.


• Cannot be called directly.
• Destructors are parameterless (hence cannot be overloaded.)

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.

You might also like