Operators & Access keywords

Summary

Introduction

C# supports the following operator keywords:

as

Operator as is used to perform conversions between compatible types. Operator as is used in the following form:

expression as type

where type must be a reference type. Operator as is essentially a casting operation except that is yields null on conversion failure rather than throwing the InvalidCastException exception. For example:

// Create and initialize an array of objects (strings and class references)
object[] aOb = new Object[3];
aOb[0] = "Hello";
aOb[1] = "World";
aOb[2] = new MyClass();

// Now attempt to convert each object to a string
string strVal;
for( int i = 0; i < aOb.Length; i++)
{
    // Determine if the current object can be converted to a string
    strVal = aOb[i] as string;
    if (strVal == null)
        continue;

    // We have a valid string. Now process it
    Trace.WriteLine( strVal );

}

Note that as differs from a casting operation in the following ways:

is

is operator is used in a conditional expression to check whether the run-time type of an object is compatible with a given type. is operator takes the following form:

expression is type

is expression will evaluate true if:

For example,

MyClass1 ob = new MyClass1();
...
if (ob is MyClass2)
{
    Trace.WriteLine( "ob type is MyClass2" );
}

if (ob is MyClass1)
{
    Trace.WriteLine( "ob type is MyClass1" );
}

new

In C#, new can be used as an operator or as a modifier:

new operator

The new operator is used to create new instances of types. There are three forms on new expressions:

  1. Object creation expressions used to create new instances of class types and value types.
  2. Array creation expressions used to create new instances of arrays.
  3. Delegate creation expressions used to create new instances of delegates.
Object creation expressions

Object creation expressions takes the form of:

new type (argument-list)

Where type is either a class type or a value type. For example, new operator is used to create objects and invoke constructors (default or user-defined):

MyClass ob = new MyClass( UserID, UserName );

or it can be used to invoke the default constructor of value types:

// These two statements are equivalent
int nNum = new int();        // nNum is 0. No dynamic allocation of memory. See notes immediately below
int nNum = 0;                // nNum is also zero

Note that the new operator implies the creation of new instances but it does not necessarily imply dynamic allocation of memory. In particular, instances of value types do not require additional memory beyond that of the variables in which they reside. Therefore, when new is used to create instances of value types no dynamic memory allocation occurs.

The run-time behavior of new when creating classes is:

  1. Allocate memory from the heap. If no memory was available, A System.OutOfMemoryException is thrown.
  2. Initialize all fields to their default values.
  3. Invoke the appropriate constructor.
Array creation expressions

Array creation expressions are used to create new instances of an array-type. C# supports single-dimension, multiple-dimension, and jagged arrays. In the following example, note that the size of the array is not part of the type. This allows you to create an array and then assign values.

// Single-dimension arrays
int[] aNums;            // Declaring an array

// Multiple-dimension arrays
int[,] aNums;          // Declaring an array

// Jagged arrays
int[][] aNums;         // Declaring an array

Initializing arrays is also simple:

// Single-dimension arrays
int[] aNums = new int[5] { 1,2,3,4,5 };    // The standard way, or
int[] aNums = new int[]  { 1,2,3,4,5 };    // omitting the size of the array, or
int[] aNums = { 1,2,3,4,5 };               // omitting new

// Multiple-dimension arrays
int[,] aNums = new int[2,3] { {1,2,3}, {4,5,6} };    // The standard way, or
int[,] aNums = new int[,]   { {1,2,3}, {4,5,6} };    // omitting the size of the array, or
int[,] aNums = { {1,2,3}, {4,5,6} };                 // omitting new

// Jagged-dimension arrays
int[][] aNums = new int[2][] {new int[] { 1,2,3}, new int[] { 5,6,7,8} };   // The standard way, or
int[][] aNums = new int[][]  {new int[] { 1,2,3}, new int[] { 5,6,7,8} };   // omitting the size of the array, or
int[][] aNums = {new int[] { 1,2,3}, new int[] { 5,6,7,8} };                // omitting new

Delegate creation expressions

Delegates have been discussed here.

new modifier

In addition to allocating new instances, new is used to hide inherited members from base classes. To hide an inherited member, declare it in the derived class with the same name and modify it with the new modifier:

public class Base
{
    public void foo() { ... }
}

public class Derived : Base
{
    new public void foo() { ... }        // hides Base.foo()
}

Name hiding through inheritance takes one of these forms:

Note that you cannot use new and override at the same time, but you can use new and virtual to signify a new specialization starting point:

new public override void foo();        // ERROR
new public virtual void foo();         // OK. A new point of specialization

sizeof

The sizeof operator is used to obtain the size in bytes for a value type. Note that the sizeof operator can only be used for value types and cannot be overloaded, It can only be used in unsafe mode. sizeof takes the following form:

sizeof( type )

For example, 

// compile with /unsafe 
int nSize1 = sizeof(long )        // nSize1 = 8
int nSize2 = sizeof(int )         // nSize2 = 4

typeof

The typeof operator is used to obtain the System.Type object for a type. typeof takes the following form:

typeof( type )

To get the type of an expression use the .NET Framework method GetType(). For example:

// Declare and instantiate a class
public class MyClass { ... }
MyClass ob = new MyClass();

// To get the type of the class
Type t1 = typeof( MyClass )

// To get the type of the object
Type t2 = ob.GetType();

true & false

true and false can both be used an overloaded operator and as a literal. As a literal:

bool bA = true;
bool bB = false;

More interestingly is their use as an operator. User-defined types can define operator true and operator false to indicate that the user-type has a state of true and false. For example, a class called Computer can have operator true and operator false to indicate whether the system is up or down. See the Point example under Operators section.

stackalloc

stackalloc can only be used in unsafe mode. It is discussed in the Unsafe Code chapter.

base

The base keyword is used to access members of the base class from a derived class. base can be used in two ways:

The following example illustrates:

public class MyPerson
{
    // Constructors
    public MyPerson()
    { ... }

    public MyPerson( int id, string name )
    { ... }

    // Public interface
    public virtual void PrintDetails()
    {
        Trace.WriteLine( "MyPerson.PrintDetails" );
    }
}

public class MyEmployee : MyPerson
{
    // Constructors
    public MyEmployee()
    { ... }

    public MyEmployee( int id , string str ) : base( id, str )
    { ... }

    // Public interface
    public override void PrintDetails()
    {
        Trace.WriteLine( "MyEmployee.PrintDetails" );
        base.PrintDetails();
    }
}

this

The this keyword refers to the current instance for which a method is called. Static methods by definition do not have the this keyword. The following are common uses of this:

// Pass this object to another object
public class Class1
{
    public void foo()
    {
        Class2 ob2 = new Class2();
        ob2.UseClass1( this );
    }
}

// Declare an indexer
public int this[int nIndex]
{
    get { return aNums[nIndex]; }
    set { aNums[nIndex] value; }
}

// Qualify members hidden by same name
void foo( string name, int id )
{
    this.name = name;
    this.id   = id;
}

Note on == and != operators

The == and != operator operate on value and reference types. For value-types, they determines whether the two values are equal/not equal. For reference-types (except string), they determine if the two objects are equal, i.e., the two operands both have the same address. For string, which is a reference type, these operators compare not the addresses of the two operands, but rather the value pointed to by these two references.