Programming in C# - Part 2
structs
in C#:
In C++, there is very less distinction between structs and classes. The only difference being that the default visibility in a struct is public whereas in a class it is private. In C# however, structs offer an important difference between classes and that is, “structs are value types” and thus are light weight objects that are created on the stack. This results in efficient use in terms of memory and execution speed when structs are used as simple objects or when they are used in arrays. However, when used in collections, structs actually are slower than classes because of boxing being needed as collections expect references. Here is a list of important characteristics of structs.
Example: Create a console type application called “TestStruct”. Change the file name of the default class from class1.cs to TestStruct.cs. Add a C# class to the project called Point. Type the following code in the Point class.
//-----Point.cs-------
using System;
namespace TestStruct
{
/// <summary>
/// Summary description for
Point.
/// </summary>
public struct Point
{
private int X;
private int Y;
public Point(int x1, int y1) // struct cannot
{ // have
parameter-less constructor
X
= x1; Y = y1;
}
public int x
{
get { return X; }
set { X = value; }
}
public int y
{
get { return Y; }
set { Y = value; }
}
public override string ToString()
{
return (String.Format("x={0}, y={1}",X,
Y));
}
}
}
Modify the TestStruct class to look as shown below:
//----------TestStruct.cs------------
using System;
namespace TestStruct
{
/// <summary>
/// Summary description for Class1.
/// </summary>
class
TestStruct
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[]
args)
{
Point p1 = new
Point(20,30); //
using new to create
// struct object
Console.WriteLine(p1.ToString());
// the
following code will work if X, Y were declared
//
public in Point class
/* Point p2;
//without using new to create struct
p2.X = 50;
p2.Y = 60; // struct must be initialized before it
// can be used
Console.WriteLine(p2.ToString()); */
}
}
}
If you build and execute the program by choosing “debug->start without debugging”, you will see the following result.
Note that the new keyword can be used to create a struct object on the stack. You can also create a struct object without the new keyword (however, it requires the struct data members to be public), as shown in the above example. It is a good practice to keep the struct data private and thus use the new keyword in creating a struct.
Interfaces:
An interface is a collection of function prototypes that may be implemented by a particular class. For example, earlier we saw an IDisposable interface that has a single method called Dispose. A class can derive from IDisposable and implement the Dispose method, which a client can call to release the resources explicitly.
public class XYZ : IDisposable
…
public
void Dispose()
{
Console.WriteLine("Disposing
resources ");
//
release the resources
GC.SuppressFinalize(this); // no need for GC to
call
//
Finalize now
}
An interface can have many methods in it e.g.,
interface IStatistics
{
int ComputeMax( );
float ComputeAvg( );
float ComputeStdDev( );
}
A C# class can derive from many interfaces, but only a single C# class. If a class is being derived from both interfaces and another class, then the class should appear first in the inheritance list e.g.,
class XYZ : MyBase, IDisposable, IStatistics
{
….
….
}
Interfaces can also contain properties besides method declarations. However, most of the time interfaces contain only method prototypes.
interface IStudent
{
float ComputeGPA( );
int HoursToGraduate( );
int ID {get; set; }
string Lname { get; set; }
string Fname { get; set; }
}
The implementing class will write the get, set functions for the properties as shown below:
class GradStudent : IStudent
{
virtual public float ComputeGPA( ) { …. // code for computing GPA }
virtual public int HoursToGraduate( ) { …. // code for computing hours left }
// virtual is optional, but will be needed if other classes
// will derive from this class, and perhaps override these
private int id;
public int ID {
get { return id; }
set { id = value; }
}
private string lname;
public string Lname {
get { return lname; }
set { lname = value; }
}
private string fname;
public string Fname {
get { return fname; }
set { fname = value; }
}
Deriving
from Multiple Interfaces and Polymorphism Issues:
When base class references (or interface references) are used to access derived class objects, some times, we need to know if the object is implementing a particular interface or not. C# provides two mechanisms for this by the use of as and is keywords. We will explain this by the following simple example.
Example: Create a C# console type application. Name the project MultiInterface. Change the default class name to MyX. Type the following code in it.
//---------MyX.cs--------------
using System;
namespace MultiInterface
{
/// <summary>
/// Summary description for Class1.
/// </summary>
interface
IStatistics
{
int
ComputeMax();
float
ComputeAvg();
}
interface
IOrder
{
void
AscendingOrder();
void
DescendingOrder();
}
public class MyX : IStatistics, IOrder
{
private int a, b, c;
public MyX(int a1, int b1, int c1) // constructor
{
a = a1;
b = b1;
c = c1;
}
//-----------IStatistics
methods---------------
public int ComputeMax()
{
int max;
max = a;
if (max
> b)
max = b;
if (max
> c)
max = c;
return
max;
}
public float ComputeAvg()
{
return (a
+ b + c)/3.0f;
}
//----------------IOrder
methods-------------------
public void AscendingOrder()
{
int temp;
if (b >
c)
{
temp = b;
b = c;
c = temp;
}
if (a >
b)
{
temp = a;
a = b;
b = temp;
}
if (b >
c)
{
temp = b;
b = c;
c = temp;
}
}
public void DescendingOrder()
{
int temp;
if (b <
c)
{
temp = b;
b = c;
c = temp;
}
if (a <
b)
{
temp = a;
a = b;
b = temp;
}
if (b <
c)
{
temp = b;
b = c;
c = temp;
}
}
public override string
ToString()
{
return string.Format("a = {0}, b= {1},
c={2}",a,b,c);
}
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[]
args)
{
MyX x1 = new
MyX(3,2,7);
Console.WriteLine("Original x1:
" + x1);
IStatistics Ist = x1 as IStatistics; // see if
it supports ISt.
if (Ist !=
null) // IStatistics
is supported
{
Console.WriteLine(" Max = {0}, Avg
= {1}",
Ist.ComputeMax(), Ist.ComputeAvg());
}
else
Console.WriteLine("IStatistics is
not suuported by MyX");
IOrder Ior = x1 as
IOrder; // see if it supports IOrder
if (Ior !=
null) // IOrder is
supported
{
Ior.AscendingOrder();
Console.WriteLine(x1);
}
else
Console.WriteLine("IOrder is not
suuported by MyX");
//----alternative
to "as" for Polymorphism : "is" keyword
Console.WriteLine("\nUsing the is
operator--------------");
MyX x2 = new
MyX(6,5,9);
Console.WriteLine("Original x2:
" + x2);
if (x2 is IStatistics) // see if
x2 is derived from IStatistics
{
IStatistics Ist2 = (IStatistics) x2;
Console.WriteLine(" Max = {0}, Avg
= {1}",
Ist2.ComputeMax(), Ist2.ComputeAvg());
}
else
Console.WriteLine("IStatistics is
not suuported by MyX");
if (x2 is IOrder) // see if x2 is
derived from IOrder
{
IOrder Ior2 = (IOrder) x2;
Ior2.AscendingOrder();
Console.WriteLine(x2);
}
else
Console.WriteLine("IOrder is not
suuported by MyX");
}
}
}
When testing for polymorphic behavior, the “as” keyword can be thought of a test to see if the object supports a given interface. Similarly, the “is” operator can be thought of as a test to see if the given object is derived from the base interface. Generally, the IL code produced by the “as” is faster than the “is” use.
You can experiment by removing the derivation of MyX from one of the interfaces in the above code to see what kind of output it produces, e.g., change the following line in the above code:
public class MyX : IStatistics, IOrder
to,
public class MyX :
IStatistics
and run the program. You will see the following output.
When a class implements an interface and a second class later on derives from this class, the second class can override the interface methods that it inherited from the first class by using the override keyword. A class is always free to add more methods than it inherited from the interfaces.
Note that the interface is conceptually similar to the abstract class with abstract methods. For example, the interface IStatistics could alternatively be written as:
abstract class IStatistics
{
abstract public int ComputeMax( );
abstract public void ComputeAvg( );
}
However, the downside of using abstract classes to defining interface methods is that a particular class can only derive from one other class (C# has single inheritance of classes), whereas a class can derive from multiple interfaces.
It is possible to have a method appear in multiple interfaces. For example, what if IStatistics had a method called ComputePrime which tests to see if any of the datamembers a,b,c are prime. Also, if IOrder had a similar method called ComputePrime which tests to see if the first number a is a prime number or not. In this scenario, how can one class derive from both IStatistics and IOrder and provide two distinct implementations of ComputePrime method? The answer is quite simple, the implementation code will explicitly identify which interface the method belongs to i.e.,
bool IStatistics.ComputePrime( )
{ ….. // method declaration is public by default but cannot be virtual
// code for ComputePrime
}
bool IOrder.ComputePrime( )
{ …. // code for ComputePrime of IOrder
// method declaration is public by default but cannot be virtual
// this may create problems if another class derives from this class
// and base class references are used to invoke derived class methods
}
An interface can be implemented by a struct. However, in this case you should access the members of the struct through the struct object without casting it to a particular interface. The reason is that when we cast a struct to an interface, an implicit boxing occurs which causes all modifications to be made on the boxed type and not the original object.
For example, you could access any of the methods in MyX class through the object of it. If you modify the main by adding the following lines of code:
//---------accessing
objets without any casting----
MyX x3 = new MyX(4,2,8);
Console.WriteLine("\nOriginal x3: " + x3);
x3.AscendingOrder();
Console.WriteLine(x3);
Similarly, for sealed classes, you
should also access the members through the object without casting it to an
interface.
In general, you should check the existence of a particular interface by using the as operator, before attempting to call the corresponding method.
Exception Handling:
C# provides a detailed exception handling mechanism similar to C++. The System namespace contains a few different types to be used as exception objects in throwing exceptions. The most popular of these is the System.Exception class. Other popular types include ArithmeticException, ArgumentNullException, InValidCastException, OverflowException etc..
Exception handling uses try, catch blocks and a throw statement. If errors are to be handled in a code block, it is enclosed in a try block as:
try {
…
If error detected throw some object
…
}
catch (System.ArithmeticException e) { // arithmetic type exception
…
}
catch { // any other kind of exception
…
}
Following the try block is a set of statements starting with the catch keyword. Each catch block has the code that is to be executed in case of a certain error condition. If there are multiple catches written to handle different error conditions, only one of the catches is executed.
The catch all in C# is a simple catch statement without any parameters (as opposed to catch(…) in C++).
C# provides a finally block which is placed after all the catch statements and is always executed regardless the exception is thrown or not. Typically you write the code to release the resources held in the finally block.
You can also create your own customized exception types. The requirements in this case are that your exception class has to be derived from System.ApplicationException class.
Other improvements in terms of exception handling include, capability to set and examine different properties of the exception object such as Message, HelpLink and StackTrace. The Message can display a helpful message when the exception is caught. The HelpLink can provide a link to a URL so that a detailed error description can be located. The StackTrace property allows you to print out the chain of calls which ended up as an exception.
For those situations where exceptions are rethrown, C# provides an InnerException property so that the original exception can be placed inside the new one and later examined to be able to determine the exception history.
We will explore the above exception capabilities in C# through examples.
Example: Create a C# console application called ExceptionTest. Name the default class MyE. Type the following code in it.
//------------------MyE.cs-----------------------
using System;
namespace ExceptionTest
{
/// <summary>
/// Summary description for
Class1.
/// </summary>
class MyE
{
float Divide(int n1, int n2)
{
float result;
result
= 0; //
expects a variable to be assigned before its use
if ((n1 == 0) && (n2 == 0))
throw new
System.ArithmeticException("Both numbers are zero");
if (n1 == 0)
throw new
System.Exception("Incorrect First Number");
if (n2 == 0)
throw new
System.DivideByZeroException();
result
= (float)n1 / n2;
return result;
}
/// <summary>
/// The main entry point
for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
try
{
int x, y;
x =
9; y = 0;
MyE
e1 = new MyE();
Console.WriteLine("Result
of Division: {0} ",e1.Divide(x,y));
}
catch(System.DivideByZeroException e)
{ // has to be before
ArithmeticException or Exception handler
Console.WriteLine("DivByZ
Exception Occured: {0}",e.Message);
}
catch(System.ArithmeticException e)
{ // has to be
before Exception handler
Console.WriteLine("Arith.
Exception Occured: {0}",e.Message);
}
catch(System.Exception e)
{
Console.WriteLine("Exception
Occured: {0}",e.Message);
}
catch // catch all
{
Console.WriteLine("Unknown
Exception Occured");
}
finally // always executed regardless an exception
//occurs
or not
{ // the code here generally releases resources
Console.WriteLine("\nFinally
Block releasing resources");
}
}
}
}
Build and run the program by choosing Debug->Start without Debugging.
Experiment with different numbers to cause different kind of exceptions.
Example: Writing your own custom exception handling class, exception handling in nested function calls and determining the stack trace.
By Right clicking on the project, add a C# class called MyException, and type the following code in it.
//-----------MyException.cs-----------------
using System;
namespace ExceptionTest
{ // custom exception
class, has to be derived from
//System.ApplicationException
public class
MyException:System.ApplicationException
{
public MyException(string
msg):base(msg)
{
}
}
}
Modify the above program (MyE.cs) to include a function called Fadjust in the MyE class with the following code in it.
float Fadjust(int p1,
int p2)
{
if (p1 == 99)
throw new
MyException("One of the parameters is illegal");
return Divide(p1,p2);
}
Modify the main to look as below (modifications needed are shown in bold):
static void Main(string[] args)
{
try
{
int x, y;
x = 0; y = 0;
MyE
e1 = new MyE();
Console.WriteLine("Result
of Division: {0} ",e1.Fadjust(x,y));
}
catch(MyException e)
{
Console.WriteLine("Customized Exception Occured: {0}", e.Message);
}
catch(System.DivideByZeroException
e)
{ // has to be before
ArithmeticException or Exception handler
Console.WriteLine("DivByZ
Exception Occured: {0}",e.Message);
}
catch(System.ArithmeticException
e)
{ // has to be
before Exception handler
Console.WriteLine("Arith. Exception Occured: {0}",e.Message);
Console.WriteLine("Arith.
Exception HelpLink: {0}",e.HelpLink);
Console.WriteLine("Arith. Exception StackTrace: {0}",e.StackTrace);
System.Diagnostics.Process.Start(e.HelpLink); // launch browser
}
catch(System.Exception e)
{ Console.WriteLine("Exception Occured:
{0}",e.Message);}
catch // catch all
{ Console.WriteLine("Unknown Exception
Occured"); }
finally // always executed regardless an exception occurs or not
{ // the code here
generally releases resources
Console.WriteLine("\nFinally
Block releasing resources");
}
}
Modify the Divide function so that the check for n1=0 and n2=0 looks like:
if ((n1 == 0) && (n2 == 0))
{
System.ArithmeticException
e = new System.ArithmeticException("Both
Numbers are zero");
e.HelpLink
= "http://www.bridgeport.edu/~mahmood/helpf1.htm";
throw e;
}
If you run the program with x=0; y=0; it will cause an Arithmetic exception for which the stack trace printout will look as shown below. The browser will also be launched displaying the help file from http://www.bridgeport.edu/~mahmood/helpf1.htm if you have already prepared and stored a helpf1.htm file at the above link.
Experiment by invoking the Fadjust function from the main with different values of x and y, and causing different types of exceptions.