Chapter 9

 

Miscellaneous

 

Members

 

There are only two types of entities that can contain members, namely, types and namespaces. These members can be accessed using the same rules i.e. the name of an entity to be followed by a dot '.' and then the member name to be given. This cardinal rule is never ever broken.

 

All derived classes inherit every member of a base class with the exception of constructors and destructors. This rule applies to private members also. Regardless of inheriting all the members, the base class members may/may not be accessible to the derived class members. Thus, the access modifiers have nothing to contribute to inheritance. In other words, the rules of inheritance ignore the access modifiers.

 

If a namespace is not specified for a member, it belongs to a default namespace called the global namespace. Like it or not, all members are part and parcel of a namespace. They cannot exist out of a namespace under any circumstance. It makes logical sense creating our own namespaces thereby avoiding the unnecessary clutter in the global namespace with the members we create.

 

The word int is an alias for System.Int32. A variable of type int is internally known as System.Int32. The basic types like int are internally not known by their name but something else that is entirely different.

 

a.cs

public class zzz

{

public static void Main()

{

System.Type t = typeof(int);

System.Console.WriteLine(t.FullName);

}

}

 

Output

System.Int32

 

The above example reconfirms the point we enumerated a little earlier. The compiler does not understand an int as a data type but knows all about a structure called System.Int32 instead. When we coached you earlier about the compiler understanding about data types like int at birth, what we truly meant was that there exists structures in the System namespace relating to these datatypes. The compiler is informed well in advance about the one-to-one connection between the alias name and their structures. When we get some time on our hand, we will list out and explain all the members of this structure.

 

To explain the above example, we use a keyword typeof, which accepts the name of a class or structure. This returns an object that looks like Type. The Type object consists of methods and properties that publish everything you ever wanted to know about a type but were afraid to ask. We are only using one member called FullName to prove our hypothesis.

 

a.cs

class zzz

{

public static void  Main()

{

string yyy = "hell";

string s = yyy;

System.Type t = typeof(yyy);

System.Console.WriteLine(s);

System.Console.WriteLine(t);

}

}

class yyy

{

}

 

Output

hell

yyy

 

typeof expects the name of a class as a parameter. C# allows a variable name and a class name to be the same. At the line s = yyy, C# assumes yyy to be the name of a variable. yyy is considered to be a class name and t is initialised to the typeof returned by it. Thus, C# uses a lot of intelligence to read between the lines.

 

Our resident astrologer informed us that at least 1.54234 per cent of our book must be copied from the documentation. To make sure that no ill luck befalls us, we copied a couple of lines from it. Thus an sbyte is an alias for System.Sbyte,byte System.Byte, short System.Int16, ushort System.UInt16, uint System.UInt32, long System.Int64, ulong System.UInt64, char System.Char, float System.Single, double System.Double, decimal System.Decimal, and, finally, bool System.Boolean. Enums are simple derived from System.Enum and are simple constants. Object is an alias for System.Object and string System.String. Interfaces are only derived from object and arrays form System.Array. Delegates are from System.Delegate. These have been mentioned earlier, they have been placed here only as a ready reckner.

 

Member Lookup

 

A member lookup is a way by means of which the meaning of a name in a type is determined or figured out. A class can have base classes and all classes have one base class object. The compiler first figures out all the names that match in the class and base class. We can have a constant and a property with the same name in a base class and derived class. We mentioned this earlier, but it merits repeating. If we ever have an override modifier in front of a member name, then this name has overridden the earlier name in the base class. It is thus removed from the list as it is a different name. If the name is a constant, field, property, event, type or enum, it hides all the members of the base class. If it is function, then all non-methods are hidden in the base class. The end result could either be a single non-method name or a series of methods. Anything else is an error.

 

Every known entity is derived from object. We have mentioned this at least a hundred times already. Value types cannot derive from any other type except object unlike classes and interfaces. However, arrays and delegates are derived from System.Array and System.Delegate respectively.

 

Name Resolution

 

