Chapter 1
Concepts Revisited
Entry
Point
The first function to be called
in any C# program is Main. Obviously someone at Microsoft tossed a coin and as
it fell 'heads', they chose Main. Had it been 'tails', one wonders what the
first function would have been called. As everything starts from Main, it is
also called the entry point of the program. It is the fountainhead of all
knowledge. Incidentally, the C programming language also calls Main as its
first function for all C-language programs.
We can have as many as four different
ways to declare Main in our program. They are as follows:
static void Main() {...}
static int Main() {...}
static void Main(string[] a) {...}
static int Main(string[] args) {...}
The operating system calls Main
and waits for it to return a value. This value denotes the success or failure
of the program.
Main can either return a number
or result in no return value, that is, void. If it returns an int, by
convention, a value of zero means success and any other value indicates an
error. No international authority can standardize the error numbers, it largely
depends on the programmer himself.
Copy as a command takes two
parameters; the source and destination files. Similarly, a C# program can also
accept command line parameters at runtime. The executable a.exe can be entered as
>a one two three
a.cs
class zzz
{
public static void Main(string[] a)
{
int i;
for ( i=0; i< a.Length; i++)
System.Console.WriteLine(a[i]);
}
}
Output
one
two
three
one, two, three are called
command line parameters. The program accepts them in an array of strings. As
the array is a parameter to the function, we are free to decide on its name.
Every array has a member called Length, which tells us the size of the array.
In our case, it is three. Thus, a[0] will contain the first word one and not
the name of the program, a[1] will contain two and a[3] - three. Main now
behaves like any other function. What we do next with the command line
arguments depends entirely upon us.
a.cs
class zzz
{
public static void Main(string[] a)
{
}
public static int Main()
{
}
}
Compiler Error
a.cs(3,20): error CS0017: Program 'a.exe' has more than one
entry point defined: 'zzz.Main(string[])'
a.cs(6,19): error CS0017: Program 'a.exe' has more than one
entry point defined: 'zzz.Main()'
You can have one and only one
function called Main in any C# program. Even though you can call it with
different parameters, with the name changing, Main as a function must be given
only once.
a.cs
class zzz
{
public static void
Main(int i)
{
}
}
Compiler Warning
a.cs(3,21): warning CS0028: 'zzz.Main(int)' has the wrong
signature to be an entry point
Compiler Error
error CS5001: Program 'a.exe' does not have an entry point
defined
Here, the compiler first displays
a warning signaling us that Main has not been created with the right
parameters. The error, following the warning, proclaims that we have forgotten
to create a function called Main. The signature includes the return type only
in special cases as entry point.
a.cs
class zzz
{
public static long Main()
{
return 0;
}
}
Compiler Warning
a.cs(3,20): warning CS0028: 'zzz.Main()' has the wrong
signature to be an entry point
Compiler Error
error CS5001: Program 'a.exe' does not have an entry point
defined
The signature refers to the
parameters given to the function plus the return value. Main in the above
program returns 'long', hence we see the error.
a.cs
class zzz
{
public void Main()
{
}
}
Compiler Error
error CS5001: Program 'a.exe' does not have an entry point
defined
The signature also includes
modifiers like static etc. It just proves the importance of the function Main
and the way it has been defined.
a.cs
class zzz {
public static void Main()
{
}
}
class yyy
{
public static void Main()
{
}
}
Compiler Error
a.cs(2,21): error CS0017: Program 'a.exe' has more than one
entry point defined: 'zzz.Main()'
a.cs(8,21): error CS0017: Program 'a.exe' has more than one
entry point defined: 'yyy.Main()'
The error proves the point that
you cannot have two different classes, zzz and yyy, containing Main. Only one
occurrence of Main is allowed. You always have only one chance of a lifetime.
Ditto with Main.
a.cs
class zzz
{
private static void
Main()
{
System.Console.WriteLine("hell");
}
}
Output
hell
The access modifiers are not
included within the signature for Main. Even though Main is made private, it is
considered as an entry point function, hence hell gets displayed. This function
is unique and works as a special case.
a.cs
public static void Main()
{
System.Console.WriteLine("hell");
}
class zzz
{
}
Compiler Error
a.cs(1,15): error CS1518: Expected class, delegate, enum,
interface, or struct
You cannot create a function
outside a class or a structure. This rule has to be strictly adhered to even
for Main.
a.cs
public class zzz
{
public static int Main()
{
return;
}
}
Compiler Error
a.cs(5,1): error CS0126: An object of a type convertible to
'int' is required
In this example, the function
Main has to return an int and we are not returning any value with the keyword
return. The compiler tries to convert a nothing into an int and hence the
error. This is a generic error that occurs whenever the compiler tries to
convert a data type into another and is not successful.
a.cs
class zzz
{
public static int Main()
{ }
}
Compiler Error
a.cs(3,19): error CS0161: 'zzz.Main()': not all code paths
return a value
The compiler's error messages
need the services of an editor. The function Main should return an int, but we
forgot to do so. This results in an error. Thus whenever an entity should
return a value, we should return such a data type or be prepared for an error.
Scope
Scope, as a concept, defines the
region within which a user-defined entity is visible and can be referred to.
a.cs
public class zzz
{
public static void Main()
{
int i;
zzz i;
} }
Compiler Error
a.cs(6,5): error CS0128: A local variable named 'i' is already
defined in this scope
Twins are always a problem. We
cannot have any two entities of the same name as the compiler will get confused
on whether we are referring to the i which is an int or a zzz. Thus never have
duplicate names even though at times they are allowed.
a.cs
class zzz
{
public static void Main()
{
{
int i = 8;
}
i = 90;
} }
Compiler Error
a.cs(8,1): error CS0103: The name 'i' does not exist in the
class or namespace 'zzz'
A variable is visible from the
position it is created to the first close bracket. As i has been created within
the inner brackets, it is not available to external code thereafter. Hence the
error.
a.cs
class zzz {
public static void Main()
{
for ( int i=0; i <= 10; i++)
{
}
i = 90;
}
}
Compiler Error
a.cs(6,1): error CS0103: The name 'i' does not exist in the
class or namespace 'zzz'
The variable i has been created
within the for statement; therefore it can be accessed only within the brackets
pertaining to for. The scope of i is limited to the for {}.
a.cs
class zzz
{
void abc()
{
i = 9;
}
public static void Main()
{ }
int i = 4;
}
The compiler does not read a .cs
program in one iteration. If it did so, then the above program would have
resulted in an error as the function abc refers to the variable i which has not
been created for the moment. In the first round, the C# compiler scans the
entire code. In another pass, it reads the code of abc. As the first pass had
already brought i into existence, it does not generate any error and compiles
successfully. We can use a variable of a class before it is declared/created
provided the variable has been declared before the class ends.
a.cs
class zzz
{
void abc()
{
i = 9;
int i = 4;
}
public static void Main()
{ }
}
Compiler Error
a.cs(5,1): error CS0103: The name 'i' does not exist in the
class or namespace 'zzz'
What works for variables in a
class does not apply to functions as well. Within abc, we, first, initialize i
to 9 and on the next line we create it. The compiler is not aware of i's
existence while parsing the first line hence it stops and shows an error. Some
people are born smart, well, our compiler is not one of them.
a.cs
class zzz
{
void abc()
{
i = 9;
int i = 4;
}
public static void Main()
{
}
int i = 7;
}
Compiler Error
a.cs(6,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 'parent or current' scope to denote something else
i as a variable is created in class zzz and is visible to all
members in the class. Within the function abc, we have created another local
variable but with the same name as i. If we are now to refer to i, then are we
referring to the local i or the parent i? The local i in abc hides or prevents any
access to the parent i. This turns out to be unacceptable to C# and hence it
reports an error.
a.cs
class zzz
{
public static void Main()
{
zzz a = new zzz();
a.abc();
a.pqr();
}
int i = 100;
void abc()
{
int i = 10;
System.Console.WriteLine(i);
}
void pqr()
{
System.Console.WriteLine(i);
i = 1;
}
}
Output
10
100
The program does not generate any
compilation error. In function abc, we are allowed to create a variable named
i, similar to the variable i in class zzz. The local variable in abc hides the
parent variable in its function. Initializing i in abc to 10 hence does not
change the value of i in zzz. Also, as there is no variable named i defined in
pqr, i will refer to the variable and value defined within zzz. i now plays two
different roles with different scopes in the same class.
a.cs
class zzz
{
public static void Main()
{
int j = ( j = 1) ;
System.Console.WriteLine(j);
}
}
Output
1
The compiler gives no error as
the variable j is being used on the same line of its creation. It does not
precede it. Had it been like in the previous example, an error would have been
generated. The compiler first reads the line, creates a variable called j, then
initializes it to 1. The () brackets gain precedence.
a.cs
class zzz
{
public static void Main()
{
int j = 1 , i = ++j ;
System.Console.WriteLine(i);
}
}
Output
2
A comma works like a semi-colon.
The compiler treats it as two different statements.
Exceptions
a.cs
class zzz
{
public static void Main()
{
yyy a;
a=new yyy();
try
{
a.abc();
System.Console.WriteLine("Bye");
}
catch
{
System.Console.WriteLine("In Exception");
}
}
}
class yyy
{
public void abc()
{
throw new System.Exception();
}
}
Output
In Exception
When we call a function, we are
not aware of the exceptions and the types of exceptions it will throw. One way
to eradicate this uncertainty is to catch all the exceptions. You can do so by
not specifying any Exception with catch. It is perfectly legal tender to
unspecify the name of the Exception we are trying to catch in the catch.
a.cs
class zzz
{
public static void Main()
{
yyy a;
a=new yyy();
try
{
a.abc();
System.Console.WriteLine("Bye");
}
catch
{
System.Console.WriteLine("In Exception");
}
catch (System.Exception e)
{
System.Console.WriteLine("In Exception");
}
}
}
class yyy
{
public void abc()
{
throw new System.Exception();
}
}
Compiler Error
a.cs(16,1): error CS1017: Try statement already has an empty
catch block
The error message is self-explanatory.
The C# compiler does not permit you to have a catch with no Exceptions followed
by a catch with an Exception. The reason being that the first catch is more
generic and it will catch all the Exceptions. This leaves no exceptions for the
second catch. Reverse the order of the catches and watch the error disappear.
a.cs
class zzz
{
public static void Main()
{
yyy a;
a=new yyy();
try
{
a.abc();
System.Console.WriteLine("Bye");
}
catch (xxx e)
{
System.Console.WriteLine("In Exception" +
e.ToString());
}
}
}
class yyy
{
public void abc()
{
throw new xxx();
}
}
class xxx : System.Exception
{
}
Output
In Exceptionxxx: An exception of type xxx was thrown.
at yyy.abc()
at zzz.Main()
An exception is a class derived
from System.Exception. It is child's play to create an exception. We can create
hundreds of Exceptions, there is no certain limit to it. The reason behind
introducing Exceptions was to simplify the job of identifying errors. Earlier
if our function returned six different types of errors then the method would
return six different numbers. Now, using exceptions it will throw 6 different
objects. All these objects are derived from the System.Exception class.
In the program above, xxx is
derived from System.Exception and contains no code. Function abc throws an
object that looks like xxx hence the catch traps an xxx object while calling
the function. The output is displayed using the ToString function of the
object.
a.cs
class zzz
{
public static void Main()
{
yyy a;
a=new yyy();
try
{
a.abc();
System.Console.WriteLine("Bye");
}
catch (System.Exception e)
{
System.Console.WriteLine("In Exception" +
e.ToString());
}
catch (xxx e)
{
System.Console.WriteLine("In Exception" + e.ToString());
}
}
}
class yyy
{
public void abc()
{
throw new xxx();
}
}
class xxx : System.Exception
{
}
Compiler Error
a.cs(16,8): error CS0160: A previous catch clause already
catches all exceptions of this or a super type ('System.Exception')
System.Exception catches all the
exceptions. It is very similar to having a catch with no Exceptions. It is
advisable to make it the last catch in the series of catches as if it is the
first, it will catch all the exceptions leaving nothing for the other catches.
The earlier explanation holds true in this case too.
Exceptions are said to be a
structured, type safe and a uniform way of error handling. All modern
programming languages support exceptions.
a.cs
class zzz
{
public static void Main()
{
yyy a;
a=new yyy();
try
{
a.abc();
System.Console.WriteLine("Bye");
}
catch (System.Exception e)
{
System.Console.WriteLine("In Exception" +
e.ToString());
}
finally
{
System.Console.WriteLine("In finally");
}
}
}
class yyy
{
public void abc()
{
throw new System.Exception();
}
}
Output
In ExceptionSystem.Exception: An exception of type
System.Exception was thrown.
at yyy.abc()
at zzz.Main()
In finally
And if we comment out the throw
statement.
Output
Bye
In finally
Once an exception is thrown,
control passes to the catch statements. The statements in try following the
function that causes an exception, are never executed. After executing the
catch code, the next executable statement is the one following the try-catch
block. Irrespective of an exception being thrown or not, the statements
following the try catch blocks are executed. The question here is 'What if we
want some code to be called irrespective of whether an exception occurred a
not'. To do so, we simply place the code in finally. The code within this block
unquestionably is executed. A finally will always be called at the end.
a.cs
class zzz
{
public static void Main()
{
int i = 5/0;
}
}
Compiler Output
a.cs(5,9): error CS0020: Division by constant zero
The C# compiler is very
intelligent. It knows that a number cannot be divided by zero and thus stops us
in our tracks with an error.
a.cs
class zzz {
public static void Main()
{
int j = 0;
int i = 5/j;
}
}
It is however not very smart and
in the above case, an exception is generated at run time.
a.cs
class zzz
{
public static void Main()
{
try
{
int j = 0;
int i = 5/j;
}
catch ( System.Exception e)
{
System.Console.WriteLine("Hi " + e.ToString());
}
}
}
Output
Hi System.DivideByZeroException: Attempted to divide by zero.
at zzz.Main()
All exceptions thrown at run time
can be caught using try-catch. In this way, any unforeseen application error
can be caught at run time gracefully.
a.cs
class zzz {
public static void Main() {
yyy a;
a=new yyy();
try
{
a.abc();
try
{
a.pqr();
}
catch (System.Exception e)
{
System.Console.WriteLine("In inner try" +
e.ToString());
}
finally
{
System.Console.WriteLine("In inner finally");
}
System.Console.WriteLine("Bye");
}
catch (System.Exception e)
{
System.Console.WriteLine("In Exception" +
e.ToString());
}
finally
{
System.Console.WriteLine("In finally" );
}
}
}
class yyy
{
public void abc()
{
//throw new System.Exception();
}
public void pqr()
{
throw new System.Exception();
}
}
Output
In inner trySystem.Exception: Exception of type
System.Exception was thrown.
at yyy.pqr()
at zzz.Main()
In inner finally
Bye
In finally
Interesting !!!
Basically, you are allowed to
have as many try-catch blocks in your code as your heart's desire. Every time
you execute a function, that function may throw an exception. So you can place
the function call in a try-catch block. Moreover, within a catch block, you can
have another try-catch which should be considered only when there is a function
call in the catch.
In the program, abc does not
throw any exception but pqr does. The catch that catches it is the inner catch
and not the outer catch. Hence, the outer catch is not called. Conversely, the
finally for both the trys is called, the order is from the inner to the outer
resulting into seeing bye before last. Finally waits for the try-catch to
complete its task.
Namespaces
C# programs are organized using
namespaces which provide an organizational system for presenting programs. A
source file is defined as a compilation unit. A C# program can consist of one
or more compilation units. When we try and compile a C# program, all
compilation units are compiled together and hence can depend on each other.
a.cs
public namespace vijay
{
}
Compiler Error
a.cs(1,8): error CS1518: Expected class, delegate, enum,
interface, or struct
A namespace is explicitly public
and we are not allowed to specify any access modifiers to it.
a.cs
public class zzz
{
public static void Main(string[] a)
{
using System;
}
}
Compiler Error
a.cs(5,7): error CS1003: Syntax error, '(' expected
a.cs(5,13): error CS1026: ) expected
The using directive can only be
placed at the beginning of the program. It cannot be used anywhere else. The
scope of using is the current source file and it does not influence any other
programs included while compiling.
a.cs
public class zzz
{
public static void Main(string[] a)
{
}
}
namespace n1
{
class yyy
{
}
}
namespace n1
{
class yy
{
}
}
Namespaces are open-ended, the
net result here will be that the namespace n1 will contain both classes -- yy
and yyy. However, we cannot change the name of the second class to yyy as we
cannot have the same name in the same namespace.
a.cs
using z = a1.a2.yyy;
using z1 = a1.a2;
public class zzz
{
public static void Main(string[] a)
{
z b;
z1.yyy c;
}
}
namespace a1.a2
{
class yyy {}
}
Using nested namespaces can make
our names too large. We are provided with some help in the form of an alias in
the using directive. In the program, we have created two aliases -- z and z1.
Wherever we write a1.a2.yyy, the full name of the class yyy, it can now be
replaced with z. z1 stands for only the namespace name and thus we have to
specify the name of the class. An alias is conceptually very simple. Wherever
C# sees a z, it will blindly replace it by a1.a2.yyy.
a.cs
using z = a1.a2.yyy;
public class zzz
{
public static void Main(string[] a)
{
}
}
namespace a1.a2
{
class yyy {}
}
class z
{
}
Compiler Error
a.cs(1,7): error CS0576: Namespace '' already contains an
alias definition for 'z'
You cannot have a class name and
an alias with similar names as this will confuse the compiler. Hence the error.
a.cs
namespace a1.a2
{
using z = a1.a2.yyy;
class yyy {}
}
namespace a3
{
class ddd : z
{
}
}
Compiler Error
a.cs(8,13): error CS0246: The type or namespace name 'z' could
not be found (are you missing a using directive or an assembly reference?)
The class zzz remains the same as
before. An alias created in one namespace or compilation unit is simply not
available to another. The scope of z is only limited to namespace a1.a2.
a.cs
using z = a1.a2.yyy;
namespace a1.a2
{
class yyy {}
}
namespace a3
{
class ddd : z
{
}
}
Here we do not get any namespace
error as we have placed the alias in the global or default namespace. It is
available to all namespaces in this source file only.
a.cs
using z = a1.yyy;
namespace a3
{
class ddd : z
{
}
}
Compiler Error
a.cs(1,11): error CS0246: The type or namespace name 'a1'
could not be found (are you missing a using directive or an assembly
reference?)
a.cs(1,11): error CS0246: The type or namespace name 'a1'
could not be found (are you missing a using directive or an assembly
reference?)
C# checks the namespace alias and
tries to resolve it to a sensible entity. If the compiler cannot resolve it, it
gives an error. Here C# is not
knowledgeable of a1.
a.cs
using z = a2.a3;
namespace a2.a3
{
}
namespace a3
{
using z = a2;
class ddd : z.yyy
{
}
}
Compiler Error
a.cs(8,15): error CS0234: The type or namespace name 'yyy'
does not exist in the class or namespace 'a2' (are you missing a using
directive or an assembly reference?)
The namespace alias can be hidden
by another namespace alias. Globally z is a2.a3 but within the namespace a3 it
is temporarily given a value of a2. Thus the error reads a2.yyy and not
a2.a3.yyy. If we comment the inner alias, the compiler evaluates z to be
a2.a3.yyy.
a.cs
namespace a2.a3
{
}
namespace a3
{
using z = a2;
using z1 = a2.a3;
using z2 = z.a3;
}
Compiler Error
a.cs(8,12): error CS0246: The type or namespace name 'z' could
not be found (are you missing a using directive or an assembly reference?)
The above error comes in as C#
interprets only one using at a time. This means that C# turns a blind eye to
the other using's. The order of usings is of no relevance. We cannot
incorporate an alias within another. All of them have to be self contained with
no dependencies.
a.cs
using a2;
using a3;
class zzz
{
public static void Main()
{
aaa a;
}
}
namespace a2
{
class aaa {}
}
namespace a3
{
class aaa {}
}
Compiler Error
a.cs(7,1): error CS0104: 'aaa' is an ambiguous reference
The reason we get an error is
because in Main, aaa can either become a2.aaa or a3.aaa. As both namespaces a2
and a3 have a class aaa, both match and hence the error.
Changing using a2 to using a3,
will give us only warnings as duplicate usings are not flagged as an error. The
class aaa now becomes a3.aaa and there is no ambiguity.
a.cs
namespace aa
{
namespace bb
{
class cc{}
}
}
namespace aa.bb
{
class cc{}
}
Compiler Error
a.cs(10,7): error CS0101: The namespace 'aa.bb' already
contains a definition for 'cc'
We cannot have a class cc in
namespace aa.bb as it already exists within the namespace bb under namespace
aa. The namespaces are additive, so it finally becomes aa.bb. The fully
qualified name for the existing class cc is aa.bb.cc. Thus namespace aa.bb can
be viewed as a namespace aa containing a namespace bb.
Variables
Variables are always stored in
memory as they represent a memory location. They cannot be stored in your or my
pockets. The name of a variable is always decided by consulting an astrologer.
This is because you always need someone to take the flak for your programs not
working, so why not blame it on a poor choice of variable names? Remember, in
this book, we decide names of variables, in your life let your loved ones
decide!
Every human being has to have
some properties associated with him or her. One of these could be, say, weight.
In the same way every variable has to have a data type associated with it, that
informs C# about the values that can be stored in it. C# is very finicky about
what you store in it, similar to people who are very finicky about the clothes
they wear. The C# compiler guarantees that the variable will never ever have a
value other than what is permitted by its data type. Big brother is always
watching over your shoulder!
Variables in C# fall in two
states, initially assigned and initially unassigned. An initially assigned
variable has a defined value, an initially unassigned variable does not.
Complex words but a simple explanation.
If you have been counting, C# has
so far created seven types of variables for us. These are: static, instance,
array, parameter types like value, reference and output and finally local
variables. Let us learn something more about each one of them.
a.cs
public class zzz {
public static int i1;
int i2;
byte [] i3;
void abc(int i4, ref int i5, out int i6)
{
int i7 ;
i6=10;
}
public static void Main()
{
}
}
First, a quick recap. i1 is a
static variable due to the keyword static. i2 is an instance variable as it is
created outside a function and i3[0] is an array element if we initialize the
array member using new. Parameters by default are passed by value like i4.
Variables like i5 and i6 are ref and out respectively due to the modifiers.
Also, as i7 is created within a function, it is called a local variable.
A static variable is born at the
time the class is loaded and is alive and kicking as long as the program runs.
A static variable is initially assigned which means that it gets the default
values of its data type.
All other variables created by
themselves in a class are called instance variables. An instance variable comes
to life only when an instance of that class is created using the keyword new.
The instance variable dies when there are no more references to the object. In
languages like C#, like in real life, we have no control over death. Thus, instance
variables die whenever C# feels that it has no use for them. The programmer is
powerless to kill an instance variable.
An instance variable is initially
assigned which means that it gets the default values of its data type. The
above line was simple cut and paste from the above paragraph. The only change
made was that the word instance was replaced by static. Good writers can waste
many words writing gibberish. A structure is always created on the stack. Thus,
an instance variable created in a structure, has the same life-time as the
structure. If the structure is initially assigned so are the instance variables
and vice versa.
Array members behave in the same
way as instance members so we see no point in explaining the same concept using
different words and consuming more paper and cutting more trees. We apologize
but had to have the above explanation spread over at least three lines without
repeating ourselves.
A value parameter is assigned a
value at the time of function invocation. The variable is alive at the open
brace { and dies at the close brace }. A reference variable unlike a value
variable does not create a new memory location. It simply stands for a
reference to the original object. It represents the original memory location of
the object in the function invocation.
Within a structure, the keyword
this is passed by reference. A reference variable has to be assigned a value on
invocation unlike the out parameter. The out parameter is similar to the ref in
many aspects. Both have the same value as the underlying variable. We cannot
initialize an out variable at the time of invocation but must initialize it at
the time of exit. Also, as it is initially unassigned, we cannot read its value
in the function. The keyword 'this' in the constructor of a struct is an out
parameter.
A local variable is created at
the open { and dies at the close }. A local variable is never initialized and
thus has no default value.
a.cs
public class zzz
{
public static void Main()
{
int i;
System.Console.WriteLine(i);
}
}
Compiler Error
a.cs(6,26): error CS0165: Use of unassigned local variable 'i'
A local variable is initially
unassigned.
a.cs
public class zzz
{
public static void Main()
{
i = 10;
int i;
}
}
Compiler Error
a.cs(5,1): error CS0103: The name 'i' does not exist in the
class or namespace 'zzz'
We cannot refer to a variable
before it is created and if we do so, the above error is reported. Had i been
an instance variable and created even after Main, it would not have resulted in
an error.
a.cs
public class zzz
{
public static void Main()
{
i = 10;
}
static int i;
}
Compiler Warning
a.cs(7,12): warning CS0169: The private field 'zzz.i' is never
used
All that we get from C# is a
benign warning saying that we are not using the variable i. From the compiler
vocabulary, initializing a variable is not similar to using a variable. At times, the garbage collector responsible
for the murder of variables may decide to terminate it before the close } as it
is not being referred to. This is perfectly legal in C#. Thus a variable may
die an earlier death, it is all at the mercy of the great C#.
As a repetition, static, instance
and array members all have default values. For a reference variable, the
default value is null.
a.cs
public class zzz
{
public static void Main()
{
System.Console.WriteLine("." + i + ".");
}
static yyy i;
}
class yyy
{
}
Compiler Warning
a.cs(7,12): warning CS0649: Field 'zzz.i' is never assigned
to, and will always have its default value null
Output
..
i is a variable of a reference
type. The warning very clearly informs us that C# has detected a variable i,
which has not been initialized to any value hence it will be null. Null means
no value and cannot be displayed. Null is not even a space and hence the two
dots have nothing separating them.
Arrays
An array is also known as a data
structure in formal computer science books. In our view, a rose by any other
name is perceived to smell as sweet no matter what name you call it by. An
array will normally contain more than one variable called the elements of the
array. An array element is accessed using a number in square brackets known by
a more difficult name -- a computed index. Unfortunately, all the members of an
array must have the same data type called the array type. The value we supply
to the keyword new in square brackets is the size of the array or as termed in
the array world, a rank specifier.
In Book 1, we have seen only
single-dimensional arrays or arrays of rank one. In case of more than one
index, we call it a multi-dimensional array. The array dimension is represented
by a value, which must be larger than or equal to zero. This value is defined
as length and must be positive. It does not have a compile time value but
receives a value at run time.
a.cs
class zzz
{
public static void Main()
{
int [] a;
a = new int[];
}
}
Compiler Error
a.cs(6,14): error CS1586: Array creation must have array size
or array initializer
During the act of creating an
array, the size must be specified.
a.cs
class zzz
{
public static void Main()
{
int [] a;
a = new int[0];
}
}
It is valid or perfectly legal to
give a size of 0. The array, however, has no practical value if you do so.
a.cs
class zzz {
public static void Main()
{
int [] a;
a = new int[10];
a[11] = 10;
}
}
Output
Exception occurred: System.IndexOutOfRangeException: An
exception of type System.IndexOutOfRangeException was thrown.
at zzz.Main()
If you exceed the bounds of an
array, you will not get any compile time error but unfortunately you land up to
a run time error. C#, in other words, is extremely concerned about going beyond
the scope of the array. The exception thrown can be caught by your program.
Once an array has been created,
its length cannot be altered any more. We are not allowed to resize the dimensions.
Everything is fixed.
a.cs
class zzz
{
public static void Main()
{
int [] a;
a = new int[10];
a = new int[1];
}
}
The first array remains
inaccessible in the program as array a has been redefined as one element array.
Once an array has been created it cannot be resized later.
All arrays are derived from
System.Array, which is an abstract class.
a.cs
class zzz
{
public static void Main()
{
string [] a;
a = new string[10];
a[1] = 9;
}
}
Compiler Error
a.cs(7,8): error CS0029: Cannot implicitly convert type 'int'
to 'string'
The conversion rules applying to
data types also apply to an array.
a.cs
class zzz
{
public static void Main()
{
int [] a = new int[3]
{0,4,8};
int [] b = new int[]
{0,4,8};
int [] c = {0,4,8};
}
}
There are many ways to skin a
cat. In the same way, we can initialize an array in different ways. The array a
has been initialized the long way where we have specified the keyword new with
the size of the array along with the initial values in {} braces. In the case
of b, we've skipped the length and directly supplied three values. The length
of this array becomes 3. In the third case, we have done away with the new
altogether.
a.cs
class zzz
{
public static void Main()
{
int [] a = new int[5]
{0,4,8};
}
}
Compiler Error
a.cs(5,24): error CS0178: Incorrectly structured array
initializer
We cannot create a larger array
and supply fewer values in the {} brackets. They have to be the same. Not one
more, not one less.
a.cs
class zzz
{
public static void Main()
{
int[,] b = {{0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}};
System.Console.WriteLine(b.Length);
foreach ( int i in b)
System.Console.Write(i + " ");
}
}
Output
10
0 1 2 3 4 5 6 7 8 9
We have created a two
dimensional array having a length of five for the innermost rank and 2 for the
outermost rank. It is similar to writing
'b = new int[5,2]' and then initializing the individual members; b[0,
0]=0, b[0, 1]=1 etc. The member Length in System.Array, inherited by b displays
10 as the total number of members in the array are 5*2. The foreach construct
then displays every member in the array.
a.cs
class zzz
{
public static void Main()
{
int j = 3;
int[] b = new int[j];
System.Console.WriteLine(b.Length);
}
}
Output
3
Irrespective of what the
documentation has to say, you are allowed to use a variable to initialize an
array. We have used the variable j to create b.
int [][,,][,] a, is a
three-dimensional array consisting of two-dimensional arrays which in turn are
of single-dimensional arrays each. The value stored in each element will be an
integer.
a.cs
class zzz
{
public static void Main()
{
int b[];
}
}
Compiler Error
a.cs(5,6): error CS0650: Syntax error, bad array declarator.
To declare a managed array the rank specifier precedes the variable's
identifier
a.cs(5,7): error CS1525: Invalid expression term ']'
a.cs(5,8): error CS1003: Syntax error, ']' expected
The array square brackets must
come before the name of the variable and not after. These are issues of syntax
and do not follow any coherent pattern. The above line would be a valid C
syntax.
a.cs
using System;
public class zzz
{
public static void Main(string[] a)
{
byte[][] s = new byte[5][];
System.Console.WriteLine(s.Length);
for (int i = 0; i < s.Length; i++)
{
s[i] = new byte[i+3];
}
for (int i = 0; i < s.Length; i++)
{
Console.WriteLine("Length of row {0} is {1}", i,
s[i].Length);
}
}
}
Output
5
Length of row 0 is 3
Length of row 1 is 4
Length of row 2 is 5
Length of row 3 is 6
Length of row 4 is 7
A jagged array has a variable
number of members. s is a jagged array with 5 members. Each of these 5 members
can be made up of an array of any size. In the first for, we are initializing
s[0], the first member, to an array of bytes which is 3 large. In the second
iteration of the for, s[1] now points to an array of bytes having 4 members.
Thus, in the second round, s[1].Length displays 4.
a.cs
using System;
public class zzz
{
public static void Main(string[] a)
{
int[][] n8 = new int[2][] { new int[] {2,3,}, new int[]
{5,6,7,8,9} };
int[][] n9 = new int[][] { new int[] {2,3,4}, new int[] {5,6,7,8}
};
}
}
And one more example for the road
to ponder on. This shows the levels of complexities that you can delve in.
a.cs
class zzz
{
public static void Main()
{
int [] a = {1,3};
zzz b = new zzz();
b.abc(a);
b.abc(new int[]{4,5});
System.Console.WriteLine(a[1]);
}
public void abc( int [] z)
{
System.Console.WriteLine(z[1]);
z[1] = 100;
}
}
Output
3
5
100
You can give an array as a
parameter to a function. This can be done in two ways, either by stating an
array name explicitly or by creating an array at the time of creating the
function. There is no way on earth the function can ever know or care on the
method you have adopted. However, an array is passed by reference and any
changes made through z will reflect the values in the original array.
In the second case, even though
the changes get incorporated, there is no way of accessing the array, as we
never gave it a name. Change the function abc by adding the params keyword.
This is as shown below.
public void abc(params int [] z)
and the above program runs
exactly the same as earlier. This proves that a params keyword is nothing but
an array parameter.
a.cs
class zzz
{
public static void Main()
{
int [] a = {1,3};
zzz b = new zzz();
b.abc(a);
b.abc(a,a);
}
public void abc(int [] y, params int [] z)
{
System.Console.WriteLine(z.Length);
}
}
Output
0
2
There is, however, a major
difference between an array and a params parameter. If the parameter is an
array then no other data type can be provided other than an array. Even a
single int has to be passed as an array. However, a params modifier is more
flexible as we are allowed to give individual data types. These are converted
into an array. A params parameter can also be null as in the first invocation
of abc. We can either pass an array or as many individual ints we desire to the
second parameter.
a.cs
class zzz
{
public static void Main()
{
int [] a = {1,3};
zzz b = new zzz();
b.abc(out a);
System.Console.WriteLine(a[2]);
}
public void abc(out int [] z)
{
z = new int[3]{1,4,6};
}
}
Output
6
With an array, we can also use
the modifiers of ref and out. The difference between them is that in the out
modifier, we have to recreate an array within the function despite having
initialized it earlier. An out modifiers demands new entities to be created
inside the function. If the data type is an array, so be it, you must create it
and if we do not initialize it, the value is automatically initialized to zero.
Apart from this distinction, the behaviour followed is like a normal ref
entity. Like any normal object, a variable needs to be initialized before using
it but in case of an out, if the array is not initialized, then it takes the
default values of the data type.
An array can be initialized to a
null if need arises as a null may mean no value. This also means that someone
somewhere gave it no value.
a.cs
class zzz
{
public static void Main()
{
int [] a = new int[3]{1,2};
}
}
Compiler Error
a.cs(5,22): error CS0178: Incorrectly structured array
initializer
Whenever we initialize an array,
the size of the array stated in the new and the values to be initialized must
be the same in number. In the above case, we are creating an array of three
ints and supplying values to initialize only two of them. We thought, wrongly
so, that the last member a[2] would be initialized to zero. The compiler proved
us wrong by giving us an error instead.
a.cs
class zzz
{
public static void Main()
{
int [] a = new int[3];
System.Console.WriteLine(a[0] + "." + a[1]);
}
}
Output
0.0
An array type is always
initialized to the default value of its type. In the above case the default
value is zero as the type of the array is an int.
a.cs
class zzz {
public static void Main()
{
int i = 0;
int [] a = new int[3]{i++,i++,i++};
System.Console.WriteLine(a[0] + "." + a[1] +
"." + a[2]);
}
}
Output
0.1.2
No astonishing facts for a
function! Same rules apply here too. No side effects at all. Each
initialization is done independent of each other and starting from left to
right.
a.cs
class zzz
{
public static void Main()
{
yyy a1 = new yyy(1);
yyy b1 = new yyy(10);
yyy c1 = new yyy(100);
int [] a = new int[3] ;
try
{
a = new int[3]{a1,b1,c1};
}
catch ( System.Exception e)
{
System.Console.WriteLine(a[0] + "." + a[1] +
"." + a[2]);
}
System.Console.WriteLine(a[0] + "." + a[1] +
"." + a[2]);
}
}
class yyy
{
public int i;
public yyy(int j)
{
i = j;
}
public static implicit operator int ( yyy z)
{
System.Console.WriteLine("op " + z.i);
if ( z.i == 10)
throw new System.Exception();
return z.i;
}
}
Output
op 1
op 10
0.0.0
0.0.0
We create an array of ints and
initialize them to objects that smell like yyy's. There is no problem in doing
so as the operator int converts the yyy object into an int.
For the object b1 whose instance
variable i has a value 10, we throw an exception. Due to this, the compiler at
run time undoes the initialization of array member a[0] to 1 and it falls back
to the earlier default value of zero. This is the first task it performs on
exceptions. The array members have to be initialized as the initialization
statement is in a try block. If we fail to do so, we get an 'uninitialized
variable error'. In this way, we fool the compiler into doing our bidding.
The array members are, by
default, initialized to zero before they get their new values. The array access
element must not be less than zero or greater than the size of the array, or
else an exception is thrown. An initialization statement of int [] a = new int[10][] creates an array of
ten members and each of them in turn is considered to be an array. Each of the
10 arrays being reference types is initialized to a value null. We cannot,
however, do the following.
a.cs
class zzz
{
public static void Main()
{
int [][] a = new int[3][2];
}
}
Compiler Error
a.cs(5,25): error CS0178: Incorrectly structured array
initializer
a.cs(5,26): error CS1002: ; expected
a.cs(5,26): error CS1525: Invalid expression term ']'
Ha Ha Ha!!! We are overjoyed as
we confused the compiler to no end. One error on our part and the compiler
bought the entire cavalry out by giving us three errors instead. We are
forbidden to initialize a sub array at the time of creation, hence the error.
They are to be initialized later and individually, as follows.
a.cs
class zzz
{
public static void Main()
{
int [][] a = new int[3][];
a[0] = new int[1];
a[1] = new int[10];
System.Console.WriteLine(a.Length + " " + a[0].Length +
" " + a[1].Length );
}
}
Output
3 1 10
The size of the array a is 3 and
it contains three members which are an array of ints. The first one, a[0]
contains only one int and the second a[1] has 10 ints. Not permitting the
initialization of sub arrays at the time of creation brings in flexibility as
we can now initialize each of them to any size we wish rather having the same
size. In life also, one size does not fit all ever.
A rectangular array is an array
of arrays where all the sub arrays have the same length. This is more efficient
than a multi dimensional array. If we write new int[10][] and initialize the
sub arrays to new int[5], we are creating one main array and 10 sub arrays.
However, if we write int [,] a = new int[10,5] we are only creating one array
in all and also it uses only one statement. Compare and contrast it with the
above and choose the one you are most comfortable with. It is finally a
personal decision.
Parameter
arrays
When we use a params parameter in
its normal form, the data type to be specified must be of a type that can be
directly or implicitly converted to the parameter array type. A similar thing
happens with a value parameter. In the expanded form, we do not use an array
but pass the parameters individually, which then is converted into an actual
array. It is the compiler's job to create an array of the same data type and
width and then initialize the members with the values stated as parameters.
Finally, it uses this array as the params parameter to the function.
Thus, if we had a simple function
abc (params object [] a) and we called the function abc as
abc(1,2,"hell"), then an array would be created as new object[] {
1,2,"hell"}. If the function is called with no parameters, then the
array becomes an empty array. An array is created after all.
a.cs
class zzz
{
public static void Main()
{
int [] a = new int[0];
}
}
There is nothing to feel sorry
about when we create an empty array.
a.cs
class zzz
{
public static void Main()
{
zzz a = new zzz();
int i = 10;
a.abc(i);
System.Console.WriteLine(i);
}
public void abc(object a)
{
System.Console.WriteLine(a);
a = 100;
}
}
Output
10
10
When calling the function abc, if
we do not specify ref or out, then there is no way in hell that the value of i
can be changed within the function. In the above case, we have upgraded our
value parameter, i, to an object using a method called boxing. In spite of it
being a reference type, a, which is an instance of class object, will not
reflect any changes made within the function abc in Main. If you do not believe
us, cast i to an object explicitly and yet nothing changes.
The
Other Odds and Ends
a.cs
class zzz
{
public static void Main()
{
int [] a = new int[10];
a[1,2] = 10;
}
}
Compiler Error
a.cs(6,1): error CS0022: Wrong number of indices inside [],
expected '1'
A one- or a single-dimensional
array can only have one indice and not 2. The error tells us that the compiler
checks whether we have given the right number of indices but it does not check
whether we have exceeded the bounds of the array. All this is part and parcel
of the array error handling enforced by the compiler. It is unfortunately very
selective in the type of error handling and some common errors go undetected.
a.cs
struct zzz
{
public static void Main()
{
System.Console.WriteLine("hi");
}
}
Output
hi
There is no rule in the world
that mandates the Main function to be placed only in a class. Here we placed it
in a structure. Main is the last function in the world to complain.
You cannot place functions
anywhere else in your program. All the rules binding other functions apply to
Main also.
a.cs
class zzz
{
public static void Main()
{
System.Console.WriteLine("hi in zzz");
}
}
class yyy : zzz
{
public new static void Main()
{
System.Console.WriteLine("hi in yyy");
}
}
Compiler Error
a.cs(3,20): error CS0017: Program 'a.exe' has more than one
entry point defined: 'zzz.Main()'
a.cs(10,24): error CS0017: Program 'a.exe' has more than one
entry point defined: 'yyy.Main()'
In the above program, we have two
functions called Main. This results in an error. Hey, but wait a minute, they
are in separate classes, so shouldn't it be acceptable? The compiler however
does not think so. It does not like this specific function, Main being repeated
twice in separate classes. We cannot use the modifiers virtual, new or
override, as the function is static. Always follow the simple dictum, only one
love, so only one function called Main!
a.cs
class zzz
{
public static void Main()
{
yyy a = new yyy();
a.aa = 10;
a.abc(ref a.aa);
}
}
class yyy
{
public int aa
{
get {return 0;}
set {}
}
public void abc( ref int b)
{
}
}
Compiler Error
a.cs(7,11): error CS0206: A property or indexer may not be
passed as an out or ref parameter
If it walks like a duck, quacks
like a duck, it's a duck. A property looks and feels like a variable but it is
actually a series of functions or to be technically correct, accessors. Only a
storage or memory location can be passed as a ref or an out parameter. A
property is not allocated any memory and thus cannot be used wherever a
variable can be used. All the restrictions of static apply to static properties too. The get accessor must terminate
either in a return or a throw statement. Control cannot flow off the accessor
body at the end. A set accessor is like a function that returns void and it has
an implicit parameter value.
a.cs
class zzz
{
public static void Main()
{
yyy a = new yyy();
a.aa = 10;
System.Console.WriteLine(a.aa);
}
}
class yyy
{
public int j,k = 0;
public int aa
{
get
{
System.Console.WriteLine("get " + j);
if ( k == 0)
{
aa = aa + 1; // aa++
k = 1;
}
return j;
}
set
{
j = value;
System.Console.WriteLine("set " + j);
}
}
}
The above program does not give
any compiler error. We are allowed to change the property value, within a
property. If you do so, the program goes into an indefinite loop. This is
regardless of the fact that by using the value of variable we ensure that the
statement aa++ is executed only once.
a.cs
class zzz
{
public static void Main()
{
yyy a = new yyy();
a.aa = 10;
System.Console.WriteLine(a.aa);
}
}
class yyy
{
public int j,k = 0;
public int aa
{
get
{
System.Console.WriteLine("get1 " + j);
aa = 4;
System.Console.WriteLine("get2 " + j);
return j;
}
set
{
j = value;
System.Console.WriteLine("set " + j );
}
}
}
Output
set 10
get1 10
set 4
get2 4
4
We all have been dumb many a
times in life. One proof lies in the above program. Here in the get accessor of
property aa, we are calling the set accessor by giving the statement aa = 4. No
indefinite loop but crazy answers!! The long and short story for that matter
is, do not call the get or set accessor with a property directly.
An indexer is similar to a
property other than the fact that the indexers, accessors take or accept
parameters. A property is identified by a name, an indexer by its signature.
a.cs
class zzz
{
public static void Main()
{ }
}
class yyy
{
public static int this[int i]
{
get {return 0;}
set {}
}
}
Compiler Error
a.cs(8,19): error CS0106: The modifier 'static' is not valid
for this item
Unlike a property, an indexer
cannot be static. A property cannot be accessed like an array or by element
access, similar to an indexer. A get and a set accessor share the same
parameter list that the indexer has. A property has no such luck and no parameters
at all but the set receives the implicit parameter value.
In an interface, the only job of
an indexer is to declare the signature. Interfaces are smart. They cannot carry
code. All that they do is boss around. They decide the signatures of functions,
indexers etc. so that other people who implement them, follow the rules built
in them.
a.cs
namespace aaa
{
int i;
}
Compiler Error
a.cs(3,1): error CS0116: A namespace does not directly contain
members such as fields or methods
The compiler is very particular
about what you place where. We are not allowed to place methods and fields
directly in a namespace or outside a namespace. Nothing stops us from placing
them in a class. These are the regulations to be followed in the compiler
world.
a.cs
namespace aaa
{
public class zzz
{
public static void Main()
{
aaa a;
}
}
}
Compiler Errors
a.cs(7,1): error CS0118: 'aaa' denotes a 'namespace' where a
'class' was expected
Wouldn't you be surprised if you
see a tiger instead of a dog? Similarly, we surprised the compiler. It expected
a class/type name and instead comes by the name of a namespace. The above error
is a generic error. If we give the name of a property instead of a class, the
error number will be the same, but the message would change. A single error to
take care of multiple possibilities. The generic rule clearly states that a
CS0118 error number is emitted whenever the compiler gets something that it
does not expect.
a.cs
namespace aaa
{
namespace bbb.ccc
{
}
}
Compiler Error
a.cs(3,11): error CS0134: Cannot use qualified namespace names
in nested namespace declarations
We can have namespaces within
namespace with nary a nod from the compiler. What we cannot have is a qualified
namespace within another namespace. Why? Ask the designers of the language.
a.cs
class zzz
{
}
using System;
Compiler Error
a.cs(4,1): error CS1529: A using clause must precede all other
namespace elements
We all wanted to come in class
but using always beat us to the draw. We have no choice but to start our code
with the using clause. This is a rule imposed upon us by the top brass and
obey, we must.
a.cs
new class zzz { }
Compiler Errors
a.cs(1,1): error CS1530: Keyword new not allowed on namespace
elements
You cannot use new on namespace
elements like class or struct. This is because we create them in a global
namespace.
a.cs
using aa = System;
using aa = System;
Compiler Error
a.cs(2,7): error CS1537: The using alias 'aa' appeared
previously in this namespace
The compiler hates seeing two of
anything even though they are the same and cause no trouble. Thus, two using's
are out whether they are the same or different. We are not allowed to even
change our mind.
a.cs
class zzz
{
public static void Main()
{
string s = @”h
i”;
System.Console.WriteLine(s);
}
}
Output
h
i
The @ sign lets you start a
string with ". The single change is that you can press enter and move to
the next line, ending the string there. The compiler doesn’t show any errors.
a.cs
class zzz
{
int i = {1};
}
Compiler Error
a.cs(3,9): error CS0622: Can only use array initializer
expressions to assign to array types. Try using a new expression instead.
The array notation can only be
used with the array data type. The {} brackets are to be used exclusively with
arrays only.
a.cs
class zzz
{
public int[] a = { 2, 3, {4}};
}
Compiler Error
a.cs(3,26): error CS0623: Array initializers can only be used
in a variable or field initializer. Try using a new expression instead.
We cannot use the {} brackets to
initialize an array within an array. For that we have to use the keyword new
instead. The {} can only be used for a single dimensional array.
a.cs
public class zzz
{
int a[2];
}
Compiler Error
a.cs(3,6): error CS0650: Syntax error, bad array declarator.
To declare a managed array the rank specifier precedes the variable's
identifier
There is lots that the C#
language has copied from C and C++ and along the way, it has changed the syntax
of a few in the language. The array brackets must come before the name of the
variable and we are not allowed to state the size of the array within them
either. All this worked very well in C/C++, C# in this case looks a lot like
Java.
a.cs
class zzz
{
public static void Main(string a [] )
{
}
}
Compiler Error
a.cs(3,34): error CS1552: Array type specifier, [], must
appear before parameter name
Array brackets [] can only be in
one place. This is before the name of the array, not after. God alone knows
why?!
a.cs
class zzz
{
public static void Main()
{
int[] a = {1,2,3};
foreach (int in a)
{
}
}
}
Compiler Error
a.cs(6,14): error CS0230: Type and identifier are both
required in a foreach statement
The foreach has a certain syntax and it lets you iterate over an object. Thus it requires the name of a variable that will contain the individual values of the collection object. We failed to give the int variable a name and hence got saddled with an error.