Csharp Programming
Csharp Programming
by
Willi-Hans Steeb
International School for Scientific Computing
and
E.J. Dembskey
Version: 2008-08-28
steeb_wh@yahoo.com
steebwilli@gmail.com
whsteeb@uj.ac.za
evan.dembskey@gmail.com
demskeye@tut.ac.za
Contents
1 Introduction 1
2 CSharp Basics 6
2.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2 Basic Data Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.3 ASCII Table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.4 Arithmetic Operations . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.5 Control Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.6 Logical Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.7 Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.8 Recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.9 Jump Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.10 Pass by Value, Pass by Reference . . . . . . . . . . . . . . . . . . . . 21
2.11 Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.12 Bitwise Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.13 Shift Operation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.14 Commmand-Line Arguments . . . . . . . . . . . . . . . . . . . . . . . 29
2.15 Boxing and UnBoxing Types . . . . . . . . . . . . . . . . . . . . . . . 29
2.16 Delegates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.17 Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.18 Reflection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.19 Generics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4 Built-in Classes 41
4.1 DateTime Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.2 Array Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
4.3 ArrayList Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.4 ListDictionary Class . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
4.5 Class IEnumerator . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
4.6 Mathematics Class and Random Class . . . . . . . . . . . . . . . . . 45
i
4.7 Point Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
4.8 Class BitArray . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
4.9 Object Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
5 Object-Oriented Programming 55
5.1 Write your own class . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
5.2 Override Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
5.3 Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
5.4 Overloading Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
5.5 Operator Overloading . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
5.6 Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
5.7 Delegates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
7 Graphics 86
7.1 Drawing Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
7.2 Color Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
7.3 Button and EventHandler . . . . . . . . . . . . . . . . . . . . . . . . 91
7.4 Displaying Images . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
7.5 Overriding OnPaint . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
8 Events 100
11 Remoting 137
11.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
13 ASP.NET 152
13.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
13.2 State Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
13.3 Page Load . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Bibliography 155
Index 155
iii
Preface
The book gives a collection of C# programs.
Without doubt, this book can be extended. If you have comments or suggestions,
we would be pleased to have them. The email addresses of the author are:
whsteeb@uj.ac.za
steeb_wh@yahoo.com
http://issc.uj.ac.za
iv
Chapter 1
Introduction
CSharp is designed for the .NET framework. The .NET framework is object ori-
ented. CSharp has a great set of tools for the object oriented programmer. CSharp
is standardised as ECMA-334 by ECMA International and as ISO/IEC 23270 by
ISO/IEC. CSharp is the first component oriented language in the C/C++ family.
Component concepts are first class:
CSharp can be embedded in web pages (ASP.NET). In C++ and Java primitive
date types (int, double, etc) are magic and do not interoperate with objects.
In Smalltalk and Lisp primitive types are objects, but at great performance cost.
CSharp unifies this with no performance cost. CSharp also adds new primitive data
types, for example decimal. Collections work for all types.
1
2 CHAPTER 1. INTRODUCTION
All types in the system are derived from class object. The class object contains
the functions string ToString() and bool Equals() which should be overriden
when we write our own class.
CSharp has built in support for events. This is useful for dealing with objects in an
event driven operating system. More than one type can register interest in a single
event. A single type can register interest in any number of events.
CSharp supports interfaces using the interface keyword. Our types can implement
interfaces. We must implement all methods. Interfaces can contain methods but no
fields with properties and events included.
CSharp also provides type conversion, for example if char c = ’a’; we can convert
to an integer int i = (int) c; via the ASCII table.
The names given to variables, methods etc. by the programmer are referred to as
identifiers. An identifier has to: begin with a letter, or begin with an underscore.
It cannot be the same as built-in keywords. Identifiers are case-sensitive.
The following is a list of keywords. Keywords are reserved identifiers that hold a
special meaning to the CSharp compiler. We can use the same name as a keyword
identifier as long as we prefix the name with an @ symbol.
3
Do avoid using class names duplicated in heavily used namespaces. For example,
do not use the following for a class name:
There are a number of preprocessor directives available. While the compiler lacks
a separate preprocessor, directives are processed as if there was one. They aid in
conditional compilation. Unlike with C and C++, these directives cannot be used
to create macros.
#if #else
#elif #endif
4 CHAPTER 1. INTRODUCTION
#define #undef
#warning #error
#line #region
#endregion #pragma
#pragma warning #pragma checksum
C# cannot be run without installing the .NET Framework. The .NET Framework is
installed automatically when Windows Server 2003, Windows XP SP2, Vista or later
is installed. It is also installed when Visual Studio 2003, 2005 or 2008 is installed.
The .NET Framework is also available as a separate download from Microsoft.
The current version of the .Net Framework is version 3.5, but many machines still
run 1.1, 2.0 and 3.0.
The .NET Framework consists of several components, the most important of which
are the runtime and Class Library. See Table 1.1 for an overview of the stack.
Version 3.5 adds several capabilities to the stack: the Windows Presentation Foun-
dation (WPF), Windows Communication Foundation (WCF), Windows Workflow
Foundation (WF), Windows CardSpace and Language Integrated Query (LINQ).
SilverLight is a web-based subset of WPF.
The runtime, or Common Language Runtime (CLR) provides a virtual machine for
the execution of .NET code. In addition, CLR provides services that include mem-
ory management, thread management, component lifetime management, garbage
collection and default error handling. A benefit of the CLR is that it provides these
execution services to all .NET applications without any additional effort on the part
of the programmer.
When a .NET program is compiled, the compiler generates an .exe file. However, this
.exe file does not contain directly executable instructions. Instead it contains Com-
mon Intermediate Language (CIL). When the .exe is executed it does not interact
5
with the operating system directly but with the CLR. This is similar to Java, with
CIL being analogous to bytecode and CLR with the Java Virtual Machine(JVM).
When a .NET application is first executed, the CLR performs a compile of the CIL
and creates a directly executable file from it. This is known as Just In Time com-
pilation. This theoretically overcomes the performance overhead associated with
using virtual machines.
For example, the System namespace is at the top of the namespace hierarchy. It
contains all the types that represent the base data types such as text, numbers and
dates. The Systems.Windows.Forms namespace allows the programmer to take
advantage of the Windows forms engine to create graphical user interface objects
easily.
CSharp Basics
2.1 Introduction
Compile and link the programs with
csc filename.cs
This generates an execute file named filename.exe. This is also known as an exe-
cutable assembly.
In C# everything must be inside a class. Thus also the Main method must be con-
tained inside a class. In the first example this is Hello1. The method name, Main,
is reserved for the starting point of a program. Main is often called the entry point
(starting address) of the program. In front of the word Main is a static modifier.
The static modifier explains that this method works in this specific class only, rather
than an instance of the class. This is necessary, because when a program begins, no
object instances exists.
To avoid fully qualifying classes throughout the program, we can use the using
directive. We write to the screen, where \n provides a newline. Note that C# is
case-sensitive. A comment is indicated by //.
// cshello.cs
using System;
class Hello1
{
public static void Main()
{
Console.WriteLine("Hello Egoli\n"); // \n newline
Console.WriteLine("Good Night Egoli");
Console.Write("midnight");
6
2.1. INTRODUCTION 7
} // end Main
}
The namespace declaration, using System; indicates that we are referencing the
System namespace. Namespaces contain groups of code that can be called upon by
C# programs. With the using System; declaration, we are telling our program that
it can reference the code in the System namespace without pre-pending the word
System to every reference. The System.Console class contains a method
WriteLine()
and a method
Write()
that can be used to display a string to the console. The difference is that the
Console.Write(.) statement writes to the Console and stops on the same line,
but the Console.WriteLine(.) goes to the next line after writing to the Console.
Strings are embedded in double quotes, for example "name"..
We write to the screen using exception handling with try and catch block. To
access command line parameters we change the signature of the Main(void) to
Main(string[] args). The expression string[] args defines an array of strings.
// hello2.cs
using System;
class Hello2
{
public static void Main(string[] args)
{
try
{
Console.WriteLine("Hello {0}",args[0]);
}
catch(Exception e)
{
Console.WriteLine(e);
}
Console.WriteLine("Good Night");
} // end Main
}
Hello2 James
To read from the keyboard we use the method ReadLine(). The command Length
returns the number of elements in the array. We are also using an if-else construct.
// hello3.cs
using System;
class Hello3
{
public static void Main(string[] args)
{
if(args.Length > 0)
{
Console.WriteLine("Hello {0}",args[0]);
}
else
{
Console.WriteLine("Enter your name: ");
string name = Console.ReadLine();
Console.WriteLine("Hello {0}",name);
}
Console.WriteLine("Good Night");
} // end Main
}
In the case of compiling using Visual Studio or some other IDE the problem of the
program’s console window disappearing too quickly to view its output might occur.
This problem can be solved using the Read() method to pause the window. The
program above can be modified like so:
...
Console.WriteLine("Good Night");
Console.Read(); // Pauses the program until a key is pressed
} // end Main
}
bool,
byte, sbyte, char,
short, ushort, int, uint, long, ulong
float, double, decimal
2.2. BASIC DATA TYPES 9
The range of byte is 0..255 and for sbyte is -128..127. short and ushort are 2
bytes (16 bits), where ushort is unsigned. int and uint are 4 bytes. The floating
point number float is 4 bytes and the floating point number double is 8 bytes.
char data type (2 bytes) in C# contains Unicode characters similar to Java. CSharp
is a strongly typed language and therefore variables must be declared with an avail-
able type and must be initialized with a value (or reference) of the same type.
At compile time the C# compiler converts the C# types into their corresponding
.NET types described in the above table. Apart from the above basic types the user
may define his own types using enum, struct and class.
// datatypes.cs
using System;
class Datatypes
{
public static void Main()
{
bool b = true; // boolean data type
Console.WriteLine(!b); // ! is the logical NOT
and the small ’a’ is 97. Furthermore ’0’ is 48 and ’9’ is 57. Space is 32.
We are doing type conversion from char -> int using the ASCII table. The type
conversion operator is (data-type). The null character is ’\0’.
// Escape.cs
using System;
class Escape
{
public static void Main()
{
char c1 = ’\b’; // backspace
int i1 = (int) c1; // ASCII value
Console.WriteLine("i1 = " + i1); // 8
char c6 = ’ ’; // blank
int i6 = (int) c6; // ASCII value
Console.WriteLine("i6 = " + i6); // 32
} // end Main
}
++, --, +, -, *, /, %
12 CHAPTER 2. CSHARP BASICS
Note that we have integer division (for example 17/4 = 4) and floating point division
depending on the data types.
// arithmetic.cs
using System;
class Arithmetic
{
public static void Main()
{
byte c = 255;
c++;
Console.WriteLine(c);
sbyte d = -125; // signed byte
d--;
Console.WriteLine(d);
int r1 = 27;
int r2 = 5;
int r3 = r1/r2; // integer division
Console.WriteLine("r3 = " + r3);
int r4 = r1%r2; // remainder
Console.WriteLine("r4 = " + r4);
double d1 = 3.14159;
double d2 = 4.5;
2.5. CONTROL STATEMENTS 13
double d3 = d1/d2;
Console.WriteLine(d3);
// myIf.cs
using System;
class myIf
{
public static void Main()
{
int i;
Console.WriteLine("Enter integer: ");
string line = Console.ReadLine();
i = System.Convert.ToInt32(line); // convert string of digits to int
if(i > 5) // if true do the next command else skip it
Console.WriteLine("The number is larger than 5");
else
Console.WriteLine("The number is smaller than 5 or equal to 5");
} // end Main
}
// forloop.cs
using System;
class forloop
{
public static void Main()
{
14 CHAPTER 2. CSHARP BASICS
for(i=0;i<x.Length;i++)
{
intsum += x[i]; // shortcut for intsum = intsum + x[i];
}
Console.WriteLine(intsum);
} // end Main
}
Another example for the for loop applied to numbers read from the keyboard. Note
that ! is the logical NOT and ToInt32(string) converts a string of digits to an
integer.
// forloop1.cs
using System;
class ForLoop
{
public static void Main()
{
int sum = 0; // initialize sum to 0
String line;
for(line=Console.In.ReadLine();line!="";line=Console.In.ReadLine())
{
sum += System.Convert.ToInt32(line);
} // end for loop
Console.WriteLine(sum.ToString() + "\n");
} // end Main
}
The foreach loop can be applied to an array of strings. We count how often the
string "otto" is in the array of strings.
2.5. CONTROL STATEMENTS 15
// foreachloop.cs
using System;
class foreachloop
{
public static void Main()
{
string[] namelist = { "willi","otto","carl","john","otto" };
int count = 0;
string n = "otto";
// whileloop.cs
using System;
class whileloop
{
public static void Main()
{
int[] numbers = { 1, 2, 3, 4 };
int i = 0;
int sum = 0;
// dowhileloop.cs
using System;
class whileloop
{
public static void Main()
{
double[] numbers = { 1.1, 2.3, 3.5, 4.5 };
int i = 0;
double sum = 0.0;
do
{
sum += numbers[i];
i++;
}
while(i < numbers.Length);
Console.WriteLine("sum = {0}",sum);
} // end Main
}
If one has a large decision tree and all the decisions depend on the value of the same
variable we use a switch statement instead of a series of if ... else constructions.
The switch statement transfers control to one of several case-labeled statements,
depending on the value of the switch expression. Note that if the break is omitted,
execution will continue over the remaining statements in the switch block. The
break statement can also be used to break out of an iteration loop.
// switch.cs
using System;
class Myswitch
{
public static void Main()
{
string st = "bella";
for(int i=0;i<st.Length;i++)
{
char c = st[i];
switch(c)
{
case ’a’: Console.WriteLine("character is a ’a’ ");
2.6. LOGICAL OPERATIONS 17
break;
case ’b’: Console.WriteLine("character is a ’b’ ");
break;
default: Console.WriteLine("character is not an ’a’ or a ’b’ ");
break;
}
} // end for loop
for(int j=0;j<numbers.Length;j++)
{
switch(numbers[j])
{
case 4: Console.WriteLine("number at position {0} is 4",j);
break;
case 6: Console.WriteLine("number at position {0} is 6",j);
break;
default: Console.WriteLine("number at position {0} is not 4 or 6",j);
break;
} // end switch
} // end for loop
} // end Main
}
// logical.cs
using System;
class MyLogica
{
public static void Main()
{
int j;
Console.WriteLine(" Enter an integer: ");
string line = Console.ReadLine();
j = System.Convert.ToInt32(line);
18 CHAPTER 2. CSHARP BASICS
Console.WriteLine();
int k;
Console.WriteLine("Enter an integer: ");
line = Console.ReadLine();
k = System.Convert.ToInt32(line);
Console.WriteLine();
int n;
Console.WriteLine("Enter an integer: ");
line = Console.ReadLine();
n = System.Convert.ToInt32(line);
2.7 Pointers
A pointer is a data type whose value refers directly to (”points to”) another value
stored elsewhere in the computer memory using its address. Thus the pointer has
an address and contains (as value) an address. Obtaining the value that a pointer
referes to is called dereferencing. The dereference operator is *. Pointers in CSharp
must be declared unsafe.
// Pointers1.cs
using System;
class Pointers
{
2.8. RECURSION 19
// dereferencing pointers
int r = *q;
Console.WriteLine("r = " + r); // 15
}
}
2.8 Recursion
Recursion plays a central role in computer science. A recursive function is one whose
definition includes a call to itself. A recursion needs a stopping condition. We use
recursion to find the Fibonacci numbers.
// recursion.cs
using System;
class recur
{
public static ulong fib(ulong n)
{
if(n == 0) return 0;
if(n == 1) return 1;
return fib(n-1) + fib(n-2);
} // end fib
// mygoto.cs
using System;
class Mygoto
{
public static void Main()
{
Random r = new Random(51);
L3:
int a = r.Next(100);
int b = r.Next(100);
int result;
L1:
Console.WriteLine("{0} + {1} =",a,b);
string s = Console.ReadLine();
result = Convert.ToInt32(s);
if(result == (a+b))
goto L2;
Console.WriteLine("sorry you are not correct: try again");
goto L1;
L2:
Console.WriteLine("congratulations you are correct");
Console.WriteLine("Want to add again: Press y for yes and n for no: ");
string t = Console.ReadLine();
if(t == "y") goto L3;
if(t == "n")
{
Console.WriteLine("bye, see you next time around");
goto L4;
}
L4:
int e = 0;
}
}
The method Exit() is used for program termination. It is part of the class Environment.
// password.cs
2.10. PASS BY VALUE, PASS BY REFERENCE 21
using System;
class password
{
public static void Main()
{
string password = "XYA";
int i,j,k;
for(i=65;i<91;i++)
{
for(j=65;j<91;j++)
{
for(k=65;k<91;k++)
{
char c1 = (char) i;
char c2 = (char) j;
char c3 = (char) k;
char[] data = { c1,c2,c3 };
string s = new string(data); // converting array of char to string
bool found = password.Equals(s);
if(found == true)
{
Console.WriteLine("Password = {0}",s);
Environment.Exit(0);
}
}
}
}
}
}
// Pitfall.cs
using System;
Note that without static in method add(int): error message: An object reference
is required for the nonstatic method Pitfall.add(int).
In the next program we use pointers to pass by reference. We rotate integer numbers.
// pointers2.cs
using System;
In the following program we use the keyword ref to pass by reference. We rotate
three integers.
// references.cs
using System;
In the following program we pass the first argument by reference and the second by
value.
// passing.cs
using System;
change(ref s1,s2);
Console.Write("s1 = {0} and s2 = {1}",s1,s2); // => s1 = yyy s2 = 111
}
}
The out keyword explicitely speciefies that a variable should be passed by reference
to a method, and set in that method. A variable using this keyword must not be
initialized before the method call.
// Divide.cs
using System;
class Divider
{
public static int Divide1(int dividend,int divisor,out int r)
{
int quot = dividend/divisor;
r = dividend - quot*divisor;
return quot;
} // end Divide1
int s;
int t;
Divide2(145,3,out s,out t);
Console.WriteLine("Quotient = {0} and Remainder = {1}",s,t);
} // end Main
}
2.11 Arrays
An array is a data structure that contains a number of variables. These are accessed
through computed indices. C# supports one-dimensional arrays, multidimensional
2.11. ARRAYS 25
arrays (rectangular arrays) and arrays of arrays (jagged arrays). As C, C++ and
Java C# arrays are zero indexed. This means the array indexes start as zero. When
declaring arrays, the square bracket [] must come after the type, not the identifiers,
for example int[] table. The size of the array is not part of its type as it is in the
C language. Thus
In C# arrays are actually objects. System.Array is the abstract base type of all
array types. The class Array contains methods for sorting and searching.
// myArray.cs
using System;
class myArray
{
public static void Main()
{
int[] numbers = { 4, 12345, 890, 23456789 };
int prod = numbers[2]*numbers[0];
Console.Write("prod = " + prod);
Console.Write("\n");
int numb = Array.BinarySearch(numbers,4);
Console.Write("numb = " + numb);
Console.Write("\n");
double[] d = new double[3];
d[0] = 1.1; d[1] = 3.4; d[2] = 8.9;
int dpos = Array.BinarySearch(d,8.9);
Console.Write("dpos = " + dpos);
Console.Write("\n");
Console.Write("pos2 = {0}",pos2);
Console.WriteLine();
for(int j=0;j<slist.Length;j++)
{
Console.WriteLine("{0} {1}",j,slist[j]);
}
}
}
To create multidimensional arrays the array initializer must have as many levels of
nesting as there are dimensions in the array. Thus:
int[,] numbers = {{0, 2}, {4, 6}, {8, 10}, {12, 17}, {16, 18}};
The outermost nesting level corresponds to the leftmost dimension. The innermost
nesting level corresponds to the rightmost dimension. The length of each dimension
of the array is determined by the number of elements at the corresponding nesting
level in the array initializer. Thus the example above creates a two-dimensional
array with a length of five for the leftmost dimension and a length of two for the
rightmost dimension. Or:
numbers[0, 0] = 0; numbers[0, 1] = 2;
numbers[1, 0] = 4; numbers[1, 1] = 6;
numbers[2, 0] = 8; numbers[2, 1] = 10;
numbers[3, 0] = 12; numbers[3, 1] = 14;
numbers[4, 0] = 16; numbers[4, 1] = 18;
We can also create jagged arrays, which are arrays of arrays. The element arrays do
not all have to be the same.
// twodim.cs
using System;
double[,] myarray;
myarray = new double[2,3];
myarray[0,0] = 3.1;
myarray[0,1] = 4.7;
myarray[0,2] = 3.3;
myarray[1,0] = 2.7;
myarray[1,1] = 1.1;
myarray[1,2] = 7.3;
double r = myarray[0,1]*myarray[1,2];
Console.WriteLine("r = " + r);
} // end Main
}
00000000000000000000000000010001
& AND
| OR (inclusive OR)
^ XOR (exclusive OR)
~ NOT
// bitwise.cs
using System;
class MylBitwise
{
public static void Main()
{
28 CHAPTER 2. CSHARP BASICS
int r3 = 4; int r4 = 5;
int r5 = r3 & r4; // bitwise AND
Console.WriteLine("Binary AND of 4 and 5 gives {0}",r5);
int x = 125;
int r10 = x^x;
Console.WriteLine("Binary XOR of 125 with itself gives {0}",r10);
int r12 = 4;
int r13 = ~r12; // one’s complement
int r14 = ++r13;
Console.WriteLine("two’s complement of 4 {0}",r14);
}
}
using System;
class Shift
{
public static void Main()
{
int i = 17; // 17 in binary 10001
int j = i >> 1; // integer divison by 2
Console.WriteLine("j = " + j); // => 8
int k = i >> 2; // integer divison by 4
Console.WriteLine("k = " + k); // => 4
int m = 9;
2.14. COMMMAND-LINE ARGUMENTS 29
// CommandLine.cs
using System;
class CommandLine
{
public static void Main(string[] args)
{
Console.WriteLine("Hello{0}",args[0]);
Comsole.WriteLine("Goodbye.");
}
}
CommandLine World
int i;
Console.WriteLine("i={0}",i);
30 CHAPTER 2. CSHARP BASICS
int i;
object obj = i; // boxing is implicit
int j;
j = (int) obj; // to unbox we use type cast
Typically unboxing is done in a try block. If the object being unboxed is null
or if the unboxing cannot succeed because the object is of a different type, an
InvalidCastException is thrown.
2.16 Delegates
A delegate essentially creates a name for a the specific type/signature of a method.
Delegates are type safe function pointers. One must first declare a delegate.
// delegates.cs
using System;
class Application
{
public static void Main()
{
MyDelegate md = new MyDelegate(FirstMethod);
md += new MyDelegate(SecondMethod);
md("message A",4,5);
md("message B",7,11);
} // end Main
The output is
2.17 Types
The typeof command is an operator. It resolves at compile time and operates over
a type. To check whether an object is compatible to a specific type is to apply the
is keyword.
// myTypeof.cs
using System;
using System.Text;
class myTypeof
{
public static void Main()
{
double b = 3.14;
string s = "xxx";
StringBuilder sb = new StringBuilder("123456789");
Type at = typeof(double);
Console.WriteLine("at = {0}",at);
Type st = typeof(string);
Console.WriteLine("st = {0}",st);
Type sbt = typeof(StringBuilder);
Console.WriteLine("sbt = {0}",sbt);
2.18 Reflection
Exposing and utilizing types at runtime is called reflection. The type of an object is
stored as an instance of System.Type class the reference to which can be obtained
using one of the following methods.
1. From the declaration type: If declaration AType var is legal then System.Type
representing AType can be obtained using the typeof operator as:
Type t = typeof(AType);
Type t = obj.GetType();
3. From the type name within current assembly: System.Type offers a static method
called GetType to obtain a Type from a fully qualified name of the type. The name
will be searched in the current assembly
Type t = Type.GetType("FullyQualifiedTypeName");
4. From the type name within any assembly: First load the assembly and obtain a
reference to it. This reference can be used to obtain the Type with a given name:
using System.Reflection;
Assembly asm = Assembly.LoadFrom("AssemblyName");
Type t = asm.GetType("FullyQualifiedTypeName");
The program showtypes.cs displays all the Types defined in an assembly whose
name is passed in as first command line argument:
// showtypes.cs
using System;
using System.Reflection;
class ShowTypes
{
public static void Main(string[] args)
2.19. GENERICS 33
{
Assembly asm = Assembly.LoadFrom(args[0]);
Type[] types = asm.GetTypes();
foreach(Type t in types) Console.WriteLine(t);
}
}
showtypes datatypes.exe
Pass complete path to any .NET exe or dll to see the types declared in it.
The next program showmembers.cs takes the assembly name and type name as
its command line arguments and displays all the members defined in that type of
assembly.
// showmembers.cs
using System;
using System.Reflection;
class ShowMembers
{
public static void Main(string[] args)
{
Assembly asm = Assembly.LoadFrom(args[0]);
Type t = asm.GetType(args[1]);
MemberInfo[] members = t.GetMembers();
foreach(MemberInfo m in members) Console.WriteLine(m);
}
}
2.19 Generics
C# Generics are similar to C++ Templates. They were introduced in version 2.0
of the C# language and the common language runtime (CLR). Generics introduce
to the .NET Framework the concept of type parameters. This makes it possible to
design classes and methods that defer the specification of one or more types until the
class or method is declared and instantiated. The .NET Framework class library
contains several new generic collection classes in the System.Collections.Generic
namespace.
throw new
InvalidOperationException("Cannot " +
"pop an empty stack");
}
}
}
Chapter 3
string s = "Hello";
which means that once the value of the string instance is set, it cannot be changed.
Even though it appears that the application is changing the value of the string
instance, it is actually returning a new instance of the string class in memory.
// mystring.cs
using System;
class mystring
{
public static void Main()
{
string s1 = "otto";
int length = s1.Length;
Console.WriteLine(s1); // => otto
Console.WriteLine(length); // => 4
string s2 = "willi";
s2 = s2.Replace(’l’,’t’);
Console.WriteLine(s2); // witti
string s3 = "Johannesburg";
string result = s3.Substring(3,5);
Console.WriteLine(result); // => annes
36
3.1. STRING CLASS 37
string s4 = "olli&ulli&ruedi";
string[] textarray = s4.Split(’&’);
Console.WriteLine(textarray[1]); // => ulli
string s5 = string.Join(":",textarray);
Console.WriteLine(s5); // => olli:ulli:ruedi
string s8 = "xeNa";
string s9 = s8.ToUpper();
Console.WriteLine(s9); // => XENA
// copy a string
string s15 = string.Copy(s14);
Console.WriteLine("s15 = " + s15);
} // end Main
}
// stringarrays.cs
38 CHAPTER 3. STRING AND STRINGBUILDER
using System;
class stringarrays
{
public static void Main()
{
// one-dimensional array of strings
string[] keywords = new string[] { "as", "do", "if", "in" };
Console.WriteLine(keywords[3]); // => in
An example is
// MyConvert.cs
using System;
{
public static void Main()
{
int i = 34;
string s1 = Convert.ToString(i);
Console.WriteLine("s1 = " + s1);
double x = 3.14159;
string s2 = Convert.ToString(x);
Console.WriteLine("s2 = " + s2);
bool b = Convert.ToBoolean("true");
Console.WriteLine("b = " + b);
char c = Convert.ToChar("x");
Console.WriteLine("c = " + c);
int j = Convert.ToInt32("12345");
Console.WriteLine("j = " + j);
string s3 = "3.14159";
double y = Convert.ToDouble(s3);
Console.WriteLine("y = " + y);
}
}
using System.Text;
// mystringbuilder.cs
using System;
using System.Text; // for StringBuilder
class mystringbuilder
{
public static void Main()
{
string s = "carl";
StringBuilder b1 = new StringBuilder(s.Length+12);
40 CHAPTER 3. STRING AND STRINGBUILDER
b1.Append("carl");
b1.Append("-uli");
Console.WriteLine(b1); // => carl-uli
b1.Remove(3,2);
Console.WriteLine(b1); // => caruli
b2.Replace(’.’,’:’);
Console.WriteLine(b2); // => A:B:C
// thuemorse.cs
using System;
using System.Text;
class ThueMorse
{
public static void Main()
{
for(int i=0;i<7;i++)
Console.WriteLine(mythuemorse(i));
}
Built-in Classes
// myDate.cs
using System;
class myDate
{
public static void Main()
{
DateTime dt = DateTime.Now;
Console.WriteLine("Date Time output: {0}",dt); // 7/29/2006 3:18:03 PM
string s = dt.ToString();
Console.WriteLine("s = " + s);
DateTime m1 = DateTime.Now;
int milli1 = m1.Millisecond;
Console.WriteLine("milli1 = " + milli1);
for(uint j=0;j<10000000;j++)
{ j = j*2; j = j/2; j++; j--; }
41
42 CHAPTER 4. BUILT-IN CLASSES
DateTime m2 = DateTime.Now;
int milli2 = m2.Millisecond;
Console.WriteLine("milli2 = " + milli2);
int diff = milli2 - milli1;
Console.WriteLine("diff = " + diff);
// Formats
DateTime currTime = DateTime.Now;
Console.WriteLine("d: {0:d}",currTime); // 9/24/2005
Console.WriteLine("D: {0:D}",currTime); // Saturday, September 24, 2005
}
}
An example is
// MyArray.cs
using System;
//using System.Collections.IList;
class MyArray
{
public static void Main()
{
int[] a1 = { 669, 56, 45, 877, 123, 456, 777 };
int p1 = Array.BinarySearch(a1,877);
Array.Reverse(s1);
for(int i=0;i<s1.Length;i++)
{
Console.WriteLine("s1[" + i + "]=" + s1[i]);
}
Array.Clear(a1,3,2);
for(int i=0;i<a1.Length;i++)
{
Console.WriteLine("a1[" + i + "]=" + a1[i]);
}
int j = Array.IndexOf(a1,123);
Console.WriteLine("j = " + j); // j = -1 why ?
}
}
using System;
using System.Collections;
class arraylist
{
public static void Main(string[] arg)
{
ArrayList alist = new ArrayList();
foreach(string s in arg) alist.Add(s);
for(int i=0;i<alist.Count;i++)
{
44 CHAPTER 4. BUILT-IN CLASSES
Console.Write("{0}",alist[i]);
}
Console.Write("\n");
Console.Write("Argument Count:{0}\n",alist.Count);
string s1 = "Xena";
alist.Insert(3,s1);
alist.RemoveAt(1);
// Dictionary.cs
using System;
using System.Collections;
using System.Collections.Specialized;
class Dictionary
{
public static void Main()
{
ListDictionary population = new ListDictionary();
population.Add("Johannesburg",9345120);
population.Add("CapeTown",3500500);
population.Add("Durban",550500);
Console.WriteLine("{0} = {1}",name.Key,name.Value);
}
population.Remove("Durban");
foreach(DictionaryEntry name in population)
{
Console.WriteLine("{0} = {1}",name.Key,name.Value);
}
} // end Main()
}
// Enumerator.cs
using System;
using System.Collections;
while(sEnum.MoveNext())
Console.Write(" {0}",sEnum.Current);
}
}
// distance.cs
46 CHAPTER 4. BUILT-IN CLASSES
using System;
class Distance
{
public static void Main()
{
double r = 6371.0;
double alpha1, alpha2, beta1, beta2, temp, theta, distance;
char dir;
string source, dest, line;
alpha1 *= Math.PI/180.0;
alpha2 *= Math.PI/180.0;
beta1 *= Math.PI/180.0;
beta2 *= Math.PI/180.0;
temp = Math.Cos(beta1)*Math.Cos(beta2)*Math.Cos(alpha1-alpha2)
+ Math.Sin(beta1)*Math.Sin(beta2);
theta = Math.Acos(temp);
distance = r*theta;
Console.WriteLine("The distance between {0} and {1} is {2} km",
source,dest,distance);
}
}
// random.cs
using System;
class LearnRandom
{
public static void Main()
{
Random r = new Random();
Console.WriteLine("Random sequence(no seed,limit 0..int.MaxVal)");
Console.WriteLine();
for(int i=0;i<10;i++)
Console.WriteLine("random no: {0}",r.Next());
Console.WriteLine("Random sequence(no seed,limit 0..10)");
for(int i = 0;i<10;i++)
Console.WriteLine("random no: {0}",r.Next(10));
4.7. POINT CLASSES 49
using System;
using System.Windows.Forms;
using System.Drawing;
class PointTest
{
public static void Main()
{
Point p = new Point(23,13);
Console.WriteLine("Our Point is: " + p);
int xcoord = p.X;
int ycoord = p.Y;
Console.WriteLine("The x coordinate is " + xcoord);
Console.WriteLine("The y coordinate is " + ycoord);
Point q = new Point(xcoord,ycoord);
bool b = p.Equals(q);
Console.WriteLine(" the points are equal: " + b);
// BitArrayTest.cs
using System;
using System.Collections;
BitArray c = a.And(b);
Console.WriteLine("Bitarray c");
int i;
for(i=0;i<16; i++)
Console.WriteLine(c[i]);
Console.WriteLine("");
BitArray e = a.Not();
Console.WriteLine("Not a");
4.9. OBJECT CLASS 51
for(i=0;i<16;i++)
Console.WriteLine("e[" + i + "]=" + e[i]);
a.SetAll(true);
Console.WriteLine("a.SetAll(true)");
for(i=0;i<16;i++)
Console.WriteLine("f[" + i + "]=" + a[i]);
BitArray g = d.Not();
for(i=0;i<16;i++)
Console.WriteLine("g[" + i + "]=" + g[i]);
BitArray f = d.Xor(g);
Console.WriteLine("Xor d to get f");
for(i=0;i<16;i++)
Console.WriteLine("f[" + i + "]=" + f[i]);
}
}
[Serializable]
public class Object
Public static (non-instance) members of this type are safe for multithreaded opera-
tions. Instance members are not guaranteed to be thread-safe. Languages typically
do not require a class to declare inheeritance from Object since the inheritance is
implicit.
Since all classes in the .NET Framework are derived from Object, every method
defined in the Object class is available in all objects in the system. Derived classes
can and do override some of these methods, including
The method GetHashCode() serves as a hash function for a particular type, suitable
for use in hashing algorithms and data structures like a hash table.
The following code compares the current instance with another object.
// object1.cs
using System;
The following example shows a Point class that overrides the Object.Equals()
method to provide value equality and a class Point3D, which is derived from the
Point class. The Object.Equals() method uses Object.GetType() to determine
whether the run-time types of the two objects are identical.
// object2.cs
using System;
int h1 = p1.GetHashCode();
Console.WriteLine("h1 = " + h1);
int h2 = p3.GetHashCode();
Console.WriteLine("h2 = " + h2);
54 CHAPTER 4. BUILT-IN CLASSES
}
}
The method
creates a shallow copy of the current object, i.e. it returns a shallow copy of the
current object. The following code shows how to copy an instance of a class using
Object.MemberwiseClone().
// object3.cs
using System;
class MyBaseClass
{
public static string CompanyName = "KXK";
public int age;
public string name;
}
Object-Oriented Programming
The following program shows an example for class Person. The Person is described
by his name, age and sex. Thus we have three attributes:
These private data members are only visible inside the body of class Person1.
// Person1.cs
using System;
class Person1
{
private string myName = "N/A";
private int myAge = 0;
private char mySex = ’n’;
55
56 CHAPTER 5. OBJECT-ORIENTED PROGRAMMING
// default constructor
public Person1() { }
// constructor
public Person1(string name,int age,char sex)
{
this.myName = name;
this.myAge = age;
this.mySex = sex;
}
// array of persons
Person1[] pmany = new Person1[2];
pmany[0] = new Person1("Carl",23,’m’);
pmany[1] = new Person1("Ola",7,’f’);
Console.WriteLine("name of person[0]: " + pmany[0].myName);
Console.WriteLine("sex of person[1]: " + pmany[1].mySex);
} // end Main
}
class Person1 defines three private member variables, myName, myAge, mySex.
These variable are visible only inside the body of class Person1. To access these
variable outside the class we use the special property methods Name, Age, and Sex.
The CSharp compiler translates for example the Name property to a pair of methods,
namely
public string get_Name()
58 CHAPTER 5. OBJECT-ORIENTED PROGRAMMING
{ return myName; }
The class Person1 contains the Main() method. In some application it would be
better to keep the class Person1 without the Main() method and have an extra file
which calls the Person objects. The file Person.cs only contains the class Person
and no Main() method.
// Person.cs
using System;
class Person
{
public string myName = "N/A";
public int myAge = 0;
public char mySex = ’n’;
// default constructor
public Person() { }
// constructor
public Person(string name,int age,char sex)
{
this.myName = name;
this.myAge = age;
this.mySex = sex;
}
{
get { return mySex; }
set { mySex = value; }
}
// PersonMain.cs
using System;
class PersonMain
{
public static void Main()
{
// create a new Person object using default constructor
Person person1 = new Person();
person2.Sex = ’f’;
Console.WriteLine("Person details - {0}",person2);
// array of persons
Person[] pmany = new Person[2];
pmany[0] = new Person("Carl",23,’m’);
pmany[1] = new Person("Ola",7,’f’);
Console.WriteLine("name of person[0]: " + pmany[0].myName);
Console.WriteLine("sex of person[1]: " + pmany[1].mySex);
} // end Main
}
// Point3D.cs
using System;
// Point3DMain.cs
using System;
double a = 2.1;
double b = 3.1;
double c = 4.5;
Point3D p2 = new Point3D(a,b,c);
bool r = p1.Equals(p2);
Console.WriteLine("r = " + r);
double k = 2.0;
double m = 1.0;
double n = 7.5;
Point3D p3 = new Point3D(k,m,n);
Point3D p4 = new Point3D(0.0,0.0,0.0);
p4 = (Point3D) p3.Clone(); // type conversion
Console.WriteLine("p4 = " + p4);
5.3 Inheritance
Inheritance is one of the primary concepts of object-oriented programming. It
allows us to reuse existing code. In the next program we use inheritence. The
class Car is derived from class Vehicle. In the class Car we use code from the
class Vehicle. First we must declare our intention to use Vehicle as the base
class of Car. This is accomplished through the Car class declaration
Then the colon, : , and the keyword base call the base class constructor. C#
supports single class inheritance only.
// MVehicle.cs
using System;
class Vehicle
{
private int weight;
private int topSpeed;
private double price;
public Vehicle() {}
public Car() { }
class myCar
{
public static void Main()
{
Vehicle aVehicle = new Vehicle(15000,120,30000.00);
Console.Write("A vehicle: ");
aVehicle.print();
Console.WriteLine("");
Car aCar = new Car(3500,100,12000.00,6,120,300);
Console.Write("A car: ");
aCar.print();
Console.WriteLine("");
}
}
An interface looks like a class, but has no implementation. The only thing it contains
are definitions of events, indexers, methods, and/or properties. The reason interfaces
only provide definitions is because they are inherited by classes and structs, which
must provide an implementation for each interface member defined. Since interfaces
must be defined by inheriting classes and structs, they must define a contract. The
following program shows an example.
// Policies.cs
{
double Rate(double principal,int period);
}
Each policy provides a Rate method which returns the rate of interest for a given
principal and for a given period. All policy classes implement the Policy interface
except GoldPolicy which defines Rate without implementing policy. We compile
Policies.cs to create Policies.dll via
Using reflection we can also create an instance of a class and invoke its member
methods at runtime. This feature can be used to write more generic (dynamically
extensible) programs which receive names of classes at runtime.
The investment.cs program including Main accepts principal, period and policy
name as arguments args[0], args[1], and args[2]. We compile investment.cs
as
66 CHAPTER 5. OBJECT-ORIENTED PROGRAMMING
// investment.cs
using System;
using System.Reflection;
class Investment
{
public static void Main(string[] args)
{
double p = Double.Parse(args[0]); // string to double
int n = Int32.Parse(args[1]); // string to int
Type t = Type.GetType(args[2]);
Policy pol = (Policy) Activator.CreateInstance(t);
double r = pol.Rate(p,n);
double amount = p*Math.Pow(1.0+r/100.0,n);
Console.WriteLine("you will get {0:#.00}",amount);
}
}
// methods.cs
using System;
+, -, *, \, %, []
// Complex.cs
using System;
68 CHAPTER 5. OBJECT-ORIENTED PROGRAMMING
class ComplexMain
{
public static void Main()
{
Complex r1 = new Complex(1.0,2.0);
Complex r2 = new Complex(2.0,1.0);
r1 = r1 + r2;
Console.WriteLine("Real: {0} Imaginary: {1}i",r1.real,r1.imag);
}
}
Exercise. Overload in the program also * for the multiplication of two complex
numbers. The multiplication of two complex numbers z1 = a + ib and z2 = c + id
(with a, b, c, d real) is given by z1 ∗ z2 = a ∗ c − b ∗ d + i(ad + bc).
5.6 Structures
In CSharp we can also use structures. Structures do not contain methods. The
objects are stored on the stack compared to classes which are stored on the heap.
There is no inheritance for structure. The program also shows how enumeration can
be used.
// enumeration.cs
using System;
5.7. DELEGATES 69
struct ClubMember
{
public string Name;
public int Age;
public MemberType Group;
}
class enumeration
{
public static void Main(string[] args)
{
ClubMember a; // Value types are automatically initialized
a.Name = "John";
a.Age = 13;
a.Group = MemberType.Eagles;
5.7 Delegates
A delegate in C# allows us to pass methods of one class to objects of other classes
that can call those methods. We can pass method m in class A, wrapped in a dele-
gate, to class B and class B will be able to call method m in class A. We can pass
both static and instance methods. This concept is familiar to C++ where one uses
function pointers to pass functions as parameters to other methods in the same
class or in another class. C# delegates are implemented in the .NET framework as
a class derived from System.Delegate. Thus the use of delegates involves four steps.
1. Declare a delegate object with a signature that exactly matches the method
signature that we are trying to encapsulate.
2. Define all the methods whose signature match the signature of the delegate object
that we have defined in step 1.
3. Create delegate object and plug in the methods that we want to encapsulate.
4. Call the encapsulated methods through the delegate object.
// Delegate.cs
using System;
class Driver
{
public static void Main()
{
Class2 c2 = new Class2();
MyDelegate d = c2.createDelegate();
Class3 c3 = new Class3();
c3.callDelegate(d,"calling the delegate");
} // end Main
}
// MyDelegate.cs
using System;
class SimpleDelegate
{
Name[] names = new Name[4];
public SimpleDelegate()
{
names[0] = new Name("John","Smithlone");
names[1] = new Name("Carl","Xenon");
names[2] = new Name("Rolf","Cooper");
names[3] = new Name("Ola","Jones");
}
Console.WriteLine("\nBefore Sort:\n");
sd.PrintNames();
Console.WriteLine("\nAfter Sort:\n");
sd. PrintNames();
}
for(int i=0;i<names.Length;i++)
{
for(int j=i;j<names.Length;j++)
{
// using delegate "compare" just like a normal method
if(compare(names[i],names[j])>0)
5.7. DELEGATES 73
{
temp = names[i];
names[i] = names[j];
names[j] = (Name) temp;
} // end if
}
}
} // end method Sort
6.1 Introduction
A stream is a sequence of bytes. A byte is 8 bits. The Stream class and its subclasses
provide a generic view of data sources and repositories, isolating the programmer
from the specific details of the operating system and underlying devices. Streams
involve the following three operations:
1) Streams can be read from. Reading is the transfer of data from a stream into a
data structure, such as an array of bytes.
2) Streams can be written to. Writing is the transfer of data from a data structure
into a stream.
3) Streams can support seeking. Seeking is the query and modifying of the current
position within a stream.
and also the data type string and StringBuilder to a stream (binary file manip-
ulation).
74
6.2. BINARY FILE MANIPULATIONS 75
// BinaryIO1.cs
using System;
using System.IO;
using System.Text;
class BinaryIO1
{
private static void WriteData(int i,double d,char c,bool b)
{
Stream s = File.OpenWrite("info.dat");
BinaryWriter bw = new BinaryWriter(s);
bw.Write(i);
bw.Write(d);
bw.Write(c);
bw.Write(b);
bw.Close();
s.Close();
}
// BinaryIO2.cs
using System;
using System.IO;
using System.Text;
76 CHAPTER 6. STREAMS AND FILE MANIPULATIONS
class BinaryIO2
{
private static void WriteData(string t,StringBuilder sb)
{
Stream s = File.OpenWrite("info.dat");
BinaryWriter bw = new BinaryWriter(s);
// write length-prefixed string
bw.Write(t);
bw.Write(sb.ToString());
bw.Close();
s.Close();
}
We convert the array of bytes into an ASCII text for displaying it on the console.
// readfile.cs
using System;
using System.IO;
using System.Text; // for encoding
class ReadFile
{
public static void Main(string[] args)
{
Stream s = new FileStream(args[0],FileMode.Open);
int size = (int) s.Length;
byte[] buffer = new byte[size];
s.Read(buffer,0,buffer.Length);
s.Close();
string text = Encoding.ASCII.GetString(buffer);
Console.WriteLine(text);
}
}
Writing to a file (text mode). We append the text provided by the line
// writefile.cs
using System;
using System.IO;
using System.Text; // for encoding
class WriteFile
{
public static void Main(string[] args)
{
Stream s = new FileStream(args[0],FileMode.Append,FileAccess.Write);
78 CHAPTER 6. STREAMS AND FILE MANIPULATIONS
string text = "all the good men and women" + "\r\n"; // append end
// of line characters
byte[] buffer = Encoding.ASCII.GetBytes(text);
s.Write(buffer,0,buffer.Length);
s.Close(); // also flushes the stream
}
}
Writing the date and time into a file using the DateTime class..
// placeorder.cs
using System;
using System.IO;
class PlaceOrder
{
public static void Main(string[] args)
{
DateTime dt = DateTime.Now;
string today = dt.ToString("dd-MMM-yyyy");
string record = today + "|" + String.Join("|",args);
StreamWriter sw = new StreamWriter("order.txt",true);
sw.WriteLine(record);
sw.Close();
}
}
The useful classes in reading/writing text files are: StreamReader and StreamWriter.
Given the file datain.txt with the two lines
the following program reads the file line by line and displays it on the Console and
also writes it to the output file dataout.txt.
// Filemanipulation.cs
using System;
using System.IO;
class FileManipulation
{
static void Main(string[] args)
{
6.4. BYTE BY BYTE MANIPULATION 79
using System;
using System.IO;
{
Console.Write((char) bs.ReadByte()); }
}
bs.Close();
s.Close();
}
In the following example we copy byte by byte a file into another file.
// TestFile.cs
using System;
using System.IO;
class TestFile
{
static void Copy(string from,string to,int pos)
{
try
{
FileStream sin = new FileStream(from,FileMode.Open);
FileStream sout = new FileStream(to,FileMode.Create);
sin.Seek(pos,SeekOrigin.Begin);
int ch = sin.ReadByte();
while(ch >= 0)
{
sout.WriteByte((byte) ch); // type conversion
ch = sin.ReadByte();
}
sin.Close();
sout.Close();
}
catch (FileNotFoundException e)
{
Console.WriteLine("--file {0} not found",e.FileName);
}
}
// Oops1.cs
using System;
using System.IO;
class Oops
{
public static void Main()
{
char cl = ’{’;
int countcl = 0;
char cr = ’}’;
int countcr = 0;
Exercise. Extend the program so that it also counts the number of rectangular
brackets (left and right) and parentheses (left and right).
// employee.cs
using System;
82 CHAPTER 6. STREAMS AND FILE MANIPULATIONS
using System.Runtime.Serialization;
[Serializable]
public class Employee
{
public string Name;
public string Job;
public double Salary;
public Employee() { }
// binsertest1.cs
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
class BinarySerializatioTest1
{
static BinaryFormatter bf = new BinaryFormatter();
<employee id="105">
<name>Jeff</name>
<job>CEO</job>
<salary>105000</salary>
</employee>
</employees>
The program listemp.cs displays id, and salary of each employee in the file
emp.xml.
// listemp.cs
using System;
using System.Xml;
class ListEmployees
{
public static void Main()
{
XmlDocument doc = new XmlDocument();
// load employee.xml in XmlDocument
doc.Load("emp.xml");
// obtain a list of all employee element
XmlNodeList list = doc.GetElementsByTagName("employee");
foreach(XmlNode emp in list)
{
// obtain value of id attribute of the employee tag
string id = emp.Attributes["id"].Value;
// obtain value of salary subtag of the employee tag
string sal = emp["salary"].FirstChild.Value;
Console.WriteLine("{0}\t{1}",id,sal);
}
}
}
The program addemp.cs takes informations from the command line (id, name, job,
salary) and makes a new entry in the file emp.xml.
// addemp.cs
using System;
using System.Xml;
class AddEmployee
{
public static void Main(string[] args)
6.6. XML DOCUMENTS 85
{
XmlDocument doc = new XmlDocument();
doc.Load("emp.xml");
// create a new employee element and value of its attribute
XmlElement emp = doc.CreateElement("employee");
emp.SetAttribute("id",args[0]);
// create a name tag and st its value
XmlNode name = doc.CreateNode("element","name","");
name.InnerText = args[1];
// make name tag a subtag of newly created employee tag
emp.AppendChild(name);
XmlNode job = doc.CreateNode("element","job","");
job.InnerText = args[2];
emp.AppendChild(job);
XmlNode sal = doc.CreateNode("element","salary","");
sal.InnerText = args[3];
emp.AppendChild(sal);
// add the newly created employee tag to the root (employees) tag
doc.DocumentElement.AppendChild(emp);
// save the modified document
doc.Save("emp.xml");
}
}
Graphics
DrawString(string,Font,Brush,int X,int Y)
The first argument is a string, i.e. the text we want to display. The last two
arguments is the position where we put the string. The method DrawEllipse() is
given by
Every method in the Graphics class has to be accessed by creating an object of that
class. We can easily update the above program to render other graphical shapes like
Rectangle, Ellipse, etc. All we have to do is to apply the relevant methods ap-
propriately.
86
7.1. DRAWING METHODS 87
Using the Pen class we can specify colour of the border and also the thickness. The
Pen class is applied for drawing shapes. The Brush is applied for filling shapes such
as SolidBrush and HatchStyleBrush.
The default Graphics unit is Pixel. By applying the PageUnit property, we can
change the unit of measurement to Inch and Millimeter.
// HelloGraphics.cs
using System;
using System.Drawing;
using System.Windows.Forms;
g.DrawRectangle(new Pen(Color.Pink,3),20,20,150,100);
}
The method
DrawPolygon(System.Drawing.Pen,new Point[]{
new Point(x,y),new Point(x,y),
new Point(x,y),new Point(x,y),
new Point(x,y),new Point(x,y)});
draws a polygon for a given set of points. The following program shows how do
draw two triangles using this method.
88 CHAPTER 7. GRAPHICS
// MyPolygon.cs
using System;
using System.Drawing;
using System.Windows.Forms;
// Fill.cs
using System;
using System.Drawing;
using System.Windows.Forms;
{
this.Paint += new PaintEventHandler(fillpaint);
}
The alpha-value is implicit 255 (no transparency). To create a Color structure from
the four ARGB component (alpha,red,green,blue) we call for example
Alpha is also known as transparency where 255 is totally solid and 0 is totally
transparent.
// draw.cs
using System;
using System.Drawing;
using System.Windows.Forms;
{
FontFamily fontFamily = new FontFamily("Times New Roman");
Font font = new Font(fontFamily,24,FontStyle.Bold,GraphicsUnit.Pixel);
PointF pointF = new PointF(30,10);
SolidBrush solidbrush =
new SolidBrush(Color.FromArgb(51,255,0,0));
e.Graphics.DrawString ("Hello",font,solidbrush,pointF);
In C# the user can choose a color by applying the ColorDialog class appropriatly.
First we have to create an object of ColorDialog class
// ColorD.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public ColorD()
{
b.Click += new EventHandler(b_click);
b.Text = "OK";
tb.Location = new Point(50,50);
this.Controls.Add(b);
7.3. BUTTON AND EVENTHANDLER 91
this.Controls.Add(tb);
}
using System;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;
public WinHello()
{
Text = "Hello World";
Size = new Size(400,400);
bnclick = new Button();
bnclick.Text = "Click.Me";
bnclick.Size = new Size(60,24);
bnclick.Location = new Point(20,60);
bnclick.Click += new EventHandler(bnclick_Click);
Controls.Add(bnclick);
Closing += new CancelEventHandler(WinHello_Closing);
92 CHAPTER 7. GRAPHICS
We can create a Font selection dialog box using the FontDialog class. The following
program gives an example
// Fonts.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public Fonts()
{
b.Click += new EventHandler(b_click);
b.Text = "OK";
tb.Location = new Point(50,50);
this.Controls.Add(b);
this.Controls.Add(tb);
7.4. DISPLAYING IMAGES 93
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
}
}
// textbru.cs
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public Texturedbru()
{
this.Text = "Using Texture Brushes";
Image bgimage = new Bitmap("forest.bmp");
bgbrush = new TextureBrush(bgimage);
this.Paint += new PaintEventHandler(Text_bru);
}
// MyOnPaint.cs
7.5. OVERRIDING ONPAINT 95
using System;
using System.Drawing;
using System.Windows.Forms;
public Draw()
{
Button button1 = new Button();
button1.Text = "Click Me";
button1.Location = new Point(210,220);
button1.Click += new EventHandler(HandleClick);
Controls.Add(button1);
}
if(b.Color == Color.Blue)
{
b.Color = Color.Red;
}
else
{
b.Color = Color.Blue;
}
e.Graphics.FillEllipse(b,x,y,diameter,diameter);
} // end OnPaint
// Rotate.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System;
public TimerVector()
{
rot = 0.0f;
7.5. OVERRIDING ONPAINT 97
Another example is
// Flicker.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public Flicker()
{
ClientSize = new Size(500,500);
SetStyle(ControlStyles.DoubleBuffer,true);
SetStyle(ControlStyles.UserPaint,true);
SetStyle(ControlStyles.AllPaintingInWmPaint,true);
t.Interval = 40;
t.Tick += new EventHandler(TimerOnTick);
t.Enabled = true;
this.KeyDown += new KeyEventHandler(OnKeyPress);
}
set
98 CHAPTER 7. GRAPHICS
{
if(value.X < 0)
{
this.playerPosition.X = this.ClientSize.Width-this.playerSize.Width;
}
else if(value.X+this.playerSize.Width > this.ClientSize.Width)
{
this.playerPosition.X = 0;
}
else
{
this.playerPosition.X = value.X;
}
if(value.Y < 0)
{
this.playerPosition.Y = this.ClientSize.Height-this.playerSize.Height;
}
else if(value.Y+this.playerSize.Height > this.ClientSize.Height)
{
this.playerPosition.Y = 0;
}
else
{
this.playerPosition.Y = value.Y;
}
}
}
if(e.KeyValue == 40)
{
this.PlayerPosition =
new Point(this.PlayerPosition.X,this.PlayerPosition.Y+this.playerSize.Height);
}
}
Events
Event handling is familiar to any developer who has programmed graphical user in-
terfaces (GUI). When a user interacts with a GUI control (e.g., clicking a button on
a form), one or more methods are executed in response to the above event. Events
can also be generated without user interactions. Event handlers are methods in an
object that are executed in response to some events occuring in the application. To
understand the event handling model of the .NET framework, we need to unter-
stand the concept of delegate. A delegate in C# allows us to pass methods of one
class to objects to other classes that can call those methods. We can pass method
m in class A, wrapped in a delegate, to class B and class B will be able to call
method m. We can pass both static and instance methods.
The first parameter (sender) in the above declaration specifies the object that fired
the event. The second parameter (e) holds data that can be used in the event han-
dler. The class MyEventArgs is derived from the class EventArgs. EventArgs is the
base class of more specialized classes, like MouseEventArgs, ListChangedEventArgs,
etc.
Thus an event is a notification sent by a sender object to a listener object. The lis-
tener registers an event handler method with the sender. When the event occurs the
sender invokes event handler methods of all its registered listerners. The event han-
dler is registered by adding +=. The -= operator removes the handler from the event.
As an example we set up two classes A and B to see how this event handling mecha-
nism works in .NET framework. The delegates require that we define methods with
the exact same signature as that of the delegate declaration. Class A will provide
event handlers (methods with the same signature as that of the event declaration).
It will create the delegate objects and hook up the event handler. Class A will then
pass the delegate objects to class B. When an event occurs in class B, it will execute
the event handler method of class B.
100
101
// MyHandler.cs
using System;
// Step 4. Call the encapsulated method through the delegate (fires events)
class B
{
public event MyHandler1 Event1;
public event MyHandler2 Event2;
public void FireEvent1(MyEventArgs e)
{
if(Event1 != null) { Event1(this,e); }
}
public void FireEvent2(MyEventArgs e)
{
if(Event2 != null) { Event2(this,e); }
102 CHAPTER 8. EVENTS
}
} // end class B
class Driver
{
public static void Main()
{
B b = new B();
A a = new A(b);
MyEventArgs e1 = new MyEventArgs();
MyEventArgs e2 = new MyEventArgs();
e1.m_id = "Event args for event 1";
e2.m_id = "Event args for event 2";
b.FireEvent1(e1);
b.FireEvent2(e2);
} // end Main
}
The next program shows GUI event handling in C#. We create two buttons each of
them fires an event.
// evthand.cs
using System;
using System.Windows.Forms;
using System.Drawing;
9.1 Processes
A process in its simplest form is a running application. The Process class provides
access to local and remote processes and enables us to start and stop local system
processes.
// process1.cs
using System;
using System.Diagnostics;
class Processtest
{
public static void Main()
{
Process p = Process.Start("notepad.exe");
string name = p.ProcessName;
int id = p.Id;
DateTime started = p.StartTime;
Console.WriteLine("name = " + name);
Console.WriteLine("id = " + id);
Console.WriteLine("StartTime = " + started);
104
9.2. THREADS 105
Process w = Process.Start("winhlp32.exe");
Process p = Process.Start("notepad.exe","c:\\csharp\\process2.cs");
WaitForExit()
9.2 Threads
9.2.1 Introduction
A thread is an execution stream within a process. A thread is also called a lightweight
process. It has it own execution stack, local variables, and program counter. There
may be more than one thread in a process. Threads are part of the same process
which execute concurrently. In .NET Base Class Library (BCL) the System.Threading
namespace provides various classes for executing and controlling threads.
// Threads0.cs
using System;
using System.Threading;
thread.Start();
int i = 0;
while(i < 6)
{
Console.WriteLine("Main thread: {0}",i);
i++;
} // end while
} // end Main
An output could be
Main thread: 0
Main thread: 1
Main thread: 2
Main thread: 3
Main thread: 4
Main thread: 5
Other thread: 0
Other thread: 1
Other thread: 0
Other thread: 1
Main thread: 0
Main thread: 1
Main thread: 2
Main thread: 3
Main thread: 4
Main thread: 5
t.IsBackgound = true;
// threadtest2.cs
using System;
using System.Threading; // for Thread class
class ThreadTest2
{
public static void SayHello()
{
for(int i=1;;i++)
{
Console.WriteLine("Hello {0}",i);
}
}
// ThreadsSleep.cs
using System;
using System.Threading;
int i = 0;
while(i < 5)
{
Console.WriteLine("Main thread: {0}",i);
Thread.Sleep(1000);
i++;
} // end while
} // end Main
Main thread: 0
Other thread: 0
Other thread: 1
Main thread: 1
Other thread: 2
Other thread: 3
Other thread: 4
Main thread: 2
Other thread: 5
Main thread: 3
Other thread: 6
Other thread: 7
Main thread: 4
Other thread: 8
Other thread: 9
// joiningthread.cs
using System;
using System.Threading;
class JoiningThread
{
public static void Run()
{
for(int i=1;i<3;i++)
Console.WriteLine("Hello {0}",i);
}
The output is
Welcome 1
Welcome 2
Welcome 3
Welcome 4
Welcome 5
Hello 1
Hello 2
9.3 Monitor
While one thread is in a read/increment/write operation, no other threads can try
to do the same thing. This is where monitors come in. Every object in .NET has
a monitor associated with it. A thread can enter (or acquire) a monitor only if no
other thread has currently “got” it. Once a thread has acquired a monitor, it can
acquire it more times, or exit (or release) it. The monitor is only available to other
threads again once it has exited as many times as it was entered. If a thread tries
to acquire a monitor which is owened by another thread, it will block until it is able
to acquire it. There may be more than one thread trying to acquire the monitor,
in which case when the current owner thread releases it for the last time, only one
of the threads will acquire it - the other one will have to wait for the new owner to
release it too. The Pulse(object) method notifies a thread in the waiting queue
of a change in the locked object’s state. The method Wait(object) waits for the
Monitor Pulse.
// waitingthread.cs
using System;
using System.Threading;
class WaitingThread
{
static object obj = new object();
If thread3 runs last after thread1 and thread2 have completed it freeze at
Good Night: 7
9.4 Synchronization
When two or more threads share a common resource access needs to be serialized in a
process called synchronization. Synchronization is done with the lock() operation.
The first program is without the lock operation. In the second program the lock
operation is introduced.
112 CHAPTER 9. PROCESSES AND THREADS
// syncthreads1.cs
using System;
using System.Threading;
class Account
{
private double balance = 5000;
class SymnThread
{
static Account acc = new Account();
// syncthreads2.cs
using System;
using System.Threading;
class Account
{
private double balance = 5000;
9.5. DEADLOCK 113
class SymnThread
{
static Account acc = new Account();
9.5 Deadlock
The second major problem of multi-threading is that of deadlocks. Simply put, this
is when two threads each holds a monitor that the other one wants. Each blocks,
waiting for the monitor that it’s waiting for to be released - and so the monitors are
never released, and the application hangs (or at least those threads involved in the
deadlock hang). An example is given below.
// Deadlock.cs
using System;
using System.Threading;
The Interlocked class provides a set of methods for performing atomic changes: ex-
changes (optionally performing a comparison first), increments and decrements. The
Exchange and CompareExchange methods act on a variables of type int, object,
or float; the Increment and Decrement methods act on variables of type int and
long.
// interlocked.cs
using System;
using System.Threading;
for(long i=0;i<5;i++)
{
Interlocked.Increment(ref count);
Console.WriteLine("I am in for loop in main");
}
thread.Join();
Console.WriteLine("final count: {0}",count);
} // end Main
while(i < 5)
{
Interlocked.Increment(ref count);
Console.WriteLine("I am in ThreadJob");
i++;
}
} // end ThreadJob
}
First the for loop in Main will run to the end and then the while loop will be done.
// ThreadsSum2.cs
using System;
using System.Threading;
class ThreadsSum
{
public static void Sum1(Object o)
{
int sum1 = 0;
int[] a1 = (int[]) o;
for(int i=0;i<a1.Length;i++)
{
sum1 += a1[i];
}
Console.WriteLine("sum1 = " + sum1);
}
{
sum2 += a2[i];
}
Console.WriteLine("sum2 = " + sum2);
}
if(ThreadPool.QueueUserWorkItem(new WaitCallback(Sum1),a1))
Console.WriteLine("Sum1 queued");
if(ThreadPool.QueueUserWorkItem(new WaitCallback(Sum2),a2))
Console.WriteLine("Sum2 queued");
1) Never invoke any method or property on a control created on another thread other
than Invoke, BeginInvoke, EndInvoke or CreateGraphics, and InvokeRequired.
Each control is effectively bound to a thread which runs its message pump. If we
try to access or change anything in the UI (for example changing the Text prop-
erty) from a different thread, we run a risk of our program hanging or misbehaving
in other ways. We may get away with it in some cases. Fortunately, the Invoke,
BeginInvoke and EndInvoke methods have been provided so that we can ask the
UI thread to call a method in a safe manner.
2) Never execute a long-running piece of code in the UI thread. If our code is running
in the UI thread, that means no other code is running in that thread. That means
we won’t receive events, our controls won’t be repainted, etc. We can execute long-
running code and periodically call Application.DoEvents(). It means we have
to consider re-entrancy issues etc, which are harder to diagnose and fix than ”nor-
mal” threading problems. We have to judge when to call DoEvents, and we can’t
use anything which might block (network access, for instance) without risking an
unresponsive UI. There are message pumping issues in terms of COM objects as well.
sure it doesn’t directly try to update the UI with its results. The thread creation
part is the same as any other threading problem. It is interesting going the other
way - invoking a method on the UI thread in order to update the UI. There are two
different ways of invoking a method on the UI thread, one synchronous (Invoke)
and one asynchronous (BeginInvoke). They work in much the same way - we spec-
ify a delegate and (optionally) some arguments, and a message goes on the queue
for the UI thread to process. If we use Invoke, the current thread will block until
the delegate has been executed. If we use BeginInvoke, the call will return imme-
diately. If we need to get the return value of a delegate invoked asynchronously, we
can use EndInvoke with the IAsyncResult returned by BeginInvoke to wait until
the delegate has completed and fetch the return value.
There are two options when working out how to get information between the various
threads involved. The first option is to have state in the class itself, setting it in
one thread, retrieving and processing it in the other (updating the display in the UI
thread, for example). The second option is to pass the information as parameters
in the delegate. Using state somewhere is necessary if we are creating a new thread
rather than using the thread pool - but that doesn’t mean we have to use state to
return information to the UI. However, creating a delegate with lots of parameters
feels clumsy, and is in some ways less efficient than using a simple MethodInvoker
or EventHandler delegate. These two delegates are treated in a special (fast) man-
ner by Invoke and BeginInvoke. MethodInvoker is just a delegate which takes no
parameters and returns no value (like ThreadStart), and EventHandler takes two
parameters (a sender and an EventArgs parameter and returns no value. However if
we pass an EventHandler delegate to Invoke or BeginInvoke then even if we specify
parameters ourself, they are ignored - when the method is invoked, the sender will
be the control we have invoked it with, and the EventArgs will be EventArgs.Empty.
// ThreadingForms.cs
using System;
using System.Threading;
using System.Windows.Forms;
using System.Drawing;
int target;
int currentCount;
Test()
{
Size = new Size(180,120);
Text = "Test";
target = rng.Next(100);
}
Thread t = new Thread(new ThreadStart(ThreadJob));
t.IsBackground = true;
t.Start();
}
void ThreadJob()
{
MethodInvoker updateCounterDelegate = new MethodInvoker(UpdateCount);
int localTarget;
lock(stateLock)
{
localTarget = target;
}
UpdateStatus("Starting");
lock(stateLock)
{
currentCount = 0;
}
Invoke(updateCounterDelegate);
// Pause before starting
Thread.Sleep(500);
UpdateStatus("Counting");
for(int i=0;i<localTarget;i++)
{
lock(stateLock)
{
currentCount = i;
}
// Synchronously show the counter
Invoke(updateCounterDelegate);
Thread.Sleep(100);
}
UpdateStatus("Finished");
Invoke(new MethodInvoker(EnableButton));
}
return;
}
// Must be on the UI thread if we’ve got this far
statusIndicator.Text = value;
}
void UpdateCount()
{
int tmpCount;
lock(stateLock)
{
tmpCount = currentCount;
}
counter.Text = tmpCount.ToString();
}
void EnableButton()
{
button.Enabled = true;
}
State is used to tell the worker thread what number to count up to. A delegate taking
a parameter is used to ask the UI to update the status label. The worker thread’s
principal method actually just calls UpdateStatus, which uses InvokeRequired
to detect whether or not it needs to ”change thread”. If it does, it then calls
BeginInvoke to execute the same method again from the UI thread. This is quite
a common way of making a method which interacts with the UI thread-safe. The
choice of BeginInvoke rather than Invoke here was just to demonstrate how to
invoke a method asynchronously. In real code, we would decide based on whether
we needed to block to wait for the access to the UI to complete before continuing
or not. It is quite rare to actually require UI access to complete first, so we should
use BeginInvoke instead of Invoke. Another approach might be to have a property
which did the appropriate invoking when necessary. It is easier to use from the
client code, but slightly harder work in that we would either have to have another
method anyway, or get the MethodInfo for the property setter in order to construct
the delegate to invoke. In this case we actually know that BeginInvoke is required
because we are running in the worker thread anyway. We do not call EndInvoke after
the BeginInvoke. Unlike all other asynchronous methods we do not need to call
EndInvoke unless we need the return value of the delegate’s method. BeginInvoke
122 CHAPTER 9. PROCESSES AND THREADS
is also different to all of the other asynchronous methods as it doesn’t cause the
delegate to be run on a thread pool thread. State is used again to tell the UI thread
how far we have counted so far. We use a MethodInvoker delegate to execute
UpdateCount. We call this using Invoke to make sure that it executes on the UI
thread. This time there’s no attempt to detect whether or not an Invoke is required.
If we call BeginInvoke it will have a different effect than calling the method directly
as it will occur later, rather than in the current execution flow, of course. Again,
we actually know that we need to call Invoke here anyway. A button is provided to
let the user start the thread. It is disabled while the thread is running, and another
MethodInvoker delegate is used to enable the button again afterwards. All state
which is shared between threads (the current count and the target) is accessed in
locks in the way described earlier. We spend as little time as possible in the lock, not
updating the UI or anything else while holding the lock. This probably doesn’t make
too much difference here. It would be disastrous to still have the lock in the worker
thread when synchronously invoking UpdateCount - the UI thread would then try
to acquire the lock as well, and we end up with deadlock. The worker thread is
set to be a background thread (IsBackground=true;) so that when the UI thread
exits, the whole application finishes. In other cases where we have a thread which
should keep running even after the UI thread has quit, we need to be careful not
to call Invoke or BeginInvoke when the UI thread is no longer running - we will
either block permanently (waiting for the message to be taken off the queue, with
nothing actually looking at messages) or receive an exception.
The BeginInvoke and EndInvoke methods can be used for asynchronous invoca-
tion of a method pointed by MyWorker delegate. In the program below the method
DoWork (it displays character c m number of times) is invoked asynchronously with
character ’+’, in the next statement this method is directly invoked with character
’*’. Both ’+’ and ’*’ are displayed (1000 times each) on the Console simultane-
ously. As an asynchronous call executes within its own background thread, we have
used Console.Read() to pause the main thread.
// Asynchronous.cs
using System;
class AsncTest
{
static MyWorker worker;
9.10 Timers
There are various different timers available in .NET, each of which basically calls
a delegate after a certain amount of time has passed. All the timers implement
IDispossible. so we have to make sure to dispose when we are not using them
anymore.
// Timers.cs
124 CHAPTER 9. PROCESSES AND THREADS
using System;
using System.Threading;
using System;
using System.Threading;
class SleepingThread
{
public static void Run()
9.11. INTERRUPT AND ABORT 125
{
for(int i=1;i<6;i++)
Console.WriteLine("Welcome {0}",i);
try
{
Thread.Sleep(5000);
}
catch(ThreadInterruptedException e)
{
Console.WriteLine("Sleep Interrupted");
}
Console.WriteLine("Goodbye");
}
// threadtest1.cs
using System;
using System.Threading; // for Thread class
class ThreadTest1
{
public static void SayHello()
{
for(int i=1;;i++)
{
Console.WriteLine("Hello {0}",i);
}
}
t.Start();
for(int i=1;i<1001;i++)
{
Console.WriteLine("Bye {0}",i);
}
t.Abort();
}
}
Chapter 10
Sockets Programming
10.1 Introduction
Most interprocess communication uses the client server model. These terms refer to
the two processes which will communicating with each other. One of the processes,
the client, connects to the other process, the server, typically to make a request for
information. A good analogy is a person who makes a phone call to another person.
The client needs to know of the existence of and the address of the server, but the
server does not need to know the address of (or even the existence of) the client
prior to the connection being established. Once a connection is established, both
sides can send and receive information.
The system calls for establishing a connection are different for the client and the
server, but both involve the basic construct of a socket. A socket is one end of an
interprocess communication channel. The two processes each establish their own
socket.
1. Create a socket
2. Connect the socket to the address of the server
3. Send and receive data
1. Create a socket
2. Bind the socket to an address. For the server socket on the Internet, an address
consists of a port number on the host machine.
3. Listen for connections.
4. Accept a connection. This call typically blocks until a client connects with the
server.
127
128 CHAPTER 10. SOCKETS PROGRAMMING
Thus besides the IPAddress we also need a port number (2 bytes) which is arbitrary
except for the well know port numbers associated with popular applications.
Example 1. When the server program is run it will indicate at which IPAddress
it is running and the port it is listening to. Now run the client program so that we
establish a connection with the server. When the connection is established the server
will display the IPAddress and port from where it has accepted the connection. The
client will ask for the string which is to be transmitted to the server. The server on
reciept of the string will display it, send an acknowledgement which will be received
by the client.
10.2. TRANSMISSION CONTROL PROTOCOL 129
// Server1.cs
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
class Server1
{
public static void Main()
{
try
{
// use local IP address and use the same in the client
IPAddress ipAd = IPAddress.Parse("152.106.40.84");
TcpListener listener = new TcpListener(ipAd,2055);
// start listening at the specified port 2055
listener.Start();
Socket s = listener.AcceptSocket();
Console.WriteLine("Connection accepted from " + s.RemoteEndPoint);
// Client1.cs
using System;
using System.IO;
using System.Text;
using System.Net;
using System.Net.Sockets;
class Client1
{
public static void Main()
{
try
{
TcpClient tcpclnt = new TcpClient();
Console.WriteLine("Connecting...");
tcpclnt.Connect("152.106.40.84",2055);
Console.WriteLine("Connected");
Console.Write("Enter the string to be transmitted: ");
String str = Console.ReadLine();
Stream stm = tcpclnt.GetStream();
ASCIIEncoding asen = new ASCIIEncoding();
byte[] ba = asen.GetBytes(str);
Console.WriteLine("Transmitting...");
stm.Write(ba,0,ba.Length);
byte[] bb = new byte[100];
int k = stm.Read(bb,0,100);
for(int i=0;i<k;i++)
Console.Write(Convert.ToChar(bb[i]));
// clean up
tcpclnt.Close();
} // end try
catch(Exception e)
{
Console.WriteLine("Error... " + e.StackTrace);
} // end catch
} // end Main
} // end class Client1
10.2. TRANSMISSION CONTROL PROTOCOL 131
The code between #if LOG and endif will be added by the compiler only if the sym-
bol LOG is defined during compilation (conditional compilation). Next we compile
the Client side
csc Client2.cs
Then on the Server side we start running the exe-file Server2 and finally we start
the exe-file Client2 on the Client side.
// Server2.cs
using System;
using System.Threading;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Configuration;
class EmployeeTCPServer
{
static TcpListener listener;
const int LIMIT = 5; // 5 concurrent clients
{
Socket soc = listener.AcceptSocket();
#if LOG
Console.WriteLine("Connected: {0}",soc.RemoteEndPoint);
#endif
try
{
Stream s = new NetworkStream(soc);
StreamReader sr = new StreamReader(s);
StreamWriter sw = new StreamWriter(s);
sw.AutoFlush = true; // enable automatic flushing
sw.WriteLine("{0} Employees available",ConfigurationManager.AppSettings.Count);
while(true)
{
string name = sr.ReadLine();
if(name == "" || name == null) break;
string job = ConfigurationManager.AppSettings[name];
if(job == null) job = "No such employee";
sw.WriteLine(job);
} // end while
s.Close();
} // end try
catch(Exception e)
{
#if LOG
Console.WriteLine(e.Message);
#endif
} // end catch
#if LOG
Console.WriteLine("Disconnected: {0}",soc.RemoteEndPoint);
#endif
soc.Close();
}
}
} // end class Server2
10.2. TRANSMISSION CONTROL PROTOCOL 133
<configuration>
<appSettings>
<add key="john" value="manager"/>
<add key="jane" value="steno"/>
<add key="jim" value="clerk"/>
<add key="jack" value="salesman"/>
</appSettings>
</configuration>
134 CHAPTER 10. SOCKETS PROGRAMMING
// Client2.cs
using System;
using System.IO;
using System.Net.Sockets;
class Client2
{
public static void Main()
{
TcpClient client = new TcpClient("152.106.40.84",2055);
try
{
Stream s = client.GetStream();
StreamReader sr = new StreamReader(s);
StreamWriter sw = new StreamWriter(s);
sw.AutoFlush = true;
Console.WriteLine(sr.ReadLine());
while(true)
{
Console.Write("Name: ");
string name = Console.ReadLine();
sw.WriteLine(name);
if(name == "") break;
Console.WriteLine(sr.ReadLine());
} // end while
s.Close();
} finally
{
// code in finally block is guranteed to execute irrespective
// whether any exception occurs or does not occur in the try block
client.Close();
} // end finally
} // end Main
} // end class Client2
10.3. USER DATAGRAM PROTOCOL 135
// UDPServer1.cs
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Configuration;
class ServerUDP
{
public static void Main()
{
UdpClient udpc = new UdpClient(2055);
Console.WriteLine("Server started servicing on port 2055");
IPEndPoint ep = null;
while(true)
{
byte[] rdata = udpc.Receive(ref ep);
string name = Encoding.ASCII.GetString(rdata);
string job = ConfigurationManager.AppSettings[name];
if(job==null) job = "No such employee";
byte[] sdata = Encoding.ASCII.GetBytes(job);
udpc.Send(sdata,sdata.Length,ep);
} // end while
} // end Main
}
<configuration>
<appSettings>
<add key="john" value="manager"/>
<add key="jane" value="steno"/>
<add key="jim" value="clerk"/>
<add key="jack" value="salesman"/>
</appSettings>
</configuration>
136 CHAPTER 10. SOCKETS PROGRAMMING
// UDPClient1.cs
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class ClientUDP
{
public static void Main()
{
UdpClient udpc = new UdpClient("152.106.40.84",2055);
IPEndPoint ep = null;
while(true)
{
Console.Write("Name: ");
string name = Console.ReadLine();
if(name == "") break;
byte[] sdata = Encoding.ASCII.GetBytes(name);
udpc.Send(sdata,sdata.Length);
byte[] rdata = udpc.Receive(ref ep);
string job = Encoding.ASCII.GetString(rdata);
Console.WriteLine("job = " + job);
} // end while
} // end Main
}
csc UDPServer1.cs
csc UDPClient1.cs
Then on the Server side we start running the exe-file UDPServer1 and finally we
start the exe-file UDPClient1 on the Client side.
Chapter 11
Remoting
11.1 Introduction
Object running in one Virtual Machine (VM) should be able to access (call functions
on) objects that are in a different Virtual Machine. Usually this means the two
processes are running on two different computers. To set up remoting we proceed
as follows:
137
138 CHAPTER 11. REMOTING
Serializable == pass-by-value
MarshalByRefObject == pass-by-reference
[Serializable]
public class Counter {
int count = 0;
public void incCount() { count++; }
public int getCount() { return count; }
}
We have to import:
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http; // for Http
using System.Runtime.Remoting.Channels.Tcp; // for Tcp
Shared objects must be available to both the client and the server. Make a separate
assembly for it
There are three main items in the process of remoting. The Interface, the Client
and the Server. The Interface is a .dll file (generated from a .cs file) which is on
the Client and Server side. The Server provides an implementation of the Interface
and makes it available to call. The Client calls the Server using the Interface.
140 CHAPTER 11. REMOTING
Example. The client provides a string to the server and the server returns the
length of the string.
// MyInterface.cs
On the Server side we now create our class which we invoke from a remote ma-
chine (client). RemoteObject.cs is on the Server side and is the implementation of
MyInterface.cs.
// RemoteObject.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
Any class derived from MarshalByRefObject allows remote clients to invoke its
methods. Now on the Server side we compile
// ServerTcp.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
First we create a TcpChannel object which we use to transport messages across our
remoting boundary. We select 9999 as the TCP port to listen on. This could cause
problems with firewalls. We use
ChannelServices.RegisterChannel
RemoteConfiguration.RegisterWellKnownServiceType
to register our RemoteClass object as a well-known type. Now this object can be
remoted.
We are using the string FirstRemote here which will be used as part of the URL
that the client uses to access the remote object. We use SingleCall mode here
which means a new instance is created for each remote call.
Remark. One could have also used Singleton in which case one object would have
been instantiated and used by all connecting clients.
// ClientTcp.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
Just as in the Server we have a TcpChannel object, though in this case we do not
have to specify a port. We also use
ChannelService.RegisterChannel
Activator.GetObject
RemotingConfiguration.RegisterWellKnownServiceType
on the Server.
To run everything on the Server side we enter the exe file at the command line
ServerTcp
Then on the Client side we enter the exe file at the command line
ClientTcp
Then we are provide with the length of the string. In the present case 16.
144 CHAPTER 11. REMOTING
Instead of using Tcp we can also use Http. Then the files ServerTcp.cs and
ClientTcp.cs are replaced by ServerHttp.cs and ClientHttp.cs shown below.
// ServerHttp.cs
// on Server side
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
// Client.cs
// on client side
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
Accessing Databases
12.1 Introduction
The .NET Framework uses ADO.NET to access databases. ADO.NET is a set of
managed classes within the .NET Framework. It operates on a disconnected data
access model. When an application requests data a new database connection is
created and destroyed when the request is completed. A disconnected data access
model is frugal with resources, which is desirable.
ADO.NET allows access to many different databases. There are two basic types of
connections. SQLClient is used for Microsoft’s SQL Server and OLEDB is used for
all other database formats.
In order to use SQL with SQL Server or SQL Server Express the following namespace
must be specified.
using System.Data.SqlClient;
using System.Data.OleDb;
Accessing a database requires that a .NET Data Provider is set up. A Data Provider
consists of
• Connection
• Command
• DataReader
• DataAdapter
146
12.2. EXAMPLES 147
The Connection creates the actual connection to the database. The Connection
object contains all the information needed to open the connection, for example the
userid and password. A Connection String is required to connect to a database.
The easiest way to find the correct connection string for a DBMS is to search the
Web. A good site to start with is http://www.connectionstrings.com.
In order to open and close a database connection the Connection methods Open
and Close are used respectively.
The Command executes an instruction against the database. For example, this
could be a SQL query or a stored procedure.
A DataReader is used for fast and efficient forward-reading, read-only database ac-
cess. It returns individual rows directly to the application. The data is not cached.
12.2 Examples
The following program shows how to connect to a Microsoft SQL Server 2005 or
Sql Express database and retrieve some basic information about the server. Care
must be taken is creating an appropriate connection string (CON STRING in these
examples). Knowledge of SQL is required.
using System;
using System.Data;
using System.Data.SqlClient;
using System.Text;
namespace csDB
{
class OpenSQLDB
{
public const string CON_STRING = "Data Source=mySqlDB;Initial
Catalog=dbTest;User ID=JoeUser;Password=User1234";
148 CHAPTER 12. ACCESSING DATABASES
Embedding the user ID and password, as in the above code, is not a good idea; a
change in the database means the code will have to be modified and recompiled. In
addition, it is not secure. ASP.NET solves this problem by using the web.config file.
<configuration>
<appSettings>
<add key="DBConnect"
value="server=mySqlDB.newdb,net;initial
catalog=dbTest;user id=JoeUser;pwd=User1234;"/>
<appSettings>
...
<\configuration>
The following line of code fetches the connection string from web.config.
string CON_STRING=ConfigurationSettings.Appsettings["DBConnect"];
The basic method of retrieving data using a Data Reader is as follows. A standard
SQL query is used and submitted to the database via a SqlCommand object. In the
case of data retrieval the SELECT statement is used.
while(myReader.Read())
{
// Do something with the data
// In the following code, the myReader[0] accesses the FIRST field of data
// the selected record. myReader[1] then accesses the SECOND field.
// string strTmp = myReader[0].ToString() + " " + myReader[1].ToString();
}
myReader.Close();
string strSearch =
"SELECT * FROM tblCustomer where fldCustName LIKE ’" +
TextBox1.Text.Trim() + "%’"; // SQL uses a % for wildcard
myReader = cmDB.ExecuteReader();
The following code shows how to access particular fields using a Data Reader.
while (rdr.Read())
{
id = (int) rdr["fldCustomerID"];
strName = (string) rdr["fldCustName"];
// etc etc
TextBox1.Text = id + " " + strName;
150 CHAPTER 12. ACCESSING DATABASES
rdr.Close();
connection.Close();
The following code shows how to insert a row into a database, using a SQL INSERT
command.
String CON_STRING
= @"Data Source=mname\sqlexpress;" +
"Initial Catalog=dbCustomer" +
";Integrated Security=True";
SqlConnection connection = new SqlConnection(CON_STRING);
connection.Open();
String MyString
= @"INSERT INTO tblCustomer(fldCustName, fldCustSurname)" +
"VALUES(’Angus’, ’MacBeth’)";
SqlCommand MyCmd = new SqlCommand(MyString, connection);
MyCmd.ExecuteScalar();
connection.Close();
It is likely that the C# programmer will have a need to extract some data from a
TextBox, either on a Windows or Web Form, and insert that value into a table. In
the case of wanting to insert the values from two TextBoxes called txtFirstName
and txtSurname, you would have the following statement in C#.
String MyString
= @"INSERT INTO tblCustomer(fldCustName, fldCustSurname)"
+ "VALUES(’"
+ txtFirstName.Text.Trim()
+ "’, ’"
+ txtSurname.Text.Trim()
+ "’)";
The following code shows how to edit a row in a database, using a SQL UPDATE
command.
string CON_STRING =
@"Data Source=mname\sqlexpress;" +
"Initial Catalog=dbCustomer;Integrated Security=True";
MyCmd.ExecuteNonQuery();
connection.Close();
The following code shows how to delete data in a database, using a SQL DELETE
command. This program uses a TRY... CATCH block for exception handling.
string CON_STRING =
"Data Source=mname\\sqlexpress;" +
"Initial Catalog=dbCustomer;" +
"Integrated Security=True";
try
{
connection.Open();
String MyString = @"DELETE FROM tblCustomer " +
"WHERE fldCustName = ’Greg’";
SqlCommand MyCmd = new SqlCommand(MyString, connection);
MyCmd.ExecuteScalar();
}
catch (Exception ex)
{
TextBox1.Text = ex.Message;
}
finally
{
connection.Close();
}
Chapter 13
ASP.NET
13.1 Introduction
ASP.NET is Microsoft’s Web technology built on the .NET Framework. Of all
the available .NET languages, C# is perhaps the best language to write ASP.NET
applications. This chapter outlines some additional code that is required when pro-
gramming web applications.
The following code retrieves the data from the Session object.
HyperLink1.NavigateUrl = "default.aspx?name=evan";
For the longer term storage of data, cookies can be used. Below is some code for
creating cookies.
152
13.3. PAGE LOAD 153
// Cookie name
String strCookieName = "Names";
cookie.Expires = DateTime.Now.AddDays(-1);
Response.Cookies.Add(cookie);
if(!IsPostBack)
{
// Code in this block will only execute once
StateDropDownList.Items.Add{"Gauteng"};
StateDropDownList.Items.Add{"Northern Province"};
}
Bibliography
[1] http://www.codeproject.com/csharp
155
Index