After the giving a namespace and a period, we can either use the name of another namespace or the name of a type. If we start, however, with the name of a type, then what follows can be one of the following. Either a type as in a nested types, or a method, a static property, static readonly field or a static field, static event, constant, or finally an enum. Anything else signals an error.

 

a.cs

class zzz

{

public static void Main()

{

aaa aaa;

aaa bbb = aaa.bbb();

aaa ccc = aaa.ccc;

}

}

class aaa

{

public static aaa ccc = new aaa();

public static aaa bbb()

{

return new aaa();

}

}

 

If you want to confuse any person using your code, use the above code. The above explanation clearly states that we can create a constant, field, local variable or a parameter with the same name as the name of a type. Thus, we have a class aaa which contains two members. One is a static field called ccc and  the other is a static function bbb which returns an aaa type. In Main, we create a variable called aaa, same as the data type. Then we call static members of the class aaa using the name of the type. All this leeway is given to us because we are allowed access to static members of the class aaa without any ambiguity.

 

An invocation expression can either be a method group or a delegate. Nothing else can be executable. If the function returns void, the result is nothing. Thus, an expression which results in nothing cannot obviously be the operand of a operator, it can only be used a statement.

 

A method group can either be one method or a group of methods. The parameter types decide which of the methods would be chosen. To execute a certain method, the compiler starts with the type before the method. It then proceeds up the inheritance chain and finds an applicable, accessible, non-override method. If it finds more than one, it uses overload resolution to decide the best one.

 

Constructor Signatures

 

a.cs

public class zzz

{

public static void Main()

{

int i = 3;

yyy a = new yyy(ref i);

int j;

yyy b = new yyy(out j);

System.Console.WriteLine(i + " " + j);

}

}

class yyy

{

public yyy(ref int p)

{

p = 100;

}

public yyy(out int q)

{

q = 1000;

}

}

 

Compiler Error

a.cs(18,8): error CS0663: '.ctor' cannot define overloaded methods which differ only on ref and out

a.cs(14,8): (Location of symbol related to previous error)

 

A constructor cannot define overloaded methods differing only on ref or out. The above logic makes sense as a constructor is nothing but a function that is called automatically at birth. It is in no way different from any other functions that we have worked with.

 

a.cs

public class zzz

{

public static void Main()

{

yyy a = new yyy(1,2,3);

}

}

class yyy

{

public yyy(params int [] q)

{

foreach ( int i in q)

System.Console.Write(i + " " );

}

}

Output

1 2 3

 

We can use the params modifier to our hearts content in displaying the parameters to the constructors.

 

a.cs

public class zzz

{

public static void Main()

{

yyy a = new yyy(1,2,3);

}

}

class yyy

{

public yyy(params int [] q)

{

foreach ( int i in q)

System.Console.Write(i + " " );

}

public yyy ( int i, int j , int k)

{

System.Console.Write("int");

}

}

 

Output

int

 

However, the params modifier is given the last priority by the compiler. If there is an exact match, a one-to-one with the parameters in the constructors, the compiler gives precedence to it and the params is ignored for good. For three ints, the constructor with three parameters will be called and for any other combination of ints, the params constructor will be called.

 

a.cs

public class zzz

{

public static void Main()

{

yyy a = new yyy(1,2,3);

}

}

class yyy

{

public yyy(int p, int p1,int p2,params int [] q)

{

}

public yyy ( int i, int j , int k)

{

System.Console.Write("int");

}

}

 

Output

int

 

Regardless of both the constructors matching very closely, the compiler yet does not give us any error. This is because it treats the params as a second class citizen of the world. We would never like to be in the shoes of a params parameter.

 

This

 

This is only permitted to be used in three places namely a constructor, instance method and an instance accessor.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

}

}

class yyy

{

public yyy()

{

System.Console.WriteLine(this);

if ( this is yyy)

System.Console.WriteLine("yes yyy");

if ( this is zzz)

System.Console.WriteLine("yes zzz");

}

public static implicit operator string (yyy a)

{

return "hi";

}

}

 

Compiler Warning

a.cs(13,6): warning CS0183: The given expression is always of the provided ('yyy') type

a.cs(15,6): warning CS0184: The given expression is never of the provided ('zzz') type

 

Output

hi

yes yyy

 

A this in a constructor is considered to be the value of the data type of the class that contains the constructor. In the above program, the is operator is used to reconfirm the above statement. Also, the implicit string operator is called as the this has to be converted to a string for WriteLine function. Ditto for instance members and accessors. Comment out the string operator and the output will be

 

yyy

yes yyy

 

For a constructor in a structure, the rules change dramatically. Here it is a variable. Like earlier, it stands for a reference to the structure it is placed in, but a major difference is that it is classified as an out parameter. Thus, all the members of the structure must be initialized before we leave the constructor.

 

In the case of an instance method, everything remains the same as above, but now it behaves as a ref parameter instead of an out. Being a ref parameter, someone else is responsible for initializing the variable. The function can access all the instance variables in the structure and also infringe the benefit of changing them if we so choose to. A this is not used on anything remotely connected with static or in variable initializer of a field declaration. A program relating to this is as follows.

 

a.cs

class zzz

{

int j = 10;

int i = this.j;

public static void Main()

{

}

}

 

Compiler Error

a.cs(4,9): error CS0027: Keyword this is not available in the current context

 

This variable is available only in certain places within a class type and can be assumed as the first parameter to the functions in the type. If we have a function abc accepting two ints as a parameter, we would write it as abc(int i, int j). In class yyy, the function gets rewritten as abc( yyy this, int i, int j).

 

This is passed as the first parameter and refers to the class the function resides in. As the programming language C++ called this reference variable as this, C# copied the same concept but with a small change for structures.

 

Base

 

We use the variable base in a completely different manner. A function in the base class gets hidden by the same function name in a derived class.

 

a.cs

class zzz

{

public static void Main()

{

aaa a = new aaa();

a.abc();

}

}

class yyy

{

public virtual void abc()

{

System.Console.WriteLine("yyy abc");

}

}

class xxx : yyy

{

public override void abc()

{

System.Console.WriteLine("xxx abc");

base.abc();

}

}

class aaa : xxx

{

public override void abc()

{

System.Console.WriteLine("aaa abc");

base.abc();

}

}

 

Output

aaa abc

xxx abc

yyy abc

 

The base variable has absolutely nothing to do with the modifiers new and override. It simply calls the function in the base class without bothering about new or override to its existing function. It simply does not care at all. Thus, using base, we can call a function from the base class irrespective of the modifiers on it.

 

We will get an error if base is used in an abstract function. Base, similar to this, is valid only in a constructor, instance method or accessor. All the methods from the viewpoint of base, are non virtual.

Writing base.abc will caste the this pointer of the current class to its lower class, i.e. the base class. Internally base.abc in class aaa derived from class xxx becomes ((xxx)this).abc and ditto for an indexer access.

 

Thus, base and this are conceptually similar, except that one acts on a base class, the other on a derived class.

 

a.cs

class zzz

{

public static void Main()

{

xxx a = new xxx();

a.abc();

}

}

class yyy

{

public virtual void abc()

{

System.Console.WriteLine("yyy abc");

}

}

class xxx : yyy

{

public override void abc()

{

System.Console.WriteLine("xxx abc");

((yyy)this).abc();

}

}

 

Do not run the above program as it will take you to a trip to the moon. Just does not stop at all. All that we said earlier was only conceptually true. You cannot replace a base with a this. The programming language contains both of them and cannot be used interchangeably.

 

 

A struct variable has a free default constructor always available. Thus unlike a class initialization, which explicitly initializes the instance variables to zero, the constructor in the case of the struct does the same.

 

Name Hiding

 

Name hiding occurs when we inherit from a class or struct and erroneously or otherwise, we introduce a similar name as that in the base class. A constant, field, property, event, or type hides the base class members with the same name.

 

a.cs

public class zzz

{

public static void Main()

{

yyy a = new yyy();

System.Console.WriteLine(a.i);

a.i = 100;

System.Console.WriteLine(a.i);

}

}

class yyy : xxx

{

public int i = 3;

}

class xxx

{

public const int i = 10;

}

 

Compiler Warning

a.cs(13,12): warning CS0108: The keyword new is required on 'yyy.i' because it hides inherited member 'xxx.i'

 

Output

3

100

 

 

A million years ago, someone somewhere in the world created a class called xxx. That gentleman, then, added one const member called i. For some reason, we decided to derive from class xxx.

 

We are allowed to create our own variable i notwithstanding the fact that it was declared to be a const in class xxx. Other than a warning issued by the compiler, we are allowed complete freedom in using whatever names we like in our derived classes, thus oblivious to what the base class has given.

 

a.cs

public class zzz

{

public static void Main()

{

yyy a = new yyy();

a.i();

}

}

class yyy : xxx

{

public void i()

{

System.Console.WriteLine("hi");

}

}

class xxx

{

public const int i = 10;

}

 

Output

hi

 

As stated earlier, we have total freedom in doing what we like within the derived class. In the above example, the function i has nothing to do with the const i in class xxx. The compiler does send you a feeble protest in the form of a simple warning but that can be ignored with no loss of life or limb. The same rules apply to indexers also. As repeated earlier, operators can never hide each other ever.

 

Concealing an inherited member does not issue any error as it would then prevent evolving any base classes independently. Lets us explain why a warning and not an error.

 

Let us assume that we are deriving from a base class that has a function called pqr. We now decided to create an abc function in the derived class. After a while, the next version of the base class, for some reason, introduces a new function called abc. At that moment, all derived classes from the base class should not break or give an error. In the scheme of things followed by C#, they are different functions and making changes to a base class does not invalidate existing derived classes at all.

 

Functions

 

There are five places in the C# language where we can place executable code. These places are constructors, methods, properties, indexers and user-defined operators. Anywhere else, and you permission from the silicon god that is not very forthcoming. Function members are not members of a namespace and thus we can only place the above in a type. You cannot have global functions that are not associated with a type. We cannot use ref and out parameters for indexers, properties or operators. These have to be value parameters only.

 

a.cs

class zzz

{

public static void Main()

{

zzz a = new zzz();

int i = 0;

a.abc(i++,i++,i++);

System.Console.WriteLine(i);

}

public void abc( int x, int y, int z)

{

System.Console.WriteLine(x + " " + y + " " + z);

}

}

Output

0 1 2

3

 

The parameters to a function are read in the order they are written, from left to right. Thus even though we are using a postfix notation, i++, the compiler uses the current value of i to initialize parameter x in function abc, and then increase i by one. Thus, i now has a value of one, that is what the parameter y is initialized to and then it is increased by one. This z becomes two and at the end of the function invocation variable i has a value of three.

 

Object Elements

 

a.cs

class zzz

{

public static void Main()

{

byte [] b = new byte[2];

b[1] = 10;

yyy a = new yyy();

System.Console.WriteLine(b[a]);

}

}

class yyy

{

public static implicit operator int(yyy a)

{

return 1;

}

}

 

Output

10

 

If we remove the overloaded operator int, we get the following error.

 

Compiler Error

a.cs(8,28): error CS0029: Cannot implicitly convert type 'yyy' to 'int'

The variable we use in the [] brackets is called the element access and must be one of the following data types namely int, uint, long, ulong or any type that can be implicitly converted to the above type. Thus, without the operator int which was responsible for converting the yyy to an int, we got the above error. On returning 1 in the operator, the array variable becomes b[1].

 

a.cs

class zzz {

public static void Main()

{

byte [] b = new byte[2];

b[1] = 10;

yyy a = new yyy();

System.Console.WriteLine(b[a]);

}

}

class yyy

{

public static implicit operator uint(yyy a)

{

System.Console.WriteLine("uint");

return 1;

}

public static implicit operator long(yyy a)

{

System.Console.WriteLine("long");

return 1;

}

}

 

