.Net Architecture and C#
Programming
.Net architecture provides a common class library and a safe execution environment for developing and executing object-oriented programs in any .Net language. The main architecture of .Net platform is shown below.
.Net programming can be carried out in any .Net compliant language. Current choices include twenty seven different programming languages, the most popular being C#. Other languages include VB.Net, J#.Net, Jscript.Net and managed C++. A .Net program is first compiled to an IL (intermediate language) code called MSIL, which is similar in concept to the Java bytecode. The CLR then translates it to the target machine code by a just in time (JIT) compilation process. So technically speaking, a .Net program can execute on any operating system (OS) if the proper CLR exists. .Net provides full language independence and integration because of a common class library which is often referred to as the Framework Class Library(FCL). A class developed in one .Net language can be derived from a class written in another .Net language.
Of all the .Net language, C# is considered to be the most powerful language as it has been elegantly designed by borrowing some of the best features of C++, Java, Visual Basic and Delphi languages. Syntactically, C# is similar to C++ and Java languages. Some of its important principles such as automatic garbage collection, translating the code into an intermediate language before execution, providing a CLR at run time for safe execution of programs, are very similar to Java language. However, unlike Java, C# is always ultimately compiled into machine language, supports operator overloading, and allows interaction with direct memory access (pointers) through an unsafe block mechanism.
Important
Features of C#:
short s1;
int a = 25;
s1 = a ; // error, proper way is : s1 = (short) a;
Similarly a decimal literal is a double and cannot be assigned to a float type variable, e.g.,
float f1 = 2.35; // error, correct way is: float f1 = 2.35f;
if (a = 5) will cause a compiler error as the return from a = 5 is 5.
switch (a) {
case 5 : a = 22; goto case 6; // compiler error without goto
case 6: b = b – 1; break;
default: b = 10; break;
}
Note that a default constructor for a base class does not need to be called explicitly. It will be implicitly called. You can also use the base keyword to call any method in the base class e.g., base.f1( );
csc filename.cs
To run the program, simply type filename
Alias .Net Class Range
byte System.Byte (0-255), sbyte System.Sbyte (-128 to 127)
char System.Char (Unicode)
bool System.Boolean (true or false)
short System.Int16 (16 bits) ushort (unsigned short)
int System.Int32 (32 bits) uint (unsigned int)
long System.Int64 (64 bits) ulong (unsigned long)
float System.Single (32 bits)
double System.Double (64 bits)
decimal System.Decimal 28 digits, for financial calculations,
requires suffix m or M
Integer values are not implicitly converted to Boolean type
const float pi = 3.141517f;
\\ for a single backslash, \n, \r, \t, \’ for a single quote, \” for a double quote..
Example:
int a;
int b = 5;
System.Console.WriteLine(“ a = {0}, b = {1}”, a, b);
The above program will generate a compiler error because a is not initialized and you are trying to print its value..
[attributes] [modifiers] enum identifier [:base type]
{
enumeration list
}
Example:
enum Colors
{
Red = 1,
Green = 2,
Blue = 3
}
An explicit conversion to a type is required before assigning the enumerated constant to a variable.
In your code, you can use the above defined enumeration as,
int myColor = (int) Colors.Red;
enum Greet : int
{
Hello, // will have a value 0
Hi, // will have a value 1
Bye = 5,
Adios // will have a value 6
}
If you are creating a class and wanted to use the enumeration inside it, you can declare the enumeration and use it as shown below.
class xyz {
enum Sizes : int
{
Small = 2,
Medium = 5,
Large = 8,
ExtraLarge = 10
}
static void Main()
{
System.Console.WriteLine(“ Large Size = {0}”,Sizes.Large);
}
}
Visual
Studio .Net – Creating a simple C# Console Program:
Run Visual Studio .Net. From the file menu, choose new project. Give the project name “First” as shown below: Make sure you choose the project type to be console, also select visual C# Projects. You may want to create a special directory for your C# projects.
After you choose OK, the wizard will generate the following code for you.
You can modify the above code to as shown below. Notice that the name of the class has been changed to Welcome. Also, this class will be stored in a file called Welcome.cs. You can change the file name by selecting the Class1.cs on the right hand side in the solution explorer, then changing the file name to Welcome.cs.
From the Build menu, you can now build the project. Then you can run it by choosing Debug->Start without debugging.
Visual Studio .Net comes with an excellent debugger similar to Visual Studio 6. You can modify the program to as shown below. Then set a break point by simply clicking in the bar area on the left hand side. Then you can choose debug-> start and step through the program by hitting F10.
Creating
Classes in C#:
Example: Create a console application called “MyTime”. Change the name of the class to Time. Type the following code:
using
System;
namespace
MyTime
{
/// <summary>
/// Summary description for Time.
/// </summary>
public class Time
{
///
<summary>
/// The main entry
point for the application.
///
</summary>
int Year;
int Month;
int Date;
int Hour;
int Minute;
int Second;
public Time()
{ // constructor
System.DateTime
dtm = System.DateTime.Now; // current date and time
this.Year
= dtm.Year;
this.Month
= dtm.Month;
this.Date
= dtm.Day;
this.Hour
= dtm.Hour;
this.Minute
= dtm.Minute;
this.Second
= dtm.Second;
}
public
Time(System.DateTime dtm)
{ // constructor
this.Year
= dtm.Year;
this.Month
= dtm.Month;
this.Date
= dtm.Day;
this.Hour
= dtm.Hour;
this.Minute
= dtm.Minute;
this.Second
= dtm.Second;
}
public void
PrintDate()
{
Console.WriteLine("Date={0}/{1}/{2}",Month,Date,Year);
}
public void
PrintTime()
{
Console.WriteLine("Time={0}:{1}:{2}",Hour,Minute,Second);
}
public void
PrintDateAndTime()
{
Console.WriteLine("{0}/{1}/{2}
{3}:{4}:{5}",
Month,Date,Year,
Hour,Minute,Second);
}
}// end of class Time
class Test1 { // tester class for the Time class
static void
Main(string[] args)
{
//
//
TODO: Add code to start application here
//
Time
t1 = new Time();
t1.PrintDate();
t1.PrintTime();
t1.PrintDateAndTime();
}
}
}
Run the program by choosing, Debug->Start without Debugging. The output will look something like:
Exercise: Modify the Main program so that it initializes the T1 object by calling the constructor that takes an object of DateTime class instead of the empty constructor.
C# does provide a default constructor which initializes all data members to their default values i.e., integers to zero, char to ‘\0’, bool to false etc..
Exercise: Modify the above program to include a copy constructor, as shown below.
public Time (Time rhs) {
Year = rhs.Year;
Month = rhs.Month;
Date = rhs.Date;
Hour = rhs.Hour;
Minute = rhs.Minute;
Second = rhs.Second;
}
You can invoke the above copy constructor by modifying the code in the Main as:
static void Main(string[] args)
{
DateTime dt = DateTime.Now; // get current date and time
Time t1 = new Time(dt);
t2.PrintDate();
t2.PrintTime();
t2.PrintDateAndTime();
}
System.Object
Class:
All classes in C# are derived from
System.Object class. Object class provides six methods as shown below. Many of
these objects are overridden by derived classes to provide a correct
implementation (ToString in particular).
Method Explanation
Equals Determines if two objects
are equal
GetHashCode Hash code identifying an object
(useful in collections).
GetType Returns the type of the
object
ToString String representing the
state of the Object
Finalize For cleanup of resources
MemberwiseClone To create copies of the object.
Formatting
Output:
When you use the WriteLine method, e.g.,
Console.WriteLine(“ a = {0:[FormatCharacter[Number]]}”,a);
Optionally, you can specify a formatting character and a number indicating the appropriate formatting field.
Formatting Character Effect
C or c Currency, adds a $ sign and rounds to two decimal places
D or d Decimal numbers e.g., D7 will give 7 spaces for the number
E or e Exponent notation i.e., Scientific form
F or f For floating point numbers, e.g., F3 will yield 3 decimal places
N or n Basic Numeric formatting with commas.
X or x Hexadecimal format.
Example: The following code segment produces output as shown below:
int
n1 = 987;
float
f1 = 2.5467f;
Console.WriteLine("n1
= {0:D7}, f1={1:F2}",n1,f1);
Console.WriteLine("n1 = {0:X}, f1={1:e}",n1,f1);
Passing Parameters:
C# passes value types by value, and objects by reference by default. However, a ref keyword is provided so that value types can be passed by reference. Further, an out keyword is provided so that those ref parameters that are not initialized can be passed successfully to a function.
Example:
public
void exchange(ref
int p, ref int q)
{ // added as a member function to tester class
int
temp;
temp = p;
p = q;
q = temp;
}
//----partial code from main method
tester test = new tester();
int l, m;
l = 5; m = 7;
Console.WriteLine("Before Exchange L = {0}, M = {1}",l,m);
test.exchange(ref l,ref m);
Console.WriteLine("After
Exchange L = {0}, M = {1}",l,m);
Now suppose the exchange function has a third parameter which the exchange function will modify to be the sum of the first two parameters. In this case, it does not make sense to explicitly initialize the third parameter before calling the function, i.e., the call might look as:
int n;
test.exchange(ref l,ref m, n);
However, the above code will generate
compiler error, as n has not been initialized. The solution to this is the out
parameter. The correct code is shown below.
public
void exchange(ref
int p, ref int q, out int r)
{
int
temp;
temp = p;
p = q;
q = temp;
r = p + q;
}
//---partial code from
Main method
tester test = new tester();
int l, m;
l = 5; m = 7;
int n;
Console.WriteLine("Before
Exchange L = {0}, M = {1}",l,m);
test.exchange(ref l,ref m, out n);
Console.WriteLine("After
Exchange L = {0}, M = {1}, N = {2}",l,m,n);
Variable
Number of Parameters:
C# allows the last parameter of a method to be prefixed by the “params” keyword. This indicates that the method can accept multiple values for this parameter of the same type.
Example:
Welcome
w1 = new Welcome();
float a1 = 2.5f;
float a2 = 4.8f;
Console.WriteLine("Average
= {0}",w1.ComputeAvg(2,a1,a2));
}
public float
ComputeAvg(int size, params float[] vals)
{
float sum = 0;
for (int i = 0; i
< size; i++)
sum =
sum + vals[i];
return sum/size;
}
Static
Members and Methods:
C# does not allow standalone functions, or global variables. One way to achieve a global function’s equivalent would be through static member functions. To invoke a static member function, you do not need to create an object of the class. You simply invoke it through the class name.
Example: If a class called XYZ has a static function called f1 in it. Then you will invoke the f1 function as: XYZ.f1( );
NOTE: Static member functions cannot access nonstatic members.
Example: Add a class called XYZ to an existing project (by right clicking on the project name and choosing add new class). The code is shown below.
using System;
namespace OperatorOverload
{
/// <summary>
/// Summary description for
XYZ.
/// </summary>
public class XYZ
{
int p, q;
public XYZ()
{
p =
5; q = 7;
}
public static void f1() // static member
cannot access non static member
{
//Console.WriteLine("p = {0}, q = {0}",p, q); //
will not work
Console.WriteLine("XYZ
static f1 called");
}
}
}
Now from the
tester class’s main method, you can invoke the XYZ static function f1 as,
XYZ.f1( );
Static
Constructors and Static data members:
A static constructor is executed only once before any object of the class it belongs is instantiated. Static constructors can be used to provide an initialization of an environment for all objects.
A common use of a static data member is to provide a count for how many objects of a class has been created. Note that a static data member is shared between all objects of a class.
Example: Modify the code in the XYZ.cs file to include a static constructor and a static data member as shown below.
using System;
namespace OperatorOverload
{
/// <summary>
/// Summary description for
XYZ.
/// </summary>
public class XYZ
{
int p, q;
static int Count;
static XYZ() // static constructor called once in the beginning
{
Count=100;
}
public XYZ() // normal constructor
{
p =
5;
q =
7;
Count++;
}
public static void f1() // static member
cannot access non static member
{
//Console.WriteLine("p = {0}, q = {0}",p, q); //
will not work
Console.WriteLine("XYZ
static f1 called, Count=" + Count);
}
}
}
You can modify the code in the main to invoke the XYZ methods to as shown below:
XYZ.f1(); // will print 100
XYZ xyz = new XYZ();
XYZ.f1(); // will print 101
Note that, often we
write static methods to provide access to private static data members.
Finalize
Method:
C# does not have destructors as objects allocated on the heap are automatically freed by the garbage collector once their reference count goes to zero. However, in some cases, if the object is holding to some unmanaged resources (e.g., file handles), we need to write a Finalize method in our class to free such resources. The Finalize method is called by the Garbage collector just before it frees the object.
Note that you should not try to call the Finalize method yourself. In C#, you write the Finalize method by actually writing as destructor, e.g., the Finalize method for the XYZ class will look as,
~XYZ() // equivalent of Finalize
method
{ // automatically calls
base.Finalize
Console.WriteLine("Finalize called ");
}
C# does not allow you to override the Finalize method in the System.Object class. Even though there is no code in the Finalize method of the Object class, you effectively override it by creating a destructor.
There are situations in which you may want to release the resources yourself as early as possible. In such situations, your class should implement the IDisposable interface. This interface has only one method called Dispose in which you will free the resources acquired and suppress the calling of the Finalize method. For example, the XYZ class would be modified as,
public class XYZ : IDisposable
…
public
void Dispose()
{
Console.WriteLine("Disposing
resources ");
//
release the resources
GC.SuppressFinalize(this); // no need for GC to
call Finalize now
}
The code in the main that invokes the Dispose method would look like:
XYZ xyz = new XYZ();
XYZ.f1();
xyz.Dispose();
Using Statement for Early
Disposing of Objects:
Instead of having the client call Dispose method explicitly, C# provides an alternative using statement. The object on which you would like to invoke the Dispose method is created and its use is marked inside a using statement block e.g.,
XYZ xyz = new XYZ();
using (xyz) {
// --- use the xyz object
} // now Dispose will be automatically called for xyz here
The biggest advantage of the using statement is that even in case of exceptions, the Dispose method is guaranteed to be invoked.
Operator Overloading: C# supports operator overloading somewhat similar to C++. All operators are declared static so binary operator functions take two parameters instead of one. C# requires you to overload some operators in pairs i.e., both = = and != should be overloaded. Similarly if you overload >, you have to overload < operator as well. Further C# recommends that if you overload the = = operator, you should also override the Equals member function (override it from the base class Object). Since some .Net languages do not provide operator overloading (e.g., VB.Net), it is a good practice to provide alternative functions to the operators you are overloading e.g., if you overload +, provide a member function called Add, that does the same thing.
Example: Create a console C# application. Name the project “OperatorOverload”. By right clicking on the project, add a class to the project called x. Type the following code in it:
//-------------------x.cs----------------------------------
using System;
namespace
OperatorOverload
{
/// <summary>
/// Summary description for
x.
/// </summary>
public class x
{
int a, b;
public x()
{
a
= 0; b = 0;
}
public x (int a1, int b1) // another constructor
{
a
= a1; b = b1;
}
public override string ToString()
{
string s = " a=" + a + " b=" + b;
return s;
}
public static x operator + (x lhs, x rhs) //
objects are passed by reference
{
x
temp = new x(); // compare this to C++ approach
temp.a
= lhs.a + rhs.a;
temp.b
= lhs.b + rhs.b;
return temp;
}
public x Add(x rhs) //
alternate method for .Net languages
{ // that do not
support overloading operators
x
temp = new x();
temp.a
= this.a + rhs.a;
temp.b
= this.b + rhs.b;
return temp;
}
public static bool operator == (x
lhs, x rhs)
{
if ((lhs.a == rhs.a) && (lhs.b == rhs.b))
return true;
else
return false;
}
public override bool Equals (object
rhs)
{
if (! (rhs is x))
return false;
else
{
if ((this.a ==
((x)rhs).a) && (this.b == ((x)rhs).b))
return true;
else
return false;
}
}
public static bool operator != (x
lhs, x rhs)
{
if ((lhs.a != rhs.a) || (lhs.b != rhs.b))
return true;
else
return false;
}
}
}
Change the name of the default class, class1 to tester. Type the following code in the tester class.
// -----------------tester.cs---------------------------------
using System;
namespace OperatorOverload
{
/// <summary>
/// Summary description for Class1.
/// </summary>
class
tester
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static
void Main(string[]
args)
{
x x1 = new x(2,3);
x x2 = new x(3,5);
x x3 = new x();
x3 = x1 +x2;
x3 = x1.Add(x3);
if
(x1 == x2)
Console.WriteLine("x1
and x2 are equal");
else
Console.WriteLine("no
they are not equal");
Console.WriteLine(x3.ToString());
Console.WriteLine(x1.GetHashCode());
Console.WriteLine(x2.GetHashCode());
}
}
}
Build and run the above program by selecting “Start Without Debugging” from the Debug menu.
Properties in C#:
C# has a new construct called properties for allowing controlled access to data members of a class. In C#, you declare a property just like you declare a data member, but instead of writing an explicit get function and set function, you write get and set accessors tied to this property.
public
class x
{
private
int a, b;
public int A
{
get
{
return a;
}
set
{
a = value;
}
}
public int B
{
get
{
return b;
}
set
{
b = (int) value;
}
}
Now someone can create an object of the class
x, and use the properties as:
X x1 = new x();
x1.A=5; x1.B=7;
Console.WriteLine(“x1.a = {0}, x1.b = {1}”,x1.A, x1.B);
ReadOnly Fields:
C# provides a readonly keyword that is typically used with static data members of a class. The idea is that the static constructor would initialize these static data members and then the readonly keyword will prevent the user from modifying its value.
Example:
public static readonly int ID;
Overriding Inherited Members – override and new
keywords:
C# requires (actually recommends by compiler warnings) an explicit keyword called override when overriding a method in a derived class. If your intention is simply to declare a completely new method in the derived class, then you use the new keyword with the method. This greatly helps in alleviating the versioning problems in case of base and derived classes.
Example:
class Circle : Shape {
…
public override void ComputeArea( ){ // overrides the base class function
….
}
public new void DrawDotted( ) { // now if some one added a DarwDotted
….// function to base class, code will not break
}
Example: Create a Console type application called OverrideNew. Add a class called CBase to it with the the following code.
//----------CBase.cs-------------
using System;
namespace OverrideNew
{
/// <summary>
/// Summary description for
CBase.
/// </summary>
public class CBase
{
public CBase()
{
}
public virtual void f1() // virtual is
important in order for base class
{ //
references to successfully call f1 in derived class
Console.WriteLine("F1 in Base Class");
}
}
}
Add another class to the project called CDerived with the following code:
//--------CDerived.cs--------------
using System;
namespace OverrideNew
{
/// <summary>
/// Summary description for
CDerived.
/// </summary>
public class CDerived : CBase
{
public CDerived()
{
}
public void f1() //compiler generates warning without override
{
Console.WriteLine("F1 in Derived class");
}
public void f2() // compiler generates warning without new
{ // when f2 is added to the base class
Console.WriteLine("F2 in Derived - new function added");
}
}
}
Modify the default class1 by changing its name to Tester. The code in tester will look as:
//--------Tester.cs-----------
using System;
//----------Tester.cs--------------
namespace OverrideNew
{
/// <summary>
/// Summary description for
Class1.
/// </summary>
class Tester
{
/// <summary>
/// The main entry point
for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
CBase b1 = new CDerived();
b1.f1();
if (b1 is CDerived)
{
CDerived d1 = (CDerived) b1;
d1.f2(); // works OK until
some one adds f2 to base class
} // now
compiler generates a warning message
}
}
}
If you compile and run the above program, it will produce the output as:
However, if you modify the base class by adding the f2 function as shown below:
Add the following function to the CBase class.
public virtual void f2( ) {
Console.WriteLine("F2 in Base Class");
}
Now the compiler will generate a warning message as shown below.
d:\csharpprogs\overridenew\cderived.cs(18,15): warning CS0108: The keyword new is required on 'OverrideNew.CDerived.f2()' because it hides inherited member 'OverrideNew.CBase.f2()'
To fix this warning, you can modify the f2 function in CDerived.cs as:
public new void f2() // compiler generates warning without new
{ // when f2 is added to the base class
Console.WriteLine("F2 in Derived - new function added");
}
Calling Base Class Functions
and Constructors:
In C#, classes do not inherit constructors, so a derived class has to explicitly call a base class constructor e.g., the CDerived constructor might look as:
CDerived(int m, int n ) {
base(m,n ); // call base class constructor
// do some more initialization
}
The default constructor in the base class does not need to be called explicitly. Note also that if you write a constructor in a class, the default constructor is not provided by the compiler. In this case, you need to write a default constructor as well in most situations.
If
there is a method in the base class that you need to invoke, you can do it by
using the base keyword e.g., base.f1( );
Abstract Methods and
Abstract Classes:
If it does not make sense to provide implementation for a method in a class, it can be marked as abstract. If a class contains one or more abstract methods, it also needs to be marked as abstract e.g.,
abstract public class Shape {
protected int x1;
protected int y1;
protected int x2;
protected int y2;
Shape(int x1, int y1, int x2, int y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
abstract public void Draw( );
}
public class Rectangle : Shape{
protected int fillColor;
Rectangle(int x1, int y1, int x2, int y2, int fillColor) {
base(x1,y1,x2,y2);
this.fillColor = 2;
}
public override void Draw( ) {
// drawing code for the rectangle
}
}
It is not possible to create an object of an abstract class. A derived class can implement the abstract method(s) using the override keyword. If the derived class does not implement the abstract method, then it too becomes an abstract class and thus cannot be instantiated.
Sealed Classes:
A class can be marked as sealed indicating that it cannot be derived from e.g.,
sealed public class X {
// data members and methods
}
Now if some one tried to derive a class from Z as:
class MyX : X {
// …
}
Compiler will generate an error message.
As an example, the Garbage Collector class (System.GC class) in the .Net framework is declared as a sealed class.
Boxing and UnBoxing Types:
Boxing refers to converting a value type to an object type, and unboxing refers to the opposite. Boxing is carried out implicitly in C# whereas you have to use type casting to unbox to an appropriate data type.
Example of Boxing:
int i = 5;
Console.WriteLine(“ i = {0}”, i);
WriteLine method requires an object, so in the above statement integer i is implicitly boxed to an object and passed to the WriteLine method.
Example of Unboxing:
int i = 5;
object obj = i; // boxing is implicit
int j;
j = (int) obj ; // to unbox, you have to 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.
Arrays and Collections:
There are many useful collections in the .Net framework, such as Array, ArrayList, Queue, Stack, Hashtable, BitArray etc..
The simplest of the above collections is an Array. The System.Array class class provides many useful methods such as:
Copy( ) - copies one section of an array to another.
Sort( ) – sorts the values in a 1-D array.
BinarySearch( ) - searches a 1-D sorted array.
Reverse( ) - reverses the order of elements in a 1-D array.
IndexOf( ) - returns the index of first occurance of a value in 1-D array.
Length - returns length of the array.
To declare and instantiate an array, you have to use the new operator as,
int [] myArray = new int[5];
The default value for integer array elements is 0.
Alternatively, you can declare and initialize the array at the same time as,
int [] myArray = { 3, 5, 7, 9, 11} ;
To traverse an array, you can either use the for loop as,
For (int i =0; i < myArray.Length; i++)
sum = sum + myArray[i];
Or you can use the foreach statement as:
foreach(int val in myArray)
sum = sum + val;
Multidimensional Arrays:
You can declare a two dimensional array in C# as:
float [,] Matrix = new float[rows, columns];
then you access the 2-D array as:
for (int i = 0; i< rows; i++)
for (int j = 0; j < columns; j++)
Matrix[i,j] = i*j;
C# also supports jagged arrays where the rows in a multi-dimensional array can be of different size.
Indexers:
C# does not allow you to overload the [] operator, but has another mechanism known as the indexer that achieves the same goal. The indexer is declared as a property in a class, and you provide the set get methods of this property to decide how the [] operator will work.
Example: Suppose you created a class in a project called Student whose code looks as:
//------Student.cs----------
using System;
namespace First
{
public class Student
{
private int id;
public int ID
{
get { return id; }
set { id = value; }
}
private string lname;
public string Lname
{
get { return lname; }
set { lname = value;
}
}
public Student(int
id, string lnm)
{
this.id = id;
this.lname = lnm;
}
}
}
Suppose you also created another class called University which can have an array of Student objects in it. In this class, we will declare an indexer on the student ID so that a client can simply use the university object as an array of students. The indexer code is shown in bold below.
//-------------University.cs--------------
using System;
namespace First
{
public class
University
{
private string uname;
public string Uname
{
get { return uname; }
set { uname = value;
}
}
private Student [] stds;
public University()
{
// initialize the three students
this.uname = "University of Bridgeport";
stds = new Student[3];
stds[0]
=new Student(100, "Baker");// IDs start at 100
stds[1]
=new Student(101, "Harris");
stds[2]
=new Student(102, "Mahmood");
}
public Student this[int stid] // creating
indexer
{
get
{
if (((stid-100) > 0) && ((stid-100) >=
stds.Length))
{
Console.WriteLine("incorrect ID
range");
return null;
}
else
return stds[stid-100];
}
set
{
if (((stid-100) > 0) && ((stid-100) >=
stds.Length))
{
Console.WriteLine("incorrect
ID range");
return;
}
else
stds[stid-100]
= (Student) value;
}
}
}
}
The nice thing about an indexer is that you can use strings or other objects inside the [] as long as you have provided (overloaded) the proper indexer inside the class. For example, if we wanted to index a University object on “Lname”, we can add the following indexer to the University class.
public Student this[string lnm] //creating
another indexer
{
get
{
bool found = false;
int i;
for (i = 0; i < stds.Length; i++)
{
if (stds[i].Lname == lnm)
{
found
= true;
break;
}
}
if (found == true)
return stds[i];
else
{
Console.WriteLine("No
student exists with this name {0}",lnm);
return null;
}
}
set
{
bool found = false;
int i;
for (i = 0; i < stds.Length; i++)
{
if (stds[i].Lname == lnm)
{
found
= true;
break;
}
}
if (found == true)
stds[i]
= (Student) value;
else
{
Console.WriteLine("No
student exists with this name {0}", lnm);
return;
}
}
}
The client code that invokes the above two types of indexers in the University class will look as:
University
u1 = new University();
u1.Uname
= "Boston University";
// trying the ID indexer
Console.WriteLine("Student
ID 100 Last Name={0}",
u1[100].Lname);
u1[101].Lname
= "Harrison";
Console.WriteLine("Student
ID 101 New Last Name={0}",
u1[101].Lname);
// trying the other lname indexer
Console.WriteLine("Student
Harrison ID = {0}",
u1["Harrison"].ID);
If the above code was typed in the Main method of a console application, then when you run the program, the output will appear as:
Hashtables:
Hashtable class implements the hash table data structure where you can store a key, value pair. The key will be placed in the Hashtable based on Hash code. If you recall, the Object class provides a virtual method called “GetHashCode( )” which you can override to provide your own hash code. When you insert a key,value pair in the Hashtable class object by calling the Add( ) method, it calls GetHashCode( ) on the key specified. Similarly, when you try to retrieve a value from the Hashtable, it calls the GetHashCode( ) to try to access the appropriate bucket. The key in a Hashtable can be a standard type or a user defined type. If you are using a user defined type as the key, then you must implement the GetHashCode( ) as well as the Equals method.
Hashtable class implements the IDictionary interfacewhich provides a property called Item. The Item property retrieves a value with the specified key. The Item property is implemented as an indexer, so you will access the Hashtable like an array when looking up information..
Example:
You will need to expose the System.Collections namespace to be able to use Hashtable class.
using System.Collections;
Suppose the following code was typed inside the Main method of a console application,
//----------Hashtable example----------
Hashtable
htbl = new Hashtable();
htbl.Add(100,"Baker");
// ID, Lname
htbl.Add(101,"Harris");
htbl.Add(102,"Mahmood");
Console.WriteLine("Last
Name for Student 101: {0}",
htbl[101].ToString());
Sometimes you need totraverse the entire Hashtable. This can be done by obtaining the IDictionaryEnumerator. Like other Enumerators, it provides the MoveNext( ) method which gives you an access to the next bucket in the Hashtable.
IDictionaryEnumerator enmr
= htbl.GetEnumerator();
while(enmr.MoveNext())
{
Console.WriteLine("key:{0},
value:{1}",
enmr.Key.ToString(),
enmr.Value.ToString());
}
When you run the program, the output will look like: