C#
Many of Microsoft’s new technologies were extensions and improvements of previous ones. For example, COM was extended from OLE and COM+ was extended from COM. Obviously, this was to maintain the backward compatibility. Though these extensions could solve the problems at hand, they ultimately led to more complexities.
Microsoft's new .NET technology is a welcome deviation from this path. It has been thought out and designed from scratch. It does not mean that .NET is not backward compatible. We can import the existing components to work in .NET. We can also create components in .NET that can be used by the existing clients. .NET is a new framework for programming Windows. It provides an application execution environment that manages memory, addresses versioning issues and improves the reliability, scalability, and security of any application.
Microsoft has upgraded VB and languages like C++, COBOL, etc. to target the .NET environment. In addition to these languages, it has provided a fresh .NET compliant language—C# (read as C Sharp). It is natural to think that migration from Microsoft's popular Visual Basic language to VB.NET or transition from VC++ to VC++.NET would be smooth. But one quick look at C# would convince you that in many cases coding in C# is cleaner more clean, pleasant and efficient.
There are two main components of .NET framework — .NET Framework Base Class Library (FCL) and Common Language Runtime (CLR). Let us understand each of them.
FCL and CLR
The base class library is an extensive common class library that can be used by any .NET compliant language. These classes encapsulate a high degree of functionality that programmers can use in their programs. The library includes classes for creating and displaying Windows forms, Web forms, serialisation, accessing the network and the Internet, manipulating XML documents, accessing databases, etc.
Common Language Runtime is the heart of the .NET framework. It actually manages the code during execution. The code that runs under CLR is called ‘managed code’. The code that is executed under .NET runtime gets benefits like cross-language inheritance, cross-language exception handling, enhanced security, versioning and deployment support, a simplified model for component interaction, and debugging and profiling services.
The adjoining table illustrates the components of the .NET framework.
Windows Forms, Web Forms, WebServices, etc.(Developed in .NET compliant languages)
.NET Framework Base Classes(ADO.NET, XML, Threading, I/O, Network)
Common Language Runtime (Memory Management, Common Type System, Lifecycle Monitoring)
The applications created in .NET compliant languages use the classes provided by the Base Class Library. These applications run under .NET runtime that manages the lifetime of objects created by applications, throws up exceptions, etc.
Let us now take a look at other important features of .NET:
Common Type System
Language interoperability under .NET is possible only when all the languages share a common data type system. For this, the common type system (CTS) is introduced. CTS ensures that an int in C# is same as an int in VC++. Under CTS, all the classes are derived from the System.Object class and all the primitive data types are mapped to the structures defined in base class library. CTS also specifies the visibility level (from where the type should be accessible) of data types.
- Intermediate Language
A .NET programming language does not compile into executable code; instead it compiles into an intermediate code called Microsoft Intermediate Language (MSIL). IL is a CPU-independent language. The IL code is sent to the CLR that converts the code to machine language using the Just In Time compiler, which is then run on the host machine. An important aspect of the IL language is that it provides the hardware abstraction layer. We can view the IL code of our application using the ILDASM tool shipped with Visual Studio.NET. - JIT Compilation
JIT (Just In Time) compiler is a crucial component of the .NET framework. The JIT compiler converts IL code into machine code, which is then executed. The JIT compiler does not compile the entire code at once because it could hamper the performance of the program. It compiles the code at runtime, at the time it is called. The code that is compiled gets stored until the execution comes to an end. This avoids recompilation of code. The reason why conversion from IL code to machine code takes place at runtime is that the JIT first gets information on the processor type and appropriately converts the IL code so that it would run on that type of processor. - Assemblies
An assembly is a unit containing IL code of a program. It is similar to a DLL file, but one difference is that unlike DLL, an assembly is self-describing. Assemblies contain assembly metadata (or manifest) that gives details of the assembly, type metadata describing the types, methods, etc, defined in the assembly and resources. - Garbage Collection
Garbage Collection is a program that is invoked by the CLR to free the memory that is not being used by the application. Because of this technique the programmers no more need to take care of memory leakages, dangling pointers and clean up of memory.
That’s it for a brief introduction to the .NET framework. Let us now create a simple program in C# that uses the .NET framework. It is assumed that you have installed Visual Studio.NET on a machine running under Windows NT/2000/XP.
The First C# Program
The steps to create the program are given below.
(a) Start Microsoft Visual Studio.NET from ‘Start Program Microsoft Visual Studio.NET’ menu option.
(b) Create a new C# project by selecting ‘File New Project’ menu option. A ‘New Project’ dialog would appear. From the ‘New Project’ dialog box select project type as ‘Visual C# Projects’.
(c) Select ‘Console Application’ from the list of Templates.
(d) Select a location where this project should get saved. Name the project as Simple. Click the OK button.
(e) A ‘Class1.cs’ file (cs stands for CSharp) would get created with the skeleton code given below.using System;
namespace Simple
{
class Class1
{
static void Main ( string[ ] args )
{
}
}
}
Now, add the following statement that would display the string “Hello world” on screen.Console.WriteLine ( "Hello C#" );
Compile and execute the program by pressing Ctrl+F5.
The program execution starts from the Main() function. Since C# is a pure object-oriented language it does not allow us to create global variables and functions. Instead, all variables and functions including Main() should be defined inside a class.
To send output to the screen we have used the WriteLine() function. We can display any data type through this function. Since WriteLine() belongs to the Console class we have to use the form Console.WriteLine() to call it. To be able to use the Console class in any program we need to import it from a namespace, which has been done in our program through the statement using System. Like the Console class our class is also enclosed in the Simple namespace.
Data Type in C#
Data types are the most basic elements of any computer language. C# offers the usual data types (built-in as well as user-defined) that one expects in a modern language. In this article, we will discuss user-defined data types like classes and structures, and a few other important topics related to data types in C#.
Classes
A class is a user-defined data type that enables the combination of data and functions that operate upon data in one entity. Generally, data is hidden from the user and functions provide an interface to use that data. The following program shows how to declare and use a class.
using System;
class sample
{
private int i, f ;
public void setdata ( int ii, int ff )
{
i = ii ; f = ff ;
}
public void displaydata( )
{
Console.WriteLine ( "{0} {1}", i, f ) ;
}
}
Here, we have declared a class sample. i and f are called data members of class sample. setdata( ) and displaydata( ) are called member functions or methods of class. C++ programmers must note that a class declaration does not end with a semicolon (;). The private written while declaring data members (or member functions) implies that they can be accessed only in the methods of class, not from outside the class. On the other hand, the public keyword specifies that the methods can be accessed both from inside as well as from outside the class. public and private are called access specifiers.
We can create an object of sample class and call the methods as shown below. sample s = new sample( ) ;
s.setdata ( 12, 12.5 ) ;
s.displaydata( ) ;
The statement sample s = new sample( ) creates an object of class sample and stores its address in s. So to say, s is a reference to the object. Methods of a class can be accessed only using its reference. So, we have used s to call displaydata( ) and setdata( ) methods.
Value Types & Reference Types
In C# data types are classified into two categories—value types and reference types. The difference between value type and reference type is that the variables of value type are allocated on stack, whereas, variables of reference type are allocated on heap. Secondly, the variable of value type contains data, whereas, variable of reference type contains the address of the memory location where data of that variable is stored. Among built-in data types, all the types of integers, floats, doubles, decimals, chars and bools are value types, whereas, the string and object are reference types. Among user-defined data types, classes, interfaces and delegates are reference types, whereas, structure is a value type.
Memory allocated for objects of value types is freed when they go out of scope. Memory allocated for objects of reference types is freed when they are no more being referenced.
Both the value and reference types have advantages and disadvantages. Memory allocation on stack is faster than that on heap. So, if the object is small, we must use a value type rather than a reference type. On the other hand, if the object is big we must avoid declaring it as a value type. Because, if we assign it to any other object, its whole contents would get copied, consuming additional memory. As against this, in case of a reference type only the reference is copied rather than the whole object.
Boxing and Unboxing
Boxing is the process of converting the value type (int, float, struct, etc) into reference type (objects). On the other hand unboxing is converting a reference type into a value type. For example, int i = 10 ;
object o = i ; // boxing
i = ( int ) o ; // unboxing
Boxing allocates memory for the value being boxed on heap, copies the value into this memory and stores its reference in an object type. Consider the following code. int i = 12 ;
Console.WriteLine ( “The value is {0} ”, i ) ;
The WriteLine( ) method is written to accept objects. When we pass a variable of value type, int in this case, it gets boxed into an object. Using this object a method ToString( ) is called, which returns the string equivalent of the value stored in a variable. WriteLine( ) then displays this string.
Primitive Data Types
Having taken a look at user-defined types, let us now switch over to primitive data types. Primitive data types in C# are slightly different than they are in C/C++. The primitive data types in C# are mapped to underlying structures defined by the .NET base class library. This is done to ensure that code written in C# can be used in other .NET compliant languages. The following table lists the primitive data types and structures they are mapped to.
The data types object and string are mapped to the System.Object and System.String class respectively.
Type Conversion
C# is a strictly typed language. This avoids accidental loss of value stored in a variable. Still, sometimes we may want to type cast a variable into another. For this, C# supports implicit and explicit type conversions. Implicit type conversion is allowed so long as there is no loss of data. If we store a short in an int or a long there is no loss of data because a short value can easily be accommodated in an int or a long. The reverse is not allowed because an int can contain a value greater than what a short can accommodate and so, there is a possibility of loss of data.
In short, C# allows widening conversion but does not allow narrowing conversion implicitly. If we want to convert a data type into another for which implicit conversion is not possible, we can do so by type casting. For example, float f = 20.45f ;
int i ;
i = ( int ) f ;
Here, we would lose the value after the decimal point. So, the value of i would now be 20.
Namespaces
Namespaces are used to hold a collection of related classes. They help in avoiding naming conflicts. For example, two programmers may design a class called books. If a user uses the class library created by these programmers, then there may be a conflict between these two classes. Namespace avoids this conflict. The following code snippet shows how. namespace Library
{
// definition of class books
}
namespace Shop
{
// definition of class books
}
To instantiate the object of books class we have to write
Library.books lb = new Library.books( ) ;
Shop.books sb = new Shop.books( ) ;
Thus, mentioning the name of namespace clearly indicates which books class the user wants to refer to. Instead of mentioning such a fully qualified name, we can use a using directive, which makes available all the classes defined in a namespace into the current scope. The statement
using Library;
will make available all the classes from the Library namespace. This is known as importing classes. This is why the statement using System is written at the beginning of a program. It imports all the classes written in System namespace. Now we have to simply say,
books lb = new books( );
Data type Mapped to
sbyte System.SByte
ulong System.UInt64
byte System.Byte
char System.Char
short System.Int16
float System.Single
ushort System.UInt16
double System.Double
int System.Int32
bool System.Boolean
uint System.UInt32
decimal System.Decimal
long System.Int64
Inheritance and Polymorphism
The C# Column - Yashawant Kanetkar
In this article, we will discuss two important concepts of object-oriented programming — inheritance and polymorphism. Inheritance permits you to use the existing features of a class as it is and add a few of your own. Instead of using all the features of an existing class on an as-is-where-is basis, we can also override a few of them and provide our own implementation of them.
For example, a class that can create a window may already exist, which we can extend to create a button, a list box or a combo box. While extending a class, the existing class remains unchanged. The new class is called ‘derived class’, whereas, the existing class is called ‘base class’. The main advantage of inheritance is code reusability. The code reusability is of great help in the case of distributing class libraries. A programmer can use a class created by another person or company, and, without modifying it, derive other classes from it. C# goes one step further and allows cross language inheritance too.
Polymorphism and inheritance go hand in hand. Polymorphism implies the existence of an entity in different forms. Polymorphism allows classes to provide different implementations of a method that can be called in the same way. Polymorphism can be of three types—inheritance polymorphism, interface polymorphism and polymorphism through abstract classes.
Let us first write a program that shows inheritance at work. Suppose a bank has a very generic class called account that allows debit and credit transactions on an account. A bank has to maintain various accounts like savings bank account, fixed deposit account, current account, etc. Every account has a few unique features. For example, a savings account can be operated only ten times in a month. Similarly, it needs to have a minimum balance against it. On the other hand, a current account allows unlimited transactions to be performed. So we can create more specific classes to maintain different kinds of accounts from the same account class. These classes can use the basic functionality of the account class and add their own account-specific functionality. Following code shows the base class account and derived classes savingaccount and currentaccount.
class account
{
protected string name ;
protected float balance ;
public account ( string n, float b )
{
name = n ;
balance = b ;
}
public void deposit ( float amt )
{
balance += amt ;
}
public void withdraw ( float amt )
{
balance -= amt ;
}
public void display( )
{
Console.WriteLine ( “Name: {0} Balance: {1}”, name, balance ) ;
}
}
class savingaccount : account
{
static int accno = 1000 ;
int trans ;
public savingaccount ( string s, float b ) : base ( s, b )
{
trans = 0 ;
accno++ ;
}
public void withdraw ( float amt )
{
if ( trans >= 10 )
{
Console.WriteLine (“Number of transactions exceed 10” ) ;
return ;
}
if ( balance - amt <>= 10 )
{
Console.WriteLine ( “Number of transactions exceed 10” ) ;
return;
}
base.deposit ( amt ) ;
trans++ ;
}
public void display( )
{
Console.WriteLine ( “Name: {0} Account no.: {1} Balance: {2} ”,
name, accno, balance ) ;
}
}
class currentaccount : account
{
static int accno = 1000 ;
public currentaccount ( string s, float b ) : base ( s, b )
{
accno++ ;
}
public void withdraw ( float amt )
{
if ( balance - amt < s =" new" a1 =" new" a2 =" new">Abstract classes and methods
If you analyse the above program carefully, you will notice that the account class is so general that we would seldom require calling its methods directly. Rather, we would always override them in derived class and call the derived class's methods. So, instead of defining the functionality in account class we can design the account class in such a way that it would only specify what functionality the derived classes should have. So, an abstract class always serves as the base class for other classes. We can achieve this by declaring the class as abstract.
abstract class account
{
abstract public void deposit( ) ;
abstract public void withdraw( ) ;
abstract public void display( ) ;
}
An abstract method does not have a definition in base class. As such, it is similar to a pure virtual function of C++. We cannot instantiate objects of abstract classes. A class that inherits an abstract class has to define all the abstract methods declared in the class.
Interfaces
Polymorphism is also achieved through interfaces. Like abstract classes, interfaces also describe the methods that a class needs to implement. The difference between abstract classes and interfaces is that abstract classes always act as a base class of the related classes in the class hierarchy. For example, consider a hierarchy-car and truck classes derived from four-wheeler class; the classes two-wheeler and four-wheeler derived from an abstract class vehicle. So, the class 'vehicle' is the base class in the class hierarchy. On the other hand dissimilar classes can implement one interface. For example, there is an interface that compares two objects. This interface can be implemented by the classes like box, person and string, which are unrelated to each other.
C# allows multiple interface inheritance. It means that a class can implement more than one interface.
The methods declared in an interface are implicitly abstract. If a class implements an interface, it becomes mandatory for the class to override all the methods declared in the interface, otherwise the derived class would become abstract.
One last thing. Be careful while designing an interface. Because, once an interface is published, we cannot change it. We cannot even add a method because all the classes that implement this interface will have to implement this method.
Setting up arrays in C#
The C# Column - Yashawant Kanetkar
An array is a collection of similar data types stored in adjacent memory locations. Though the syntax of declaring and using a C# array is more or less similar to that of C/C++, they are actually created as objects of the System.Array class. Hence arrays in C# fall in the category of reference types. Like any other reference type, an array object refers to a memory space allocated on the heap. Following code fragment shows how to declare and initialise an array.
int[ ] a, b ;
a = new int [ 10 ] ;
b = new int [ ] { 0, 1, 2, 3, 4 } ;
foreach ( int i in b )
Console.WriteLine ( i ) ;
The first statement merely sets aside space for references to arrays. The second and third statement allocates memory for ten and five integers respectively. The references of arrays created in the heap are stored in a and b. Note that though the array gets allocated on the heap, the references a and b are created on the stack.
The foreach loop iterates through the array and extracts one element at a time from the array. We can also use the subscript notation to access the array elements. Like C/C++, C# too uses zero-based index to access array elements. All the array elements are set to a default value 0 unless we initialise them. However, if we create an array of reference types, the array elements are set to null. Here is an example that creates an array of objects of a class sample.
sample[ ] s = new sample [ 5 ] ;
Here s is a reference to an array. Each element of the array in itself is a reference to the sample object.
Array Bounds Checking
In C# checking the array bounds is not the programmer's responsibility. If the array bounds are exceeded, the .NET runtime informs the application about it by throwing an exception. An exception is a runtime error caused by fatal errors like array bounds out of range, using an invalid reference, etc. Thus, following code would never work in C#.
int[ ] a = { 1, 2, 3, 4 } ;
a [ 4 ] = 10 ;
Although compiler would not report any error .NET runtime would throw an exception.
Passing an array to a method
As said earlier, an array is implemented as an object. Hence, when we pass an array to a method, only its reference gets passed to the method. Here is the program that shows how to pass an array to a method.
using System ;
namespace arraytofunc
{
class Class1
{
static void Main ( string[ ] args )
{
int[ ] a = { 1, 2, 3, 4, 5 } ;
print ( a ) ;
Console.WriteLine ( “Elements of a” ) ;
for ( int i = 0 ; i < i =" 0">
In Main( ), we have created a 1-D array referred to by a and passed a to the change( ) method. In change( ) method we have collected it in another reference b. Now, both a and b are referring to the same memory. Next, we have incremented the values of array by 5. Since b refers to the same array which a is referring to, changes made to array elements in change( ) get reflected in Main( ) as well. If we display the elements of array using the reference a it displays the modified elements, 6 7 8 9 10
Apart from single dimension array, C# provides multidimensional arrays, also called rectangular arrays and jagged arrays. This is how we can declare a 2D array. int [ , ] arr1 = new int [ 4, 4 ] ;
arr1 = new int [ 4, 4 ] ;
int [ , ] arr2 = { { 3, 5, 7, 9 }, { 11, 13, 15, 17 } } ;
We can access elements of arr1 as arr1[0, 0].
A jagged array is an array of arrays. When we declare a jagged array, we specify number of rows. Each row holds an array, which can be of different length. Following statements show how to declare and initialise a jagged array.
int [ ] [ ] arr1 = new int [ 2 ] [ ] ; // array of 2 arrays
arr1 [ 0 ] = new int [ 3 ] ; // first array has 3 elements
arr1 [ 1 ] = new int [ 2 ] ; // second array has 2 elements
We can access elements of arr1 as arr1[0][0], arr1[0][1], etc.
The System.Array class provides number of methods for array manipulation. A few of them are used below.
int[ ] arr1 = new int [ 5 ] { 1, 4, 7, 8, 9 } ;
int[ ] arr2 = new int [ 10 ] ;
arr2.SetValue ( 5, 0 ) ;
Console.WriteLine ( "Number of elements in arr1:" + arr1.Length ) ;
Array.Copy ( arr1, 2, arr2, 6, 2 ) ;
Array.Reverse ( arr2 ) ;
Array.Sort ( arr2 ) ;
The SetValue( ) method sets the value 5 at 0th location in arr2. Length is a property of Array class that returns number of elements in the array. The Copy( ), Reverse( ) and Sort( ) methods copy, reverse and sort the array elements.
Strings
C# has provided a built-in data type string to hold the sequence of Unicode characters. The string data type is mapped to the System.String class. So, a string is represented as an object rather than an array of characters. When we create a string through the statement,
string s = “Hello”;
a reference s gets created on the stack. An object of type String gets created on heap with memory required to hold the string “Hello”. The object gets initialised with the string “Hello”. The reference s then starts referring to this object.
Strings in C# are more sophisticated, powerful and easy to use. The following statements would prove this.string s1 = “True Rural Ruler”;
s1 += “Is A Truly Rural Ruler”;
for ( int i = 0 ; i <>
Strings are immutable. What this means is that once initialised, strings cannot be changed. So, when we try to change the string, changes do not take place in the same object but a new object of type String gets created with the new, changed string. The string reference then starts referring to this new object. For example, in the above code, when we concatenate strings, a new object that is capable of holding the concatenated string gets created.
The reference s1 then starts referring to this object. The memory allocated for the first object becomes unreferenced, which then gets collected by the Garbage Collector.
Suppose we create two strings having exactly same literal as shown below.
string s1 = “hi there!” ;
string s2 = “hi there!” ;
Here, two different objects would not get created. Only one object with string “hi there!” would get created and both the references s1 and s2 would refer to that object. As soon as we assign a different string constant to any of the references it will start referring to different object.
The StringBuilder Class
Creating a new object every time we change the string sounds reasonable if our program deals with strings having moderate length. But if the program extensively performs text processing, then this may affect the performance of the program as there may be a number of unreferenced objects on the heap waiting to be collected by the Garbage Collector. Hence .NET has provided another class named StringBuilder to cope with this problem. Although StringBuilder does not provide as much functionality as the String class, it certainly provides methods for the most common operations. To be able to use StringBuilder class we must import classes of System.Text namespace.
Properties and Indexers
The C# Column - Yashawant Kanetkar
All the object-oriented languages support encapsulation, inheritance and polymorphism. What is different about C# is that it supports additional features like properties, events, indexers, delegates, etc. That is why C# is often termed as a modern object-oriented (OO) language. In this article we will discuss two modern OO concepts—properties and indexers.
Properties
Properties provide an interface for the data members of a class. Using properties, values can be stored and retrieved from the data members. So, what is the big deal about that? Even methods do the same thing. What's good here is that we can access a property as if we are accessing a data member. Manipulating data using properties is done through get and set accessors. In the following example we have defined a property Size for a data member size.
class sample
{
private int size ;
public int Size
{
get
{
return size ;
}
set
{
if ( value > 10 )
Console.WriteLine ( “Invalid Size” ) ;
else
size = value ;
}
}
}
The get accessor is invoked when we retrieve the value of the property, whereas the set accessor is invoked when we assign a value to the property. For example,
sample s = new sample( ) ;
s.Size = 10 ;
int i = s.Size ;
Here, the statement s.Size = 10 would invoke the set accessor. The value 10 would get collected in value. value is a keyword and is an implicit parameter of the set accessor. In the set accessor we have assigned the contents of value to the size data member. Before that we have validated the value assigned by the user to the property. Thus a property gives the syntactical convenience of a data member as well as the functionality of a method. We can write any program logic and throw exceptions in a property. It can be overridden and can be declared with any modifiers. The statement int i = s.Size would invoke the get accessor which returns the value of the size data member. The returned value would get collected in i.
Generally a property is associated with a data member. But this is not necessary. The get accessor can return a value without accessing a data member. For example, we can write a get property that returns system time without using a data member. The data type of a property can be a preliminary data type or a user-defined data type.
Indexers
An indexer is a new syntax that allows us to use an object as if the object itself is an array. This syntax is used if the class contains an array as a data member. An indexer allows access to the array elements within the class using the [ ] operator with an object. In one sense, we overload the [ ] operator. Unlike arrays, indexers allow usage of non-integer subscripts as well. Below, we see a program in which an indexer takes a string as a subscript.
using System ;
namespace stringindexer
{
class sample
{
string[ ] english = new string[ ] {“Sunday”, “Monday”, “Tuesday”,
“Wednesday”, “Thursday”, “Friday”, “Saturday”} ;
string[ ] hindi = new string[ ] {“Itwar”, “Somwar”, “Mangalwar”,
“Budhwar”, “Guruwar”, “Shukrawar”, “Shaniwar”} ;
public string this [string c]
{
get
{
for ( int i = 0 ; i < i =" 0" s =" new">
In this example, the user would provide a string representing a weekday in English. The get accessor of the indexer would return the corresponding name in Hindi, whereas, set accessor would store the specified name in Hindi for the corresponding weekday in English. For example, s[“Monday”] should return “Somwar”. Similarly, s[“Monday”] = “Som” should set “Som” as Monday's equivalent Hindi name. To implement this, we have declared two arrays of strings—english and hindi as data members of class sample. In Main( ), we have instantiated an object of sample and called the get accessor through the statement Console.WriteLine ( s [ “Monday” ] ) ;
“Monday” would get collected in c in the get accessor. If the specified weekday is available in the english array we have returned its Hindi equivalent from the hindi array. We have done a case insensitive comparison by mentioning true as a third parameter of the Compare( ) method.
Next, we have called the set accessor through the statement s [ “Monday” ] = “Som” ;
The string “Monday” would get collected in c. We have stored the name “Som”, which is available in the implicit parameter value, in the hindi array.
Indexers are nameless. It means that unlike properties we cannot assign name to an indexer. We use the this reference for writing indexers. Indexers may be overloaded. We can write one indexer for a 1-D array and another for a 2-D array in the same class. Needless to say, a 2-D indexer is accessed using [ , ] syntax.
Although indexers allow us to use an object as an array, we cannot use it in a foreach loop like an array. This is because foreach works only with collections. Writing an indexer does not qualify a class to be a collection.
It is not possible to write indexers for jagged arrays because a statement such as public int this [ int index1] [ int index2 ] results in an error.
Lastly, indexers cannot be declared as static because of the simple reason that the this reference is not available in static methods.
The C# Column - Yashawant Kanetkar
Creating efficient data structures has always been a nightmare for programmers. But while writing a C# program, how to build a data structure is the last thing on one’s mind. The .NET Foundation Class Library contains a namespace called System.Collections that provides classes to manage data structures like arrays, lists and maps. It also provides interfaces to enumerate and compare the elements of collections.
Let us first discuss in brief the classes available for maintaining the above-mentioned data structures.The ArrayList class is similar to the Array class except that its size can grow dynamically. Like the Array class, the ArrayList class contains various methods to perform functions like adding, inserting, removing, copying, etc. The Queue and Stack classes provide the ‘First In First Out’ and ‘Last In First Out’ types of collections respectively.
The base class library provides a class called BitArray that allows the creation of an unusual array—an array of bits. If we provide an int array, the bits array gets created with the bit values of every element of the array. The Hashtable and SortedList classes manage a collection of key-value pairs. The difference between the Hashtable and SortedList class is that in the SortedList class, the values are sorted by keys and are accessible by key as well as by index.
Using these classes is straightforward and is dealt with in enough detail in several popular C# books. We simply have to instantiate an object of the relevant collection class and call its methods. The difficult program logic comes into the picture when we have to provide collection support to our class. Let us see what collection support means.
A collection is a set of the same type of objects that are grouped together and that is able to supply a reference to an enumerator. An enumerator is an object that iterates through its associated collection. It can be thought of as a movable pointer pointing to any element in the collection. In order to provide an enumerator, a class must implement the IEnumerable interface. If a class implements IEnumerable interface, we can use an object of the class in foreach statement. We will now see an example that shows how to provide enumeration support to our class.
We will write a class sample having an int array named arr as a data member. The sample class would implement the IEnumerable interface so that we can enumerate through the array arr using the foreach statement. Here is the code.
using System;
using System.Collections;
class sample : IEnumerable
{
int[ ] arr = { 10, 11, 12, 13, 14 };
class myenumerator : IEnumerator
{
int c;
sample s;
public myenumerator ( sample ss )
{
c = -1;
s = ss;
}
public object Current
{
get
{
return s.arr [ c ];
}
}
public bool MoveNext( )
{
if ( c < c =" -1;">
The IEnumerable interface declares only one method GetEnumerator( ). The GetEnumerator( ) method returns reference to the enumerator object. We have to write our own enumerator so that it would enumerate our collection. To create an enumerator we have to write a class that implements the IEnumerator interface. Here our enumerator class is myenumerator.
Let us now understand how an enumerator works. When an enumerator object is initialised, it does not point to any element of the collection. We must call the MoveNext( ) method that moves the enumerator to the first element of the collection. We can retrieve this element by calling the Current property. We can do whatever we want with this element. Then we must move to the next element by calling the MoveNext( ) method again. If we want to enumerate the collection again we can call the Reset( ) method that returns before the beginning of collection.
Our implementation of MoveNext( ), Reset( ) and Current does the same.
Now, we can use the foreach statement on an object of the class sample as shown below.
sample s = new sample( ) ;
foreach ( int i in s )
Console.WriteLine ( i ) ;
The foreach statement works like this.
IEnumerator e = s.GetEnumerator( ) ;
while ( e.MoveNext( ) )
Console.WriteLine ( e.Current ) ;
The foreach statement would terminate when the MoveNext( ) method would return false.
Note that it is necessary to design a separate class implementing the IEnumerator interface rather than implementing it in the collection class itself. If we implement the IEnumerator interface in the collection class, we would face a problem when enumerating the same object again.
Understanding Delegates
The C# Column - Yashawant Kanetkar
A delegate is an important element of C# and is extensively used in every type of .NET application. A delegate is a class whose object (delegate object) can store a set of references to methods. This delegate object is used to invoke the methods. Many developers find delegates complicated. But believe me, it’s a simple concept to understand. In this article we will see how delegates work and in what situations they are used.Let’s start with a simple program that shows how to declare and use a delegate.
class sample
{
delegate void del1();
delegate void del2 (int i);
public void fun()
{
del1 d1 = new del1 (f1);
d1();
del2 d2 = new del2 (f2) ;
d2 ( 5 ) ;
}
public void f1()
{
Console.WriteLine (“Reached in f1”) ;
}
public void f2 (int i)
{
Console.WriteLine (“Reached in f2”) ;
}
}
This would create a delegate object d1. To the constructor of the delegate class we have passed the address of the method f1( ). C++ programmers may know that mentioning a method name without parentheses represents its address. The delegate object would now hold an address of the method f1( ). Next, we have called the f1( ) method using the delegate object d1. This is achieved using the statement d1( ) ;
If d1 is an object, how can we use it as if it is a method? Actually, the call d1( ) gets converted into a call to the d1.Invoke( ) method. The Invoke( ) method calls the f1( ) method using its address stored in delegate object. A delegate can contain references to multiple methods. In this case a single call to Invoke( ) calls all the methods.
Similar to d1 we have instantiated another delegate object d2 and stored in it an address of the method f2( ). As the method f2( ) takes an integer as a parameter we pave passed 5 to d2( ). The value 5 would get passed to all the methods whose references are stored in this delegate object. This makes it obvious that the signature of the delegate and that of the methods to be invoked using the delegate must be identical.
If we create an object of sample class and call the fun( ) method both the f1( ) and f2( ) methods would get called through d1 and d2 respectively.
Delegates make possible calling of methods using reference to methods the object oriented way. This avoids using any complex technique like pointers to functions.
Delegates in Inheritance
Suppose there is a base class and a class derived from this class. A method is invoked from a base class method using a delegate. We can initialise this delegate in derived class so that when the method is invoked from the base class it is the derived class’s method that would get called. Here is the program that works similar to this situation.
using System;
namespace delegateinherit
{
public delegate void del1();
class mybase
{
public del1 d;
public void fun()
{
d();
}
}
class der : mybase
{
public der()
{
d = new del1 (f1);
fun();
d = new del1 (f2);
fun();
}
public void f1()
{
Console.WriteLine (“Reached in f1”) ;
}
public void f2()
{
Console.WriteLine (“Reached in f2”) ;
}
}
class Class1
{
static void Main (string[] args)
{
der d = new der();
}
}
}
Here, from the fun( ) method of the mybase class we have used the delegate object d to invoke a method. The fun( ) method is called twice from the constructor of the derived class der. Before calling fun( ) method, we have associated the delegate d, first with method f1( ) and then with f2( ). Thus, if we create an object of the der class, first the f1( ) and then f2( ) method get called. We can invoke both the methods in a single call to fun( ) as shown below.
d = new del1 ( f1 ) ;
d += new del1 ( f2 ) ;
fun( ) ;
Where Delegates are useful
Delegate at work
As said earlier, all the event handlers are called using delegates. We will now see a dummy program to explain how the Paint event handler must be getting called using a delegate.
using System;
namespace delegateatwork
{
public delegate void PaintHandler();
class Form
{
public PaintHandler Paint ;
public void OnPaint()
{
Paint();
}
}
class form1 : Form
{
public form1()
{
Paint += new PaintHandler (form1_Paint);
OnPaint() ;
}
public void form1_Paint()
{
Console.WriteLine (“form1_Paint”);
}
}
class Class1
{
static void Main (string[] args)
{
form1 f = new form1();
}
}
}
The C# Column - Yashawant Kanetkar
Creating WinForms has many advantages over creating applications using MFC. In unmanaged Windows-based programs, we need to apply certain styles to the window only when it is created. WinForms eliminate this quirk. If we apply styles that are meant to be applied at creation time, .NET destroys the previous window and creates a new one with new styles.
The .NET Framework Class Library is richer than MFC. Also, the classes remain the same for all the .NET compliant languages. All the classes that are used to create and design a WinForm are encapsulated in System.Windows.Forms namespace. For example, the System.Windows.Forms.Form class is used to create the form, System.Windows.Forms.Button class is used to create a button and so on. To create a GUI, we place components and controls on the form and so, a form acts as a container of components and controls. To create the GUI we drag and drop the controls from Toolbox and change their properties to suit our requirements. When we drag the control on form and change properties, Visual Studio.NET generates suitable code for us. However, only adding controls to create a user interface is not enough. Our application must also respond when the user interacts with the application. A user can interact with the application by moving the mouse, clicking the mouse button, pressing a key, etc. Whenever a user interacts with the GUI, events are generated. Events are notifications sent to the container, which can then respond to it. For example, when we click a button, an event is generated notifying that the user has clicked the button. In response, we can display a message box or do something else. This job is done in methods called ‘event handlers’. When a particular event is generated, the corresponding event handler gets called to process the event. For calling the event handlers, .NET takes help of events and delegates.
An event in C# is a multicast delegate having a predefined prototype. The prototype is such that every event returns a void and always accepts two parameters. The first parameter is always a reference to an object of System.Object class and the second parameter is always a reference to an object of the System.EventArgs class or a class derived from it. The EventArgs object contains information about the event. The object that raises an event is called an ‘event raiser’ and the object that receives an event is called an ‘event receiver’. The event raiser class always declares the event and the receiver class must have an event handler to handle that event.
The first parameter collected by the event handler is always a reference to an event raiser object. The addresses of event handlers are stored inside the events (remember that events are actually delegates) and hence they also must have the same signature as the event. This kind of delegate is declared using the keyword event. To understand events, consider the following program.
using System;
namespace Sample
{
public class mouseeventargs
{
public int x,y;
public mouseeventargs (int x1, int y1)
{
x = x1;
y = y1;
}
}
public delegate void click (object m, mouseeventargs e);
public class MyForm
{
public event click c1;
public void mouseclick()
{
mouseeventargs m = new mouseeventargs (10,20);
c1 (this, m);
}
}
class MyForm1:MyForm
{
public MyForm1()
{
c1 += new click (button_click);
mouseclick();
}
public void button_click (object m, mouseeventargs e)
{
Console.WriteLine (“Mouse Coordinates: “ + e.x + “ “ + e.y) ;
Console.WriteLine (“Type is: “ + m.GetType().ToString()) ;
}
static void Main ( string[ ] args )
{
MyForm1 f = new MyForm1( ) ;
}
}
}
c1 (this, m);
We could call a method even by using a delegate. Then what are events for? Take a look at the following statement.
c1 = new click(button_click);
The minimal code that is required to create a WinForm application is given below:using System;
using System.Windows.Forms;
namespace WinFormDemo
{
public class Form1 : Form
{
static void Main()
{
Application.Run ( new Form1( ) ) ;
}
}
}
private System.Windows.Forms.Button button1;
The reference button1 would get initialised in the InitializeComponent( ) method as given below.
this.button1 = new System.Windows.Forms.Button();
On adding a handler for the Click event, the code generated for it looks as given below.
this.button1.Click += new System.EventHandler(this.button1_Click);
private void button1_Click (object sender, System.EventArgs e)
{
}
The first parameter passed to button1_Click( ) method identifies the object that fired the event (button1 in this case). The second parameter contains additional information about the event.
The objects of every control class fire events. Now you can appreciate how easy it is to handle these events under .NET
GDI+ — An extension to GDI
The C# Column - Yashavant Kanetkar
Changes in programming model
GDI uses the concept of Device Context (DC). Device Context is a structure that stores all the drawing related information, namely, features of the display device and attributes that decide the appearance of the drawing. Every device context is associated with a window. To draw on a window, one must first obtain a device context of that window. To change any attribute, say, pen colour, it first has to be selected in the device context by calling the SelectObject( ) method. Once selected, all the drawing is done using this pen, until such time as another pen is selected in device context.
GDI+ works with ‘graphics context’ that plays a similar role as device context. The graphics context is also associated with a particular window and contains information specifying how a drawing would be displayed. However, unlike device context, it does not contain information about pen, brush, font, etc. In order to draw with a new pen we simply have to pass an object of Pen class to the DrawLine( ) method (this method draws a line on window). We can pass different Pen objects in each call to DrawLine( ) method to draw the lines in different colours. Thus GDI uses a stateful model, whereas GDI+ uses a stateless model. The Graphics class encapsulates the graphics context. Not surprisingly, most of the drawing is done by calling methods of the Graphics class.
Working with GDI+
So let’s learn how to draw text and graphics using GDI+ by writing a small program. Create a Windows Application. Windows programmers know that a window receives WM_PAINT message when it is to be painted. We need to handle this message if we want to do any painting in the window. In .NET we can do this either by overriding the virtual method OnPaint( ) of the Form class or by writing a handler for the Paint event. The base class implementation of OnPaint( ) invokes the Paint event handler through delegate. Hence we should write our code in the Paint event handler.
Add the Paint handler to the form. The Form1_Paint( ) handler would look like this.
private void Form1_Paint (object sender, PaintEventArgs e)
{
}
The first parameter passed to the Form1_Paint( ) handler contains the reference to the object of a control that sends the event. The second parameter contains more information about the Paint event. We would first see how to display a string on the form. To display the string we would use DrawString( ) method of the Graphics class.
private void Form1_Paint ( object sender, PaintEventArgs e )
{
Graphics g = e.Graphics;
Font myfont = new Font (“Times New Roman”, 60); StringFormat f = new StringFormat();
f.Alignment = StringAlignment.Center;
f.LineAlignment = StringAlignment.Center;
g.DrawString (“Hello!”,myfont,Brushes.Blue,ClientRectangle,f);
}
The Graphics class contains various methods to draw different shapes. This includes drawing rectangle, line, arc, bezier, curve, pie, etc. We would add the code in Form1_Paint( ) handler that draws rectangles in different pens and brushes. You would be able to draw other shapes on similar lines.
The following code draws a rectangle using green coloured pen having line thickness of 3.
Pen p=new Pen(Color.Green, 3);
g.DrawRectangle(p,20,20,150,100);
The Pen class encapsulates various styles of pens like solid, dash, dash-dot, etc. We can change the style of pen using the DashStyle property of the Pen class. This is shown in the following statement.
p.DashStyle = DashStyle.Dash;
If we want, we can specify custom pen style by using the DashPattern property. There are several other properties of the Pen class that allow us to specify the pen type (hatch fill, gradient fill, solid color, etc), cap style, join style, etc.
Unlike GDI, GDI+ provides separate methods for rectangle and filled rectangle. To fill the rectangle we need to pass a Brush object. This is shown below.
HatchBrush hb=new
HatchBrush(HatchStyle.BackwardDiagonal,Color.Red, Color.Black);
g.FillRectangle (hb,200,20,150,100);
LinearGradientBrush gb=new LinearGradientBrush(ClientRectangle,
Color.BlanchedAlmond,Color.Aquamarine,90);
g.FillRectangle(gb,ClientRectangle);
Here, we have created an object of the LinearGradientBrush class and passed to its constructor the rectangle to be filled, and two colours that form the gradient pattern. The last parameter specifies the angle from which we wish to draw. Specifying 90 would fill the window vertically.
Coordinates and Transformations
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g=e.Graphics;
g.PageUnit=GraphicsUnit.Inch;
Pen p=new Pen (Color.Green, 1/g.DpiX);
g.DrawLine (p,0,0,1,0);
}
Here, firstly we have set the PageUnit property to GraphicsUnit.Inch specifying that the unit of measure is an inch. We have created a Pen object and set its width to 1 / g.Dpix. The Dpix property of the Graphics class indicates a value, in dots per inch, for the horizontal resolution supported by this Graphics object. Note that this is necessary because now Pen object also assumes 1 unit = 1 inch. So, if we don’t set the pen width like this, a line with 1 inch pen width would get drawn. Next we drew a line having one unit measure, which happens to be an inch.
Let us now shift the origin to the centre of the client area and draw the line again.
private void Form1_Paint (object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.PageUnit = GraphicsUnit.Inch;
g.TranslateTransform ( ( ClientRectangle.Width/g.DpiX )/ 2,
(ClientRectangle.Height/g.DpiY)/2);
Pen p = new Pen ( Color.Green, 1/g.DpiX);
g.DrawLine (p,0,0,1,0);
}
Here, after setting the unit to an inch using the PageUnit property, we have called the TranslateTransform( ) method to shift the origin to the centre of the client area. This method maps the world coordinates to page coordinates and so the transformation is called world transformation. The x and y values we have passed to the TranslateTransform( ) method get added to every x and y values we pass to the Graphics methods. Finally, we created a pen having proper width and drew the line.
GDI+ also allows us to orient the x and y axes’ direction to the specified angle. For this, it provides the RotateTransform( ) method. For example, if we call the RotateTransform( ) method before drawing the line as shown below,
g.RotateTransform(30);
then line would get displayed slanting downwards, 30 degrees below the base line. We can use this functionality of the RotateTransform( ) method to create an application like an analogue clock.
Disposing graphics objects
penobject.Dispose();
Also release the Graphics object obtained by calling the CreateGraphics() method.
Reading metadata using reflection
The C# Column - Yashawant Kanetkar
Reflection allows us to either read data from assembly or write data to assemblies. The System.Reflection namespace contains classes to read an assembly, whereas, System.Reflection.Emit namespace contains classes to write to the assembly. In this article, we would firstly create an application that lists all the types of selected assembly. Then, on selecting a type, its members get displayed.Create a Windows Application and design a form as shown in the following figure.
The variables and events of different controls in the form are given in the following table.Control Variable Event
TextBox aname
Load Button load Click
Types ListBox list SelectedValueChanged
Methods and listmethods
Constructors
ListBox
Properties listprop
ListBox
Events ListBox listeve
Also add the OpenFileDialog control to the form. On clicking the ‘Load’ button the standard ‘Open’ dialog box gets displayed. If the user selects an assembly and clicks the OK button, its path gets displayed in the text box, the assembly gets loaded in memory and its types get displayed in the list box. To do this, we have added the Click event handler as shown below.
private void load_Click ( object sender, System.EventArgs e )
{
openFileDialog1.InitialDirectory =
@”C:\WINDOWS\Microsoft.NET\ Framework\v1.0.3705” ;
openFileDialog1.Filter = “Assemblies (*.dll)*.dll” ;
if ( openFileDialog1.ShowDialog( ) == DialogResult.OK )
{
list.Items.Clear( ) ;
aname.Text = openFileDialog1.FileName ;
Assembly a ;
try
{
a = Assembly.LoadFrom
(aname.Text);
tarray = a.GetExportedTypes( ) ;
}
catch ( Exception ex )
{
MessageBox.Show ( “Error in loading assembly” ) ;
return;
}
foreach ( Type type in tarray )
list.Items.Add ( type.FullName ) ;
list.SetSelected ( 0, true ) ;
}
}
Here, firstly we have set the default folder where all .NET framework assemblies are stored. We have also set the Filter property to ‘.dll’ so that only assemblies would be listed in the dialog. To load the selected assembly, we have read the assembly path from the text box and called the LoadFrom( ) static method of the Assembly class. This method returns a reference to the Assembly object. Using this reference, we have called the GetExportedTypes( ) method that returns an array of types available in the assembly. tarray is an array of Type objects. Add this array as a private data member. The types returned by the GetExportedTypes( ) method are added to the list box in the foreach loop using the Add( ) method.
Now let us write the code to display type members in respective list boxes, if any type is selected. The SelectedValueChanged event handler is given below.
private void selchange (object sender,System.EventArgs e)
{
int index = list.SelectedIndex;
Type t = tarray[index];
listmethods.Items.Clear();
listprop.Items.Clear();
listeve.Items.Clear();
ConstructorInfo [] ci = t.GetConstructors();
foreach (ConstructorInfo c in ci)
listmethods.Items.Add (c);
PropertyInfo [] pi = t.GetProperties();
foreach (PropertyInfo p in pi)
listprop.Items.Add(p);
EventInfo [] ei = t.GetEvents();
foreach (EventInfo ev in ei)
listeve.Items.Add(ev);
MethodInfo [] mi = t.GetMethods(BindingFlags.Instance
BindingFlags.DeclaredOnly BindingFlags.Public BindingFlags.NonPublic);
String str = “”;
foreach (MethodInfo m in mi)
{
if (t == m.DeclaringType)
{
str = m.Name;
str += “ ( “ ;
ParameterInfo [] pif = m.GetParameters() ;
foreach (ParameterInfo p in pif)
{
str += p.ParameterType;
str += “ “;
str += p.Name;
str += “, “;
}
int c = str.LastIndexOf (‘,’);
if (c != -1)
str = str.Remove (c, 1);
str += “ )”;
listmethods.Items.Add (str);
}
}
}
To display members we must first obtain the item selected from the list box. We have done so by using the SelectedIndex property of the ListBox class. We have retrieved the selected type from the tarray array.
The Type class contains various methods to access members of a type. We have used the same to read constructors, methods, properties and events of the selected type. These methods return a reference to an array containing respective members. We have listed the members in the respective list boxes. To the GetMethods( ) method we have passed certain flags. These flags indicate that we intend to read the instance methods, methods that are not inherited from base type and methods that are public as well as non-public. We can mention this flag in all the other methods as well. Since method name and its parameters need to be read separately, we have used string concatenation to form a string that looks like a prototype declaration of method. Again, we have listed only those methods that directly belong to this type. We have used the DeclaringType property of the MethodInfo class.
We would now see a small example that loads a user-created assembly, obtains a class type, creates its instance and invokes a method defined in the assembly. Here is the code...
Assembly a = Assembly.LoadFrom(“c:\\mymath.dll”);
Type t = a.GetType (“mymath”);
MethodInfo m = t.GetMethod(“add”);
Object obj = Activator.CreateInstance (t);
Object[ ] arg = new Object [2];
arg[0] = 10;
arg[1] = 20;
m.Invoke(obj, arg);
mymath is the name of class written in ‘mymath.dll’. To the GetMethod( ) method we have passed the name of the method we want to invoke. Next, by calling the CreateInstance( ) method we have created an object of type stored in t and collected its reference in obj. The add( ) method takes two integers. So, we have created an array of objects arg. Lastly, we have called the add( ) method by using the Invoke( ) method. The add( ) method adds the two integers passed to it and displays the result.
Working with Attributes
The C# Column - Yashawant Kanetkar
Attributes are used to specify additional information about an entity. This information gets written in metadata at compile time. An entity to which an attribute can be applied is known as the ‘attribute target’. The target can be an assembly, interface, structure, class and all possible class members. An attribute is applied to the target by specifying the attribute name in brackets as shown below.
[Serializable]
This attribute is applied to a class stating that objects of this class can be serialised. When the compiler encounters this attribute, it adds suitable instructions in the metadata.
There are thousands of pre-defined attributes in FCL. In addition to them .NET allows us to define our own attributes, called ‘custom attributes’. In this article we will examine the process of defining and using custom attributes and reading them using reflection.
We will create a custom attribute that can be applied to create a documentation of a class and its members. Documentation of a class would include the purpose of the class and how many methods it has, whereas documentation of method would include their purpose. We can write comments to achieve this. However, comments remain in the source code and are not accessible through the assembly. If we add documentation through attributes they go to the assembly and can be accessed using reflection.
Declaring an Attribute
As everything in C# is declared in a class, an attribute is also embodied in a class. To create a custom attribute we have to derive a class from the System.Attribute class. We have named our derived class as DocumentAttribute. The class DocumentAttribute has three data members as shown below.
class DocumentAttribute:System.Attribute
{
string info;
int cnt;
}
The string variable info keeps a description of the entity and the integer variable cnt keeps count of the methods. We can declare attributes either with parameters or without parameters. The parameters can be positional parameters or named parameters. The positional parameters are initialised in constructor, whereas, named parameters are initialised by defining a property. Another difference between the two is that positional parameters must be passed in the same sequence as given in the constructor. The named parameters are optional and can be passed in any sequence. We have declared the Document attribute to take two parameters. We want that one of them viz. info should be the positional parameter and cnt should be the named parameter. This is because cnt would be used only if attribute is applied to class. For class members we can drop it. So, we must define a constructor and a property as shown below.
public DocumentAttribute (string i)
{
info = i ;
}
public int Count
{
get
{
return cnt ;
}
set
{
cnt = value ;
}
}
As we know, custom attributes are written in metadata and can be read through reflection. Generally speaking, reading an attribute means reading values of data members of the attribute class. If we try to read the Document attribute it won’t be able to access the info data member as it is declared private. So, in order to expose it through metadata, we would write a read-only property. Here it is:public string Info
{
get
{
return info ;
}
}
We must also mention the attribute targets. This can be done by using an attribute called AttributeUsage. The AttributeUsage attribute targets classes. So, it has to be declared just above the DocumentAttribute class as shown below.
[AttributeUsage (AttributeTargets.Class AttributeTargets.Method
AttributeTargets.Constructor AttributeTargets.Field,
AllowMultiple = false )]
class DocumentAttribute : System.Attribute
{
}
The AttributeUsage attribute takes two parameters. Using the first parameter we can specify attribute targets. By setting the second parameter to true we can specify that one member can have multiple Document attributes. When attribute targets are assembly or module, the attributes should be placed immediately after all using statements and before any code.
Now that our attribute has been readied, let us see how to use it.
Using an Attribute
We would use the Document attribute to create documentation of the class test1. Here is the declaration of test1 class.
[Document (“Class test1: created for testing custom attributes”, Count = 2 )]
class test1
{
[Document ( “i: Counter variable” )]
int i = 0;
[Document ( “Ctor test1: zero-argument ctor” )]
public test1()
{
}
[Document ( “increment( ): Increments counter” )]
public void increment()
{
i++ ;
}
[Document ( “decrement( ): Decrements counter” )]
public void decrement()
{
i--;
}
}
We have applied the Document attribute to the class through the statement
[Document (“Class test1: created for testing custom attributes”, Count=2 )]
Note that although name of our class is DocumentAttribute we have omitted the word Attribute here—the compiler would add it in automatically. When the compiler encounters this statement, it searches for a class derived from the System.Attribute class in all the namespaces mentioned in using statements.
The positional parameters are passed as the normal parameters, but named parameters are passed by mentioning the name of the parameter like
Count = 2
Needless to say this would invoke the set block of Count property. On similar grounds, we have applied our attribute to other class members.
Reading Attributes
The following code snippet shows how to read the Document attribute applied to a class.
Type t=typeof(test1);
object[] a= t.GetCustomAttributes(typeof (DocumentAttribute), false);
foreach (object att in a)
{
DocumentAttribute d=(DocumentAttribute) att;
Console.WriteLine (“{0} {1}”, d.Info, d.Count);
}
To begin with we have created a reference t to the Type object. We have used the typeof operator on test1 class, which returns reference to an object of Type. The GetCustomAttributes( ) method takes the type of the attribute we want to search and returns an array of references to objects, each of type DocumentAttribute. Next, we have run a foreach loop to read and display each attribute in the array. In our case only one attribute would get displayed.
To read the attributes for methods, we would first obtain all the methods and then read attributes of each of them. This is done through the following
statements.MethodInfo[] mi=t.GetMethods();
foreach (MethodInfo m in mi)
{
object[ ] o = m.GetCustomAttributes ( false ) ;
foreach (object ma in o)
{
DocumentAttribute d = ( DocumentAttribute ) ma ;
Console.WriteLine ( “{0}”, d.Info ) ;
}
}
We have used the reference t to obtain the methods and collected them in an array of type MethodInfo. We have called the GetCustomAttributes( ) method of the MethodInfo class to read the attributes of each method. Similarly we can use the GetCustomAttributes( ) method of the ConstructorInfo and FieldInfo classes to read the attributes given to constructors and fields.
Accessing the Windows registry
The C# Column
The Windows registry is used to store all the configuration information relating to Windows setup, user preferences, software installed and the devices. The registry is arranged in a hierarchical tree-like structure and can be viewed using the popular regedit utility. Each node (folder in a yellow colour) in this tree is called a key.
Each key can contain additional keys or sub-keys (which allow further branching) as well as values that are used to store the actual data. Each registry key may have several values and each value comprises of name-data pair.
For example, a registry key ‘Desktop’ may have values like ‘Wallpaper’, ‘TileWallpaper’, etc. Each value in a key contains the actual data. This data may take several forms ranging from a simple integer value to a user-defined binary object. Each type is represented by a special registry specific type. For example, an integer is represented by REG_DWORD and a string is represented by REG_SZ.
Many applications store their status (information like ‘last file opened in the application’, ‘options selected by the user’, and so on) in registry. We shall also create such an application that stores the background image, foreground colour, size of the window and its position in the registry so that the next time the application is run these selections can be used to build the window.
For accessing the registry we will use the Registry and RegistryKey classes available in the Microsoft.Win32 namespace. Naturally, we will have to add a using statement for this namespace.
Create a Windows Application and design a form as shown in the following screen shot:

Also add the OpenFileDialog and ColorDialog controls to the form. Name them as file and color respectively. The ‘Choose Bitmap’ button allows the user to select an image file through the ‘Open’ file dialog. The ‘Choose Color’ button lets the user select a foreground colour through the standard ‘Color’ dialog.
To store the information of our application we will create a tree structure of two sub-keys-Theme and Settings under the ‘HKEY_LOCAL_MACHINE/SOFTWARE’ key. The Settings sub-key would contain all the values and data. Each key we want to access is represented by an object of the RegistryKey object. So, create the objects that would represent the SOFTWARE key and two user-defined keys as shown below:
RegistryKey skey, tkey, rootkey;
To write anything in registry keys, we must open them. If keys are not created, we must first create them. We would do this in constructor after a call to the InitializeComponent( ) method. Add the following statements to the constructor:
rootkey=Registry.LocalMachine.OpenSubKey(“SOFTWARE”, true);
tkey=rootkey.CreateSubKey(“Theme”);
skey = tkey.CreateSubKey(“Settings”);
In a file system, root drives like C:\ or D:\ are the root directories. In the registry, there are root keys called ‘registry hives’ or ‘base keys’. There are seven such registry hives. The Registry class provides seven fields of type RegistryKey representing the seven registry hives. The LocalMachine field represents the HKEY_LOCAL_MACHINE base key. The OpenSubKey( ) method opens the key. Since we have called the method using LocalMachine field, it would open the SOFTWARE key of Local Machine hive. By passing true as the second parameter we have specified that the key is to be opened for writing. Next, we have created the ‘Theme’ sub-key in the SOFTWARE key and ‘Settings’ in the ‘Theme’ sub-key. We have called the CreateSubKey( ) method for this.
Now let us add handlers for the buttons. The b_bitmap_Click( ) handler gets called when the ‘Choose Bitmap’ button is clicked.
private void b_bitmap_Click ( object sender, System.EventArgs e )
{
file.Filter = “Image Files (*.bmp,*.gif,*.jpg)*.bmp;*.gif;*.jpg” ;
if ( file.ShowDialog( ) == DialogResult.OK )
{
BackgroundImage = Image.FromFile ( file.FileName ) ;
skey.SetValue (“Image”, ( string ) file.FileName ) ;
}
}
Here, we have displayed the ‘open’ dialog. We have applied a filter so that only image files get displayed. If the user selects file we have changed the background image of the form and also written the file path as data of the ‘Image’ value in the ‘Settings’ sub-key. We have called the SetValue( ) method for writing the data. The SetValue() method takes the value name and data as parameters. The data needs to be typecast in suitable type as the method takes parameter of type object.
The b_color_Click( ) method gets called when clicked on the ‘Choose Color’ button. The handler is given below.
private void b_color_Click (object sender, System.EventArgs e )
{
if ( color.ShowDialog( ) == DialogResult.OK )
{
ForeColor = color.Color ;
skey.SetValue ( “Red”, ( int ) color.Color.R ) ;
skey.SetValue ( “Green”, ( int ) color.Color.G ) ;
skey.SetValue ( “Blue”, ( int ) color.Color.B ) ;
}
}
Finally add the code to write size and position to the registry. This code should get executed when the window is closed. So, we would add this code in the Dispose( ) method. The Dispose( ) method is given below:
protected override void Dispose ( bool disposing )
{
skey.SetValue ( “Height”, ( int ) Height ) ;
skey.SetValue ( “Width”, ( int ) Width ) ;
skey.SetValue ( “X”, ( int ) DesktopLocation.X ) ;
skey.SetValue ( “Y”, ( int ) DesktopLocation.Y ) ;
skey.Close( ) ;
tkey.Close( ) ;
rootkey.Close( ) ;
// AppWizard generated code
}
After writing the values we have closed the keys by calling the Close( ) method. Now our writing part is over. We want that when the application is executed the next time, the form should get displayed the same way as it was before closing. To read the information from registry, we have written a user-defined method readsettings().
public void readsettings()
{
rootkey = Registry.LocalMachine.OpenSubKey ( “SOFTWARE”, false ) ;
tkey = rootkey.OpenSubKey ( “Theme” ) ;
if ( tkey == null )
return ;
skey = tkey.OpenSubKey ( “Settings” ) ;
string f = ( string ) skey.GetValue ( “Image” ) ;
if ( f != null )
BackgroundImage = Image.FromFile ( f ) ;
try
{
int r = ( int ) skey.GetValue ( “Red” ) ;
int g = ( int ) skey.GetValue ( “Green” ) ;
int b = ( int ) skey.GetValue ( “Blue” ) ;
ForeColor = Color.FromArgb ( r, g, b ) ;
Height = ( int ) skey.GetValue ( “Height” ) ;
Width = ( int ) skey.GetValue ( “Width” ) ;
int x = ( int ) skey.GetValue ( “X” ) ;
int y = ( int ) skey.GetValue ( “Y” ) ;
DesktopLocation = new Point ( x, y ) ;
}
catch ( Exception e )
{
return ;
}
}
Here, firstly we have opened the keys. When the application is run for the first time, keys are not created. So, we have checked for the null reference in the RegistryKey object. The statements written thereafter read the data from registry and use it. Call this method from the InitializeComponent() method after the initialisation code.
Creating multithreaded applications
The C# Column - Yashawant Kanetkar
In a broad sense, multithreading can be thought of as the ability to perform several jobs simultaneously. For example, while working in Windows, we can simultaneously carry out different jobs like printing a document on the printer, receiving e-mails, downloading files and compiling programs. All these operations are carried out through different programs, which execute concurrently in memory.
Even though it may appear that several tasks are being performed by the processor simultaneously, in actuality it is not so. This is because in a single-processor environment, the processor divides the execution time equally among all the running threads. Thus each thread gets the processor’s attention in a round-robin manner. Once the time-slice allocated for a thread expires, its state and values are recorded, the operation that it is currently performing is put on hold and the processor directs its attention to the next thread. Thus at any given moment if we take a snapshot of memory we will find that only one thread is being executed by the processor. Thread switching happens so fast that we get a false impression that the processor is executing several threads simultaneously.
Multithreading has several advantages to offer. These are listed below:
Responsiveness: Take the example of MS-Word. Here, had the spell checker and the grammar checker not run as different threads, we would have been required to write the document and then submit it to each checker later. This would have resulted in low responsiveness. But since the checkers run in different threads, our document gets checked as we type, thereby increasing the responsiveness of the application.
Organisation: Threading simplifies program organisation. In the ‘File Copy’ example if both the operations—playing the animation and the actual copying were run in the same thread, then after copying a few thousand bytes we would be required to play the next frame of animation. If we run the copying code and animation code in separate threads we can avoid cluttering the copying code with animation code and vice versa.
Performance: Many a time it happens that a program needs to wait for user input or has to give some output. The I/O devices are generally slower than the processor. So the application waits for the I/O operation to finish first. If instead, we use another thread for the I/O operation, the processor time can be allotted to other important tasks independent of the I/O operation, thereby increasing the performance.
Launching threadsNow that we know what threads are, let us now see how they can be created. Starting a thread is very simple. The following statements launch a new thread.
ThreadStart ts = new ThreadStart (func);
Thread t1 = new Thread();
t1.Start();
While creating a thread, we must specify which method should get executed when the thread starts execution. If we wish that a static method func( ) should get executed in the thread, we would have to wrap its address in a delegate and pass it to the constructor of the Thread class. For this, we have to use a predefined .NET delegate class called ThreadStart. The signature of this delegate is such that it returns a void and does not accept any arguments. Hence func( ) also needs to have the same signature. To start the thread we must call the Start( ) method of the Thread class. Both the Thread class and the ThreadStart delegate belong to the System.Threading namespace hence we need to write using System.Threading at the beginning of any program.
Let us now see an example where multithreading is actually needed. Here we plan to make a simple game. The UI of the game is given in the adjacent screen shot:
This application consists of two buttons named start and hit, and three textboxes named num1, num2 and num3.
The user needs to click on the start button to start the game. As soon as this button is clicked, random numbers would get displayed on the three textboxes. Now at any instance if the user clicks on the hit button, the display of numbers stops. If the numbers turn out to be the same the user hits the jackpot.
It is impossible to generate and display the random numbers in the three textboxes simultaneously in the main thread. If we attempt to do so, numbers in the second textbox would get generated only after generating and displaying numbers in the first textbox. So here if we want concurrent functionality, we need to generate and display the numbers for the textboxes in three different threads. On clicking the start button, the three threads get launched as shown in the following code snippet:
private void start_Click(object sender, System.EventArgs e)
{
t1 = new Thread (new ThreadStart (this.display1));
t1.Start();
t2 = new Thread (new ThreadStart (this.display2));
t2.Start();
t3 = new Thread (new ThreadStart (this.display3));
t3.Start();
}
We have added the references t1, t2 and t3 of the Thread class as data members of the Form1 class. The display1( ) method is given below.
public void display1()
{
int j = r.Next (0, 50);
int k = r.Next (50, 100);
for (int i = j ; i <= k ; i++) { num1.Text = i.ToString(); Thread.Sleep(200); } }
Here we have used an object of the Random class referred to by r that we also added as a data member of the Form1 class. Using this object we have created two random numbers and displayed all numbers lying between the two in the textbox. The display2( ) and display3( ) methods are on the same lines. Hence random numbers keep on getting displayed simultaneously on the three textboxes. Now the user needs to click the hit button. The handler for the hit button is given below:private void hit_Click(object sender, System.EventArgs e)
{
t1.Abort();
t2.Abort();
t3.Abort();
if (int.Parse (num1.Text) == int.Parse(num2.Text) &&
int.Parse(num2.Text) == int.Parse(num3.Text))
MessageBox.Show( “Jackpot” );
}
Here we have aborted the threads using the Abort( ) method, compared the numbers and displayed a message if they are same.
Thread states
From the time a thread is started until it gets terminated the thread undergoes many transitions and is said to be in at least one of the several ‘thread states.’
A thread can be in one or more of the following states at any given time: Unstarted, Running, Suspended, WaitSleepJoin, SuspendRequested, Aborted, AbortRequested, Stopped and StopRequested. All these states are members of the ThreadState enumeration declared in System.Threading namespace. Let us now discuss these states.
Unstarted: A thread is said to be in the Unstarted state when it is first created. It remains in this state until the program calls the Start() method of the Thread class.
Running: As soon as the Start() method is called, the thread starts executing and is said to be in the Running state. This means that the thread will now start getting CPU time slots.
Suspended: When a running thread’s Suspend() method is called, the thread goes into the Suspended state and would stop receiving CPU time-slots.
WaitSleepJoin: At times we may want to prevent our thread from getting CPU cycles till passage of some time or make our thread wait till some other thread completes some operation or wait till some other thread terminates.
We can achieve these three scenarios by calling the methods Thread.Sleep( ), Monitor.Wait( ) and Thread.Join( ) methods respectively on our thread. Upon calling one of these three methods, our thread enters the WaitSleepJoin state.
Once in this state we can transition out of it in the following three ways:
(a) The sleeping time specified by the Sleep() method expires.
(b) Another thread calls the Thread.Interrupt() method before the sleeping time expires.
(c) Another thread calls the Monitor.Pulse() method or Monitor.PulseAll() method. Monitor.Pulse() moves the waiting thread into the Running state. Monitor.PulseAll() method moves the thread along with all the other waiting threads into the Running state.
All the above three ways move the thread from the WaitSleepJoin state to the Running state.
Stopped: When a thread completes its execution, it goes into the Stopped state.
Aborted: Whenever an exception is thrown and the thread stops executing abruptly it goes into the Aborted state.
AbortRequested: The AbortRequested state is reached when a thread is in another state but is being requested to abort. As soon as it comes out of the present state, it gets aborted.
SuspendRequested: The SuspendRequested state is reached when a thread is in another state but is being requested to suspend using the Suspend( ) method. As soon as it comes out of the present state, it gets suspended.
StopRequested: The StopRequested state is reached when a thread is being requested to stop. There is no method available that we can invoke to manually force our thread in this state.
Effective synchronisation of threads
The C# Column - Yashawant Kanetkar
C# provides a lock keyword to lock the shared object or resource. Whatever is written inside the parenthesis following the lock keyword gets locked for the current thread and no other thread is permitted to obtain the lock on that resource or object. We can write an expression that evaluates to an object, or just the object on which we wish to have a lock, inside the parenthesis. Whatever the expression may be, the result should be a reference type. The code that uses the object to be synchronised should be written inside the block of the lock statement.To give you a more concrete example, consider two threads working on a file. If both the threads try to open the file and write to it simultaneously, an exception is thrown. Here what we need to do is synchronise both the threads in such a way that only one thread opens the file and writes in it at any given moment.
We have illustrated the same scenario in the example that follows. The User Interface of the application is shown in the following screen shot:
Here we have added a textbox named quantity, a button called purchase and a label called msg. In this application we have simply asked the user the number of goods he wants to purchase, i.e. the demand. We have maintained number of goods in stock in a file called log.bin. Whenever the user purchases a certain amount of goods, the number in the file is updated.
In this application we have also launched another thread which keeps on checking continuously whether the goods are out of stock or not. If the thread finds that the number of goods in the store is zero, it fills the store with hundred items and hence updates the number in log.bin file.
We need to synchronise the two threads so that only one thread works on log.bin at one time. We added the following as data members of the Form1 class.
FileInfo f;
FileStream fs;
byte total=100;
total denotes the total number of goods in store. We have used a value like 100 that can be represented in one byte. This is because FileStream is used to write an array of bytes in the file and if we take a larger number, we would have to first create an array of bytes representing the large number. For the sake of simplicity here we have used a number that can be represented in one byte.
We initialised the reference f, and launched the thread that would keep on checking the stock from time to time after the call to the InitializeComponent( ) method in the constructor as shown below:
f = new FileInfo ( “C:\\log.bin” );
Thread t = new Thread (new ThreadStart(checker));
t.IsBackground = true;
t.Start();
We have made this thread a background thread because we wish that as soon as the application terminates, the thread should also get terminated. The checker( ) method is given below:
public void checker()
{
while ( true )
{
lock (f)
{
try
{
fs = f.OpenWrite( ) ;
if ( total <= 0 ) { total = ( byte ) ( total + 100 ) ; fs.WriteByte ( total ) ; msg.Text = “Stock sold out!!!! Goods incremented” ; } else msg.Text = “Items in stock: “ + total ; Thread.Sleep ( 1000 ) ; fs.Close(); } catch { MessageBox.Show ( “Can not Open file” ) ; } } } }
We have put the checker logic in a while loop so that the store is checked continuously after every 1000 milliseconds (Thread.Sleep(1000) ensures this). Next we have obtained a lock on the file object referred to by f using the lock statement. After acquiring the lock we have checked whether total is zero or less than zero. If yes, we have incremented total by 100 and written the value back in the file. If it is not zero or less than zero, we have simply displayed the number of goods in the label named msg. We have put the whole logic in a try-catch block, the reason for which would be clarified later.
Now if the user supplies some number in the textbox and clicks on the purchase button, the following handler gets called:
private void purchase_Click ( object sender, System.EventArgs e )
{
lock ( f )
{
try
{
fs = f.OpenWrite( ) ;
byte b = byte.Parse ( quantity.Text ) ;
total = ( byte ) ( total - b ) ;
fs.WriteByte ( total ) ;
MessageBox.Show ( “Transaction Complete” + “\n” + “Item
remaining: “ + total ) ;
fs.Close( ) ;
}
catch
{
MessageBox.Show ( “Cannot Open file” ) ;
}
}
}
Here also we have first acquired a lock on the file object. Next we have collected the quantity demanded by the user in a byte called b. Then we have subtracted that amount from total and written back the new value of total in the file. We have put this logic also in a try-catch block. This method gets executed in the main thread, whereas checker( ) gets executed in a thread referred to by t.
Now if you execute the program, you would see that the number of goods in the store is displayed continuously on the msg label. As soon as the user purchases some goods, the number is updated and when the number goes below zero, the goods are incremented.
This works fine, but if you really want to understand the need of synchronisation, remove or comment the lock statement and see the effect. As soon as the user clicks on purchase, an exception gets thrown indicating that the file is already in use. To catch this exception we have used the try-catch blocks. The exception gets thrown because both the threads try to open and write to the file simultaneously.
Besides the lock statement, synchronisation can also be achieved in C# using the Interlocked class, the Monitor class, Mutexes and Events.
The Interlocked class provides four methods to perform operations like incrementing, decrementing or exchanging variables in a thread-safe manner. The four static methods are—Increment( ), Decrement( ), Exchange( ), and CompareExchange().
Monitors: In .NET, Monitors are similar to the lock statement and are used to synchronise concurrent thread accesses so that any resource can be manipulated only by one thread at a time. Thus they support mutually exclusive access to a shared resource. Monitors are similar to Critical Sections in Windows. The Framework Class library provides a Monitor class to represent the Monitor. The above program will also work if we use Monitor.Enter( f ) and Monitor.Exit( f ) methods, belonging to the Monitor class, instead of the lock statement. All we need to do is to write the code present in the lock block between the calls to Monitor.Enter( f ) and Monitor.Exit( f ) methods. In fact when we use the lock statement it gets resolved into calls to Monitor.Enter( ) and Monitor.Exit( ) methods in the IL code.
Mutexes: The word mutex comes from two words-”mutually’ and “exclusive”. Mutexes are similar to monitors except that they permit the object or resource to be shared across processes. Mutexes can thus synchronise threads belonging to different applications. The System.Threading.Mutex class represents a mutex object.
Events: are used to synchronise threads in some order. For example if two threads are executing and we want that not only should the threads work synchronously, but they should also get executed alternately, we should use events. If we use events we will get the following order of execution:
Thread1 executing
Thread2 executing
Thread1 executing
Thread2 executing
And so on... But instead if we use only monitors, we will achieve synchronisation but threads are likely to get executed in the following manner:
Thread1 executing
Thread1 executing
Thread2 executing
Thread1 executing
The .NET Framework class library wraps two of the Windows’ kernel objects-auto-reset events and manual-reset events-into two classes called AutoResetEvent and ManualResetEvent.
Executing file IO with serialisation
The C# Column - Yashawant Kanetkar
Serialisation is the process of writing objects on a persistent storage media such as a file on the disk. Complementary to serialisation is deserialisation using which we can restore objects. Let’s take a look at an example where we have used serialisation and deserialisation to store and retrieve objects of a class to/from a file.
The User-Interface of the application is given in the following screen shot:
This application enables the user to collect information of students who wish to apply to a college and store their information in a file called student.dat in the form of objects of the student class. We have used textboxes to collect the information such as the name of the student in a textbox named sname, the address of the student in a textbox named sadd, the field in which he wants to do his major in a textbox named smaj and his age in a textbox named sage. To uniquely identify a student’s application we have provided an application number to each applicant, which would get displayed in a label named lappnum and would get incremented every time the user adds information of a new applicant. As soon as the user clicks on the “Apply” button named apply, the data would get serialised in the student.dat file.
The application also provides the user with a facility through which he can retrieve the information of any particular student back from the student.dat file and display it. The user needs to supply the application number in the textbox named tappnum and click on the “Look Up” button called lookup. As soon as he clicks the button, the information of that particular student gets displayed in a message box.
To be able to serialise or deserialise objects of the student class, we need to attach the [Serializable] attribute to the class, which indicates that objects of the class can be serialised and deserialised. The student class is given below:
[Serializable]
class student
{
int appnum;
string name;
string address;
string major;
int age;
public student (int ap, string n, string ad, string m, int ag)
{
appnum = ap;
name = n;
address = ad;
major = m;
age = ag ;
}
public override string ToString()
{
string s = “Application Number:“ +appnum.ToString( ) + “\n”;
s += “Name: “ + name + “\n”;
s += “Address: “ + address + “\n”;
s += “Major: “ + major + “\n”;
s += “Age: “ + age.ToString( );
return s;
}
}
Here we have added relevant data members to the class representing the information of the student. The five-argument constructor is used to initialise the data members and the overridden method ToString( ) is used to generate a display string of any instance on which the method is called.
We have created an array of this class in our Form1 class. We also added an integer data called appnum that would keep track of the application number of each student, a reference of the FileInfo class and a reference of the BinaryFormatter class as data members of the Form1 class as shown below:
student[] stu = new student[20];
int appnum = 1;
FileInfo f;
BinaryFormatter b;
We added the following code to the constructor of the Form1 class after the call to the InitializeComponent( ) method.
lsappnum.Text = appnum.ToString();
f = new FileInfo(“C:\\student.dat”);
b = new BinaryFormatter();
Here we have first set the label displaying the application number to appnum. Next we have created a FileInfo object and stored its reference in f. To be able to create an object of the FileInfo class we need to add using System.IO at the beginning of the program. Then we have created an object of the BinaryFormatter and stored its reference in b. This object would be used to serialise and deserialise data. For this we need to add the statement using System.Runtime.Serialization.Formatters.Binary;
When the user enters student information in the textboxes and clicks on the apply button, the following handler gets called:
private void apply_Click(object sender, System.EventArgs e)
{
int ap = int.Parse(lsappnum.Text);
string n = sname.Text;
string ad = sadd.Text;
string m = smaj.Text;
int ag = int.Parse(sage.Text);
student s = new student(ap, n, ad, m, ag);
Stream sw = f.Open(FileMode.Append, FileAccess.Write);
b.Serialize(sw,s);
sw.Close();
appnum ++ ;
lsappnum.Text = appnum.ToString();
sname.Clear();
sadd.Clear();
smaj.Clear();
sage.Clear();
}
Here we have first collected the information in local variables and created an object of the student class by passing the values to the constructor of the student class. Next we have opened the file referred to by f with the FileMode as Append because we wish to append the file with the current student at the end of the file, and the FileAccess as Write because we will be writing to the file. We have collected the reference of the Stream object returned by the Open( ) method in sw. Next we have called the Serialize( ) method of the BinaryFormatter class and passed the Stream reference to it along with the object that we wish to serialise. After serialising the object we have closed the stream. Next we have incremented the value of appnum by 1 and set the text of the label to the incremented value. Lastly we have cleared all the textboxes so that the user can enter information of another student.
Now if the user wishes to retrieve the information of any user, he needs to supply the application number of the student and click on the lookup button. On doing so the following handler would get called:
private void lookup_Click(object sender, System.EventArgs e)
{
int app = int.Parse(tsappnum.Text);
Stream sr = f.Open(FileMode.Open, FileAccess.Read);
int i = 0;
while (sr.Position != sr.Length)
{
stu[i] = (student) b.Deserialize(sr);
i++;
}
string str = stu[app-1].ToString();
MessageBox.Show(str);
sr.Close();
}
Here we have first collected the application number of the student whose information we wish to retrieve in a local variable called app. Next we have opened the student.dat file again with the FileMode as Open and FileAccess as Read and collected the stream returned in a reference called sr. The Position property of the Stream class indicates the current position within the stream and the Length property indicates the total length of the stream. We have used these two properties in a while loop and deserialised all the objects in the array referred by stu. Now it is very easy to retrieve the instance of the student requested by the user from the array, i.e. it would be the same instance present at the index app-1 in the array. Next we have called the overridden ToString() method on that instance and displayed the string in the message box. Lastly we have closed the stream.
There is a drawback in the application in that as soon as we execute the program for the second time, the application number is set to 1. To overcome this, we can store the application number in the registry and set the application number to the value in the registry every time we execute the application.