Output

uint

10

 

Rules, rules everywhere, but not a drop to drink. The element access of an array as stated earlier must either be an int, uint, long or a ulong. The above order must be followed. The compiler checks for the operators in the above order and on finding the very first match, it uses that operator. It stops short in its tracks and does not complain that it could use both the above operators.

It is one of the few cases where although both the operators were applicable, it does not give us an error. In the above types, a short is not mentioned. Recall that sometime back, we spoke of a short being converted into a int. Operators are allowed to throw an exception. If one takes place then the processing stops.

 

a.cs

class zzz

{

public static void Main()

{

byte [] b = null;

System.Console.WriteLine(b[1]);

}

}

 

Output

Unhandled Exception: System.NullReferenceException: Value null was found where an instance of an object was required.

   at zzz.Main()

 

Like a delegate, if we try and access any null object, an exception is thrown. No compile time checks are performed for null as the value. Maybe the next version of the compiler will check for a null and it is on our wish list for Santa Claus. Ditto if we try and exceed the bounds of an array. The exception thrown is  IndexOutOfRangeException instead.

 

a.cs

class zzz {

public static void pqr(ref object x)

{

System.Console.WriteLine("pqr " + x);

}

public static void abc(object x)

{

System.Console.WriteLine("abc " + x);

}

public static void Main() {

object[] a = new object[2];

object[] b = new string[2];

abc(a[0]);

abc(b[1]);

pqr(ref a[0]);

pqr(ref b[1]);

}

}

 

Output

abc

abc

pqr

 

Unhandled Exception: System.ArrayTypeMismatchException: Exception of type System.ArrayTypeMismatchException was thrown.

   at zzz.Main()

 

The rules of array co-variance allow an array to be populated by any data type provided there exist an implicit conversion between them. Array b is an array of objects and there exists an implicit conversion from a string to an object. Hence, we can initialize an array of objects to an array of strings. The array object b is not an array of objects but one that comprises of an array of strings. The compile time data type may be that of an object but the run time data type must be that of a string.

 

Whenever we have a ref parameter being passed a reference, we use an array instead. The compiler is smart enough to perform a run time check on the data type being passed as an array now can hold dual data types. In the last call to function pqr, we are passing b[0] which at compile time is an object but at run time is a string. Thus, an exception is thrown. Remember exceptions can only be thrown at run time. This holds true only for ref parameters and not value parameters.   

 

a.cs

class zzz {

public static void Main() {

}

int x;

void abc( int a)

{

x = 1;

if (a > 10)

{

float x = 1.0;

}

}

}

 

Compiler Error

a.cs(10,7): error CS0136: A local variable named 'x' cannot be declared in this scope because it would give a different meaning to 'x', which is already used in a 'parent or current' scope to denote something else

a.cs(10,11): error CS0029: Cannot implicitly convert type 'double' to 'int'

 

A block is represented by a {} braces. Any variable or identifier created within a block must primarily be unique within that block. Then it must also be unique within the immediate enclosing block. Thus in both blocks, the name must be unique and therefore refer to the same entity. The meaning of the identifier must remain the same within the block.

 

We get an error in the above program as in the outer block we have an int x and in the inner block, within the if statement we have another variable called x again. The second error comes in handy as it very clearly states that it assumes the variable x to be an int within the if statement. The golden rule again. We cannot have a variable with the same name in the inner and outer block. If we remove the statement x = 1 from the above program we will get a slightly different error as follows.

 

Compiler Error

a.cs(10,11): error CS0664: Literal of type double cannot be implicitly converted to type 'float'; use an 'F' suffix to create a literal of this type

 

a.cs

class zzz

{

public static void Main()

{

}

int x;

void abc( int a)

{

if (a > 10)

{

x = 1;

}

else

{

double x = 1.0;

}

}

}

 

Compiler Warning

a.cs(15,8): warning CS0219: The variable 'x' is assigned but its value is never used

