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.