a.cs(6,5): warning CS0169: The private field 'zzz.x' is never used

 

The compiler gives us no errors as the variable x created outside of a function in the outer block is not used in the main function block. It is only used in the if statement. Since the else block of the if statement, which is at the same level is not executed, the compiler adjourns after giving a few warnings.

 

a.cs

class zzz

{

public static void Main()

{

}

int x;

void abc( int a)

{

x = 3;

if (a > 10)

{

x = 1;

}

else

{

double x = 1.0;

}

}

}

 

Compiler Error

a.cs(16,8): error CS0136: A local variable named 'x' cannot be declared in this scope because it would give a different meaning to 'x', which is already used in a 'parent or current' scope to denote something else

a.cs(16,12): error CS0029: Cannot implicitly convert type 'double' to 'int'

 

If we knowingly initialize the instance variable x in the function abc, we are using the one created in the outer block. So, we are not allowed to recreate the same variable x the inner block under any circumstance. If you comment the line with x=1 or double x=1.0, the compiler will give you warnings. This proves that the compiler is confused on the variable it should work with while initializing x to 1 as there are two variables available by the same name.

 

a.cs

class zzz

{

public static void Main()

{

}

void abc( bool a)

{

if (a)

{

int i = 0;

}

int i = 3;

}

}

 

Compiler Error

a.cs(12,5): error CS0136: A local variable named 'i' cannot be declared in this scope because it would give a different meaning to 'i', which is already used in a 'child' scope to denote something else

a.cs(12,5): error CS0103: The name 'i' does not exist in the class or namespace 'zzz'

 

What we forgot to inform you earlier is that the reverse is not true. That is, a variable created in an inner block or scope cannot be defined outside the parent or outer scope. This is in contrast to the above explanation.

 

a.cs

class zzz

{

public static void Main()

{

}

void abc( bool a)

{

if (a)

{

int i = 0;

}

if ( a)

{

int i = 3;

}

}

}

 

We get no error above as the two if statements are at the same level and the variables created within the blocks are independent of each other. They do not interfere with each other.

 

The above explanation, called the rules of invariant, apply only to simple names. It is not applicable to member access, an example of this is seen below.

 

a.cs

class zzz

{

public static void Main()

{

}

int x;

void abc( int x)

{

this.x = x;

}

}

 

We have two variables called x in the above program. One is an instance variable and the other is a parameter to a function. Within the function abc, the parameter variable x hides the instance variable. If we want to access the instance variable, however, we have to preface the variable name with this. It is therefore, a good idea to preface all instance variables with the reserved word this.

 

Indexers

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

xxx b = new xxx();

a[1] = b;

System.Console.WriteLine(a[1]);

}

}

class yyy

{

public xxx this[int i]

{

get

{

return new xxx();

}

set

{

}

}

}

class xxx

{

}

 

Output

xxx

 

An indexer can store any value provided it is a class, struct or interface. However, the return value of an indexer is not part of its signature. We can have only one return type but multiple types of parameters.

 

Throw

 

a.cs

class zzz {

public static void Main()

{

throw null;

}

}

 

Output

Unhandled Exception: System.NullReferenceException: Exception of type System.NullReferenceException was thrown.

   at zzz.Main()

 

The keyword null is like a rubber man. Fits everywhere. If we throw a null, the actual exception thrown is NullReferenceException.

 

a.cs

class zzz

{

public static void Main()

{

throw ;

}

}

 

 

Compiler Error

a.cs(5,1): error CS0156: A throw statement with no arguments is not allowed outside of a catch clause

 

A throw can be used without the name of an expression only in a catch. This is one of the few error messages that actually sound English-like. Hope, he is not sacked for committing blasphemy.

 

a.cs

class zzz

{

public static void Main()

{

try

{

throw null;

}

catch ( System.Exception e)

{

System.Console.WriteLine("catch");

throw;

}

}

}

 

Output

catch

 

Unhandled Exception: System.NullReferenceException: Exception of type System.NullReferenceException was thrown.

   at zzz.Main()

 

In this program, a throw is used without any parameters in a catch statement. It is actually a short form of the throw and it throws the same exception that the catch invokes.

 

In this case, the earlier throw showed a NullReferenceException Exception and the inner throw throws the same exception. Also, remember the end point of a throw can never be reached even by a miracle. Exception propagation is the process of transferring control from a throw to one of the exception handlers that may exist.

a.cs

using System;

class zzz

{

void abc()

{

try

{

pqr();

}

catch (Exception e)

{

Console.WriteLine("abc: " + e.Message);

e = new Exception("abc");

Console.WriteLine("abc: " + e.Message);

throw;

}

}

void pqr() {

throw new Exception("pqr");

}

public static void Main()

{

zzz a = new zzz();

try {

a.abc();

}

catch (Exception e) {

Console.WriteLine("Main: " + e.Message);

}

}

}

 

Output

abc: pqr

abc: abc

Main: pqr

 

The throw by itself re-throws the same exception. The exception parameter is like a value variable and even though we are re initializing it, the original remains the same. Thus the message displayed by e.Message does not change from pqr to abc ever. The change is only affected within the catch block as shown by the WriteLine function. The behaviour is the same as that of a value variable.

 

a.cs

class zzz

{

public static void Main()

{

try

{

}

}

}

 

Compiler Error

a.cs(8,1): error CS1524: Expected catch or finally

 

Man cannot live on love alone. So also, a try needs either a catch or a finally or both to live. The variable offered to a catch is just like a parameter to a function and it can be definitely assigned like a value parameter. It also unfortunately dies at the end of the catch. Life-time and visibility do not exceed the catch block!

 

a.cs

class zzz

{

public static void Main()

{

try {}

catch ( System.Exception ) {}

}

}

 

We do not have to supply the exception name in a catch. If we do not, as in the above program, there is no known way of figuring out what exception took place. Common sense dictates that we give the exception a name and try and use it in the code written in the catch.

 

a.cs

class zzz

{

public static void Main()

{

try {}

catch () {}

}

}

 

Compiler Error

a.cs(6,8): error CS1015: An object, string, or class type expected

a.cs(6,10): error CS1026: ) expected

 

What is mandatory however, is the name of the exception class. The documentation says it is possible to have no parameters and the catch is then called a general catch. Does not work as advertised for us. The documentation also claims that it has to be the last. No such luck.

 

a.cs

class zzz

{

public static void Main()

{

try {}

catch (System.NullReferenceException e) {}

catch (System.NullReferenceException e) {}

}

}

 

Compiler Error

a.cs(7,8): error CS0160: A previous catch clause already catches all exceptions of this or a super type ('System.NullReferenceException')

 

You cannot have two catch statements of the same type as the compiler examines them in textual order, first come first serve. The first match is the chosen exception handler, thus the second catch would never be called making it unreachable. This is a no-no as the compiler will have to execute a catch that is unreachable.

 

 

a.cs

class zzz

{

public static void Main()

{

zzz a = new zzz();

try {}

finally

{

goto aa ;

System.Console.WriteLine("aa");

aa:

goto bb ;

}

bb:

System.Console.WriteLine("bb");

}

}

 

Compiler Warning

a.cs(10,1): warning CS0162: Unreachable code detected

 

Compiler Error

a.cs(12,1): error CS0157: Control cannot leave the body of a finally clause

 

We can use a goto statement to jump around within a finally. It would be an act of God to jump out of a finally. The same rules hold for a break or continue. No leaving a finally under any circumstance including a natural calamity. If we have forgotten to tell you this earlier, no returns help either.

 

a.cs

class zzz

{

public static void Main()

{

zzz a = new zzz();

try

{

goto aa ;

}

finally

{

System.Console.WriteLine("aa");

}

aa:

System.Console.WriteLine("bb");

}

}

 

Output

aa

bb

 

No such restrictions apply to a try block. Everything that we taught you earlier can also be used in a try block. The only proviso is that we have to first execute code in the finally block before leaving the try block.