C# Language Reference
C# Language Reference
Programming in C#
SkillAssure
SkillAssure Page 1
C# Programming
01 - Basics
Goals:
Overview
We will now introduce the core of the C# language; that is, we will cover the features that
every program will need to use. Most of this section will be a relatively straightforward
examination of syntax so you may need to practice a bit of delayed gratification - we'll
get to cooler stuff very soon.
Class
A class is the primary way for the C# developer to create a new data type. The definition
syntax is the keyword class, the class name, and then curly braces enclosing the class
body.
A class typically contains two types of things: variables (i.e. data) and methods (i.e.
functions). All variables and methods must be members of some type because the
language does not allow global variables or global methods. For now, we will keep things
simple and create classes that contain just the Main method. We will expand on the
capabilities of a class in later chapters.
Note that there are several other ways to create new types: struct, enum, delegate, and
interface; however, each of these are more specialized than class.
SkillAssure Page 2
C# Programming
Main method
The Main method defines the application entry point. The name is case sensitive so the
capital M is required. Main must be a static method of some class. We will discuss the
meaning of static in a later chapter.
In addition to serving as the application entry point, Main can also interact with the
environment. Main can take an array of strings as its argument. The strings will be
obtained from the command line when the program is invoked.
Main can also return an int code back to the invoking environment. The return code is
most often used to indicate the success or failure of the program execution. Traditionally,
a return value of zero indicates success while non-zero values are used as error codes to
indicate failure.
SkillAssure Page 3
C# Programming
Simple types
The set of core types offered by C# are called the simple types. These are analogous to
what other languages call built-in types or primitive types.
There is a Boolean type named bool which can take on only two values: true and false.
The character type is 16 bit Unicode. Character literals are enclosed in single quotes; for
example, a capital A would be written as 'A'. A less common but interesting special case
allows a character to be specified by giving the hexadecimal or Unicode value. The
capital A character has a Unicode value of 65 (hex 0041) so it could be written as either
'\u0041' or '\x0041'.
There are several different flavors of integer that vary by size and by whether they are
signed or unsigned. Signed types can store both positive and negative values while
unsigned types store only positive numbers. Common practice is to use int as the
primary integer type since 32 bits is usually large enough for most needs and a signed
quantity is typically preferred.
There are three different sizes of floating point types: float, double, and decimal. The
64-bit type double is the most commonly used of the three. The float type is only 32
SkillAssure Page 4
C# Programming
bits and so is often considered too small for any application that does serious floating
point arithmetic. The decimal type provides extremely high precision at the expense of
range and so is intended for specialized financial and scientific calculations.
The string type represents a sequence of characters. String literals are enclosed in
double quotes.
Local variables
Local variables are declared inside a method and are only accessible inside the method
body. The declaration syntax is type name followed by comma separated list of variables
with a semicolon ending the statement.
Local variables can optionally be given an initial value when they are declared. The
initialization syntax is the assignment operator = followed by the initial value. The initial
value can be a literal or an expression.
SkillAssure Page 5
C# Programming
Local variables must be assigned to before they can be used. The compiler tracks the state
of all local variables and rejects any attempt to use them before they have been given a
value. This is an extremely nice service provided by the compiler and should help to
eliminate an entire category of program bugs.
Type conversion
It is occasionally necessary to convert a value from one simple type to another; for
example, converting from int to long or from double to int. These conversions fall into
two categories: implicit and explicit. An implicit conversion happens automatically and
there is no need for any special action on the part of the programmer. On the other hand,
an explicit conversion requires that the programmer explicitly request the change by
adding a cast to their code. The cast syntax is the destination type name enclosed in
parentheses. In general, conversions that could lose information require an explicit cast.
For example, a cast is needed to convert double to float, float to int, or long to int.
SkillAssure Page 6
C# Programming
The simplest way to read input is using the ReadLine method which reads an entire line
of input at once. The result is a string containing the entire input line.
If more control over input is needed, the Read method can be used to read each input
character one at a time.
After reading the user input, the next step is to get it into the desired form. If the user is
entering a string value such as their name, then you are done since the input data is
already a string. However, if the user is entering a numeric value such as their age or
salary, then the input string must be converted to a numeric form. The .NET Framework
Class Library supplies a class named Convert that can perform most commonly needed
conversions. The Convert class is in the System namespace.
The Console class also supplies output methods. There are several methods named
WriteLine that write various data types to the console. In addition to writing the data,
WriteLine adds a line terminator to the output so any subsequent output will appear on a
new line.
SkillAssure Page 7
C# Programming
The Console class also offers a set of methods name Write that work just like
WriteLine except that they do not add a line terminator to the output.
Namespace
A namespace represents a logical group of types. The .NET Framework Class Library
makes heavy use of namespaces to partition it into smaller pieces that are easier for users
to handle. The core of the library is gathered into a namespace called System. To access
the members of a namespace, you use the namespace name and then the dot operator as a
separator.
SkillAssure Page 8
C# Programming
Prefixing each use of a library component with the namespace name quickly becomes
tedious and makes the code unnecessarily verbose. A using directive can be used to
obtain shorthand access to the components of a namespace. The using directive is
typically placed at the top of source file. The syntax consists of the keyword using, the
namespace name, and a semicolon.
Arithmetic operators
A comprehensive set of arithmetic operators are available. The standard +, -, *, and /
operators are present. A slightly more interesting offering is the remainder operator ( %)
which computes the remainder of integer division. In addition, there are some specialized
operators which manipulate the binary representation of a variable
SkillAssure Page 9
C# Programming
Each arithmetic operator has a corresponding combination assignment version that does
two things at once: it performs the arithmetic and assigns the result. For example, the
combination operator for addition is +=.
The designers of the C language family (C, C++, Java, C#) seem to have agreed that
providing such combination operators is desirable since there are quite a few of them
available.
There are two specialized operators to perform the increment (++) and decrement (--)
operations. The basic use of these operators is extremely simple: they add or subtract 1
SkillAssure Page 10
C# Programming
It turns out that the increment and decrement operators are slightly more powerful (and
subtle) then they at first appear. The operators can actually be placed on either side of a
variable and they have a slightly different effect in the two different positions. This added
ability only comes into play when the increment/decrement is used as part of a larger
expression. In that case, the position of the operator determines when the
increment/decrement is performed.
When the operator is placed in front of the variable the operation is performed first,
before the variable is used in the larger expression. This is called pre-increment or pre-
decrement.
When the operator is placed after the variable the operation is performed second; that is,
after the variable is used in the larger expression. This is called post-increment or post-
decrement.
Finally, there is the somewhat cryptic conditional operator ?: that provides a shorthand
way of writing an if-then-else statement. The ? and the : serve as delimiters between the
three operands. The first operand is a Boolean expression. The Boolean expression is
evaluated, if true, the result is the expression after the ? otherwise the result is the
expression after the :.
SkillAssure Page 11
C# Programming
Comments
There are two primary comment styles: multi line and single line. A multi line comment
is delimited beginning with /* and ending with */. A single line comment begins with //
and extends to the end of the line.
There is one additional type of comment that you might see called a documentation
comment. The documentation comment contains XML that serves to document the code
for users. The C# compiler provides a /doc switch to build an XML file from the
documentation comments.
SkillAssure Page 12
C# Programming
Array
Arrays provide efficient storage for multiple data elements. An array is specified using
square brackets [ ] and must be created using the new operator. To create an array, both
the element type and the length must be specified.
Array elements are accessed using [ ] and the desired index. Indices start at 0, so the
valid indices for a length 5 array are 0, 1, 2, 3, 4. Bounds checking is performed to
prevent access to memory outside the array. If an invalid index is used, the CLR traps the
error and generates an exception of type IndexOutOfRangeException. The bounds
checking is performed at runtime and does add a small amount of overhead; however, the
designers of the CLR thought that the added program safety and robustness were well
worth the time.
The length of an array is fixed at the point it is created and it cannot be resized. For
convenience, the length of an array is recorded in its Length property which can be
accessed using the dot operator.
SkillAssure Page 13
C# Programming
When an array is first created, each element is set to a default value based on its type. For
all the numeric types such as int, double, etc. the default value is 0. For other types the
story is a bit more interesting: an array of bool has each element set to false and an
array of characters starts with each element set to the null character ( 0x0000).
if else statement
The primary way to perform conditional execution is with an if statement. An if
statement has an associated Boolean expression and a statement: if the Boolean
expression is true then the statement is executed; otherwise, the statement is skipped. C#
is case sensitive so if must appear in lower case. Also note that the parentheses around
the Boolean expression are required.
An if statement can optionally contain an else part. The statement associated with the
else part gets executed only if the expression is false.
SkillAssure Page 14
C# Programming
Boolean operators
Boolean expressions are built using the comparison and logical operators. The
comparison operators provide ways to test common properties such as equal, greater than,
less than, etc. The logical operators perform the Boolean logic functions and, or, not.
Code block
Most of the control constructs such as the if statement and the loops permit only a single
statement as their body. Placing multiple statements is an error.
In order to associate multiple actions with a control construct, the statements must be
placed inside curly braces. The curly braces form what is sometimes called a compound
statement or a code block.
SkillAssure Page 15
C# Programming
switch statement
A switch statement performs selection from a fixed set of options. It consists of an
expression followed by a set of cases. The expression is evaluated and the result
compared against each case. If a match is found, the code associated with that case is
executed. If no match is found, the default case is executed. The default case is
completely optional.
SkillAssure Page 16
C# Programming
A break is required to mark the end of each case that contains associated code. It is a
compile time error if the break is omitted.
It is possible to associate multiple options with a single action by placing the cases one
after the other with no intervening code. Note that it is legal to omit the break from a
case if it does not have any associated code; this fact is crucial in understanding why
multiple labels are allowed and only a single break is required at the end of the entire
SkillAssure Page 17
C# Programming
construct.
Loops
C# offers 4 loops: while, do, for, and foreach.
A while loop is the most basic looping construct. It consists of the keyword while
followed by a Boolean expression and then a statement or code block. As long as the
Boolean expression continues to evaluate to true, the associated code will get executed.
A do loop is both more complicated and less useful than a while loop. It consists of the
keyword do, a statement or code block that forms the body of the loop, the keyword
while, a Boolean expression, and a semicolon to end the whole construct. The odd thing
about the do loop is that the test condition is at the bottom of the loop. When the loop
begins to run, the test is not evaluated so the body is executed immediately. Only after the
body is executed is the test evaluated. If the test is evaluates to true, then the process
repeats; otherwise the loop ends. This behavior means that the body of a do loop is
always executed at least once.
SkillAssure Page 18
C# Programming
A for loop has three parts separated by semicolons. The first part is used for initialization
and is performed only once when the loop first begins execution. The second part is the
test condition used to determine if the loop should continue to run. The third part is
executed after each iteration and so is typically used to increment a loop counter.
A foreach loop is a specialized construct for use with collections of data such as arrays.
The syntax and use is simple and very readable: the keyword foreach, an open
parenthesis, the type of data stored in the collection, a variable name used to hold each
successive value from the collection, the keyword in, the collection to be traversed, a
close parenthesis, and finally the loop body. The loop body is executed once for each
element in the collection.
SkillAssure Page 19
C# Programming
The keyword break can be used to exit any of the loops before the loop would normally
terminate. break is most often used to abort processing if an error occurs.
SkillAssure Page 20
C# Programming
Goals:
Overview
Steps:
1. Basic output.
The Console class provides two output methods: Write and
WriteLine. There are many overloaded versions of each method
allowing them to handle all the required types. For example,
there are versions of both methods that take an int argument,
SkillAssure Page 21
C# Programming
other versions that take a double, versions that takes a string, etc.
The difference between Write and WriteLine is that WriteLine
automatically adds a line terminator to the output. Experiment
with both Write and WriteLine. Print literal values and variables of
several different types. A good set of types might be string, int,
double, and char. It might also be interesting to see how a bool
value is printed. Note that there is a version of WriteLine that
takes no arguments and simply prints a line terminator. This
might be useful to visually separate the output of your program.
2. Formatted output.
There are versions of Write and WriteLine that take a format string
followed by a variable number of other arguments. The format
string can contain placeholders which consist of an integer inside
curly braces. The integer corresponds to the position of one of
the additional parameters, so {0} represents the first parameter
after the format string, {1} is the second, {2} the third, and so
on. WriteLine replaces the placeholder with the value of the
specified parameter and prints the resulting string. For example,
the following can be used to embed the value of the variable a in
the output string:
Console.WriteLine("The value of a is {0}", a);
Experiment with this version of WriteLine. It might be interesting
to print several variables at once.
3. Input.
The Console class offers two input methods: Read and ReadLine.
Read returns a single character and ReadLine an entire line of
input. For our purposes the easiest approach is to ask our users
to enter one input value per line so we can simply assume each
call to ReadLine will get one input value. The ReadLine method
returns the next input line as a string. For example:
string s = Console.ReadLine();
We can then keep the value as a string or convert it to another
type, as in:
int i = Convert.ToInt32(s);
double d = Convert.ToDouble(s);
Experiment with the Convert methods by reading a few strings
and converting to appropriate types.
SkillAssure Page 22
C# Programming
Steps:
Part 3- Switch
Write a program to implement a very simple calculator. The calculator
will read the operation (+, -, *, /) and the two operands, perform the
operation, and output the result.
Steps:
1. Read the operation from the user using ReadLine. The operation
can remain as a string or you can convert it into a single
character using Convert.ToChar.
SkillAssure Page 23
C# Programming
2. Read the two operands using ReadLine. The user will need to
enter each on a separate line. Convert the strings into doubles.
3. Use a switch statement to determine the operation. Perform the
operation and output the result.
4. Use the default case of the switch to notify the user if they enter
an invalid operation.
Part 4- Array
Write a program to find the minimum value contained in an array.
Steps:
SkillAssure Page 24
C# Programming
Steps:
SkillAssure Page 25
C# Programming
Steps:
SkillAssure Page 26
C# Programming
02-Class
Goals:
Overview
C# code is primarily organized into classes. A class contains both the data and operations
needed to implement a type. For example, if a drawing program needed the concept of
'rectangle' then all the code needed to implement the rectangle type would be collected
into a Rectangle class. Grouping code in this way is not only clean and organized, it also
makes it easy to locate the appropriate code for maintenance, extension, or debugging.
Class
A class represents a concept in the application domain. Programmers working on a
graphics program might have classes such as Circle, Rectangle, and Line. A team
working on a financial application would have classes such as Stock, Bond, and
Property. In C#, a class is the primary way to create a user defined data type.
A class is defined using the keyword class, the name of the class, and the body of the
class enclosed in curly braces.
SkillAssure Page 27
C# Programming
Fields
A class can contain fields to store data. Fields are defined using the type name and the
field name. Fields defined in this way are called "instance fields" or "instance variables"
since each instance of the class gets it own copy. Note that fields are defined at class
scope; that is, they are not defined inside a method.
Object creation
Objects of class type are created using the new operator. new allocates memory for all the
instance fields of the class.
Member access
The dot operator is used to access class members. Instance members must be accessed
using an object so the syntax is typically object.member.
SkillAssure Page 28
C# Programming
Methods
The operations supported by a class are defined using methods. For example, a rectangle
type might support operations such as Draw, Move, Area, etc. while a stock type would
offer Buy, Sell, SetPrice, etc. In C#, methods must be placed inside a class. A method
is defined by specifying the return type, name, parameter list, and body. A return type of
void is used for methods that do not return a value. Methods defined in this way are
called "instance methods" since they must be invoked on an object of the class.
The set of methods offered by a stock class might look something like the following.
SkillAssure Page 29
C# Programming
Methods are invoked by applying the dot operator to an object, selecting the desired
method by name, and enclosing the arguments in parentheses.
Methods typically need access to the fields of the class. For example, the rectangle Area
method must access the width and height of the rectangle, and the stock Buy method
needs to modify the number of shares owned. To understand how methods are able to
access needed fields it helps to examine the concept of the "current object". Suppose
there two stock objects in existence: ibm and sun as shown below.
Each time an instance method is invoked, a particular object must be specified. For
example, a method call such as ibm.Buy(...) would manipulate the ibm object whils
sun.Sell(...) would access sun. Thus in each method call there is the concept of the
"current object"; that is, the object on which the method was invoked. The notion of the
current object is formalized by the keyword this. When a method is invoked, the
runtime arranges for this to represent the object on which the method was called. this
exists only for the duration of the method call and is only accessible from inside the
method.
Method implementations use this as a handle to get access to the members of the current
SkillAssure Page 30
C# Programming
object.
In the previous example, the method parameter and a field were both named shares.
Because of the ambiguity, use of this was required in order to access the field. If this
were omitted, the code would have compiled; however, both uses of the name shares
would have referred to the parameter and the field would not have been updated. In
situations where there is no ambiguity, this can be safely omitted and the correct class
member will still be accessed.
Methods may return a value. To return a value, the method declaration must specify the
type of data returned and the method implementation must use the keyword return to
send the data out of the method. For example, the stock class might supply a method such
as Value which computes and returns the current value of the stock.
SkillAssure Page 31
C# Programming
The entire implementation of the stock class is shown below for reference.
SkillAssure Page 32
C# Programming
Method overloading
It is legal for a class to offer more than one method with the same name as long as the
parameter lists are different. For example, the stock class could offer multiple versions of
the Buy method that perform slightly different operations. The compiler examines the
client code and calls the correct version based on the type and amount of data passed.
SkillAssure Page 33
C# Programming
Parameter passing
There are three primary ways to pass parameters to methods: value, ref, or out.
A value parameter can be thought of as "in only"; that is, the data passed is copied into
the method and the method then operates on the local copy. Any changes made by the
method modify only the local copy and are lost when the method ends. Pass by value is
the default parameter passing mechanism so there is no keyword needed to specify its
use.
An out parameter is "out only"; that is, no data is passed into the method but an
assignment to the parameter is visible in the client code. The keyword out must be placed
on both the method definition and the method call to create an out parameter.
SkillAssure Page 34
C# Programming
A ref parameter is "in and out"; that is, the parameter is just a "handle" or "reference" to
the actual argument. The value is available for reading and/or writing and any changes
made inside the method are visible in the outside world. The keyword ref must be placed
on both the method definition and the method call to create a ref parameter.
Member access
C# provides the ability to control access to class members. The three most common
access levels are public, private, and protected. We will cover public and private
here and discuss protected later during the module on inheritance.
The class designer can choose to limit the usage of a field or method by adding the
keyword private to its declaration. private members can only be accessed from inside
that class. Consider the following implementation of a stock class where the fields have
been made private. Note how the methods inside the stock class are able to access the
private fields while the client code outside the stock class cannot.
SkillAssure Page 35
C# Programming
Private is the default access level, so the following two declarations are equivalent.
To allow client code to access a field or method, the member can be made public by
adding the keyword public to its declaration.
SkillAssure Page 36
C# Programming
The public/private split allows a class to be divided into interface and implementation.
The implementation is kept private while the interface is made public. Client code can
only access the public interface and is prevented from directly accessing the
implementation. This concept is often described using the terms 'encapsulation' and
'information hiding'. The most commonly cited advantage of this coding style is that the
class designer is free to modify the implementation at any time without breaking any
client code. This is possible since the client code cannot access the implementation
directly anyway so changes have no impact.
Naming conventions
Some naming conventions have been adopted by C# developers in order to increase the
uniformity of code written by different programmers in different organizations. Most
names use the "intercaps" convention where the first letter of each word is capitalized.
SkillAssure Page 37
C# Programming
SkillAssure Page 38
C# Programming
Overview
A class is the primary unit of code in C#. A class defines a new type
that models some aspect of the 'real' world. In this activity, we will
write code that demonstrates a few of the most common coding
patterns for classes. We will also explore the various ways parameters
can be passed to methods.
Part 1- Point
Here we will implement a class to model a two dimensional point. The
point class will provide a good example of the most common type of
class: instance fields store the state of an object and methods
implement the behavior of the class by manipulating and reporting the
state.
Steps:
1. Create a new project in Visual Studio .NET for the point exercise.
2. Create a class called Point to represent a two dimensional point.
3. class Point
4. {
5. ...
}
6. Add two instance fields to store the state of the point. The fields
are private since we only want code in the Point class to access
them. Users will be forced to go through the public interface that
SkillAssure Page 39
C# Programming
9. Implement public get and set "access methods" for the x and y
coordinates. The practice of making the data private and
providing public access methods is relatively common in object
oriented programming.
10. public void SetX(int x) ...
11. public void SetY(int y) ...
12.
13. public int GetX() ...
public int GetY() ...
Recall that the formula for the distance between points is:
You can use the Math.Sqrt method to compute the square root.
SkillAssure Page 40
C# Programming
17. Code a Driver class with a Main method to test your work.
Steps:
Create a class called Driver and add a Main method to it. Create a
ParameterTest object and call the Value method and verify that it
behaves as expected.
SkillAssure Page 41
C# Programming
void Test()
{
Process("data"); // zero ints
Process("data", 1); // one int
Process("data", 1, 2, 3); // three ints
Process("data", new int[] {1, 2, 3, 4}); // explicit array ok too
}
Steps:
1. Write a method called Sum that uses the params technique to take
a variable number of integers, compute their sum, and return
the result.
SkillAssure Page 42
C# Programming
Steps:
1. Create a new project in Visual Studio .NET for the robot exercise.
2. Create a class called Robot to represent a robot.
3. class Robot
4. {
5. ...
}
6. Add four instance fields to store the state of the robot. The fields
are private since we only want code in the Robot class to access
them. Users will be forced to go through the public interface that
we will add shortly.
7. private string name;
8. private int x;
9. private int y;
private int direction;
SkillAssure Page 43
C# Programming
{
string[] names = { "North", "East", "South", "West" };
return names[direction];
}
SkillAssure Page 44
C# Programming
27. Next we add the ability for the robot to turn either left or
right. Turning the robot modifies the direction the robot is facing.
For example, if the robot is currently facing north and turns left,
the robot would then be facing west.
return names[direction];
}
Add the following field to the robot simulation. Notice how the
generator uses the current time as the seed to ensure a different
sequence of random numbers each time the program is run.
Add a method called Step to the Robot class which makes use of
the random number generator. The generator offers a method
named Next which takes an int parameter (called the bound) and
returns a random number between 0 and bound-1. The step
method should choose randomly between moving and turning. A
reasonable probability distribution might be a 50% chance for
Move(), a 25% chance for Turn(Left), and a 25% chance for
SkillAssure Page 45
C# Programming
Turn(Right).
Notice that calling Next(2) on the random number
generator has an even probability of returning 0 or 1 thus
providing a convenient way to create a 50/50 chance. Call Step
from Main to test your work.
29. Here we have the robot search for a goal. The goal is
simply an (x,y) coordinate pair. To keep the simulation from
running indefinitely we will define a "world size" and restrict the
robot's movement to coordinates in the valid range. Add the
following constant to the Robot class.
int goalx = 5;
int goaly = 5;
SkillAssure Page 46
C# Programming
Repeatedly call Step on the robot until it reaches the goal. Use
the At method to determine if the robot is at the goal. You may
also want to create several robots and have them compete to
find the goal.
SkillAssure Page 47
C# Programming
03-Initialization
Goals:
Overview
Here we discuss the various options available to initialize the instance fields of a class.
Default values
Instance fields of a class are set to default values when an object is created. The default
value depends on the type of the field.
Numeric fields such as int, float, double, etc. are set to zero.
Characters fields are set to the null character (often written '\x0000').
References are set to null. We will discuss references in the module on reference types.
SkillAssure Page 48
C# Programming
Variable initializer
Instance fields can be initialized inline, that is, at the point of declaration. The official
name for this technique is 'variable initializer'.
SkillAssure Page 49
C# Programming
Constructor
The most powerful initialization technique is to provide a constructor. A constructor is a
special method that is called automatically each time an object is created. The definition
of a constructor is similar to a regular method except for two small differences: the name
of the constructor must be the same as the name of the enclosing class and no return type
is allowed. The client code does not invoke the constructor directly, instead the client
creates objects using the new operator and passes any needed arguments inside the
parentheses after the type name. The constructor is called automatically as part of the
object creation and initialization process.
SkillAssure Page 50
C# Programming
Constructor overloading
A class can supply multiple constructors with different parameter lists. It is even possible
to write a constructor that takes no arguments. The no argument constructor is sometimes
called the 'default constructor'. The client code chooses the constructor to call by passing
the appropriate parameters when an object is created.
SkillAssure Page 51
C# Programming
Constructor initializer
One constructor can call another constructor in the same class. The syntax is :this(...)
placed after the constructor signature but before the constructor body. The number and
type of arguments passed determine which constructor version gets called. This technique
is called a "constructor initializer". The main benefit is that it allows common
SkillAssure Page 52
C# Programming
initialization code to be placed in one constructor and any other constructors can simply
make a call rather than repeating the code.
Initialization order
The various initialization options are executed in a well defined order. First, all fields are
set to their default values. Second, the variable initializers are executed in order of their
declaration. Finally, the constructor is run.
SkillAssure Page 53
C# Programming
Overview
Steps:
SkillAssure Page 54
C# Programming
constructor for the Book class that takes three arguments: the
title, author, and flag. To verify that your code is working
correctly add a WriteLine statement to the constructor that prints
the values being used for the initialization. To test your work,
create a class called BookTest containing a Main method. In the
Main method create a book object and pass arguments to the
constructor.
2. class Book
3. {
4. private string title;
5. private string author;
6. private bool available;
7.
8. public Book(string title, string author, bool available)
9. {
10. ...
11. }
12. ...
}
13. Add a second constructor to Book that takes only the title
and author. Set the flag to true. Modify the Main method to create
a Book using this new constructor.
14. class Book
15. {
16. public Book(string title, string author)
17. {
18. ...
19. }
20. ...
}
21. Add a third constructor to Book that takes only the title. Set
the author to "anonymous" and the flag to true. Modify the Main
method to create a Book using this new constructor.
22. class Book
23. {
24. public Book(string title)
25. {
26. ...
27. }
28. ...
}
SkillAssure Page 55
C# Programming
31. {
32. public Book()
33. {
34. ...
35. }
36. ...
}
SkillAssure Page 56
C# Programming
04-Static
Goals:
Overview
The keyword static can be applied to fields and methods. Static fields implement the
idea of data that is shared by multiple clients. Static methods are typically either access
methods to static data or utility methods that do not need to maintain state across calls.
Static field
Regular fields are often called "instance fields" because each instance of the class gets
their own copy. Consider the following Item class containing three instance fields and
notice how the objects look in memory.
SkillAssure Page 57
C# Programming
By contrast, there is only one copy of each static field and the single copy is shared by
all objects. Consider the new version of the Item class shown below. The Revenue field
has been made static; therefore, there is a single copy generated that is separate from all
Item objects.
SkillAssure Page 58
C# Programming
Client code that needs to use a static field must access it using the class name.
Static fields are used to implement the concept of "shared resource" since the single copy
can be shared by multiple clients. In other languages, this role is often filled by global
variables. The type designer can choose the access level for a static field: private static
SkillAssure Page 59
C# Programming
fields are shared only by instances of that type while public static fields are shared by all
clients.
The memory for static fields is allocated by the CLR when the class is initialized.
Conceptually, you can think of this as occurring "at program startup"; however, the exact
timing is up to the CLR and in many cases this setup will occur later. The key thing to
take away is that the memory for a static field will be available for use even if no objects
of that type have been created.
Numeric fields such as int, float, double, etc. are set to zero.
Characters fields are set to the null character (often written '\x0000').
References are set to null. We will discuss references in the module on reference types.
SkillAssure Page 60
C# Programming
Static constructor
The most powerful initialization technique is to provide a static constructor. The static
constructor is defined using the keyword static, the class name, and an empty argument
list. No return type, access modifier, or parameters are allowed. The CLR invokes the
static constructor automatically when the class is initialized. The exact timing of the
invocation is not guaranteed, but it will be run after the static variable initializers have
been executed.
Static method
A method can be made static by adding the keyword static to its declaration.
SkillAssure Page 61
C# Programming
Static methods are not as powerful as instance methods. In particular, static methods can
only access the static parts of their type and are not allowed to access any instance fields
or methods. This limitation means static methods are not quite as useful as instance
methods; however, static methods do still have their place. Static methods are typically
used for 'utility' methods that do not need to maintain state across calls. Several good
examples of this application can be seen in the Math and Convert classes from the .NET
Framework Class Library. A less common application of static methods is as accessors or
manipulators of some 'global' object. The classic example of this is the library Console
class where the read and write methods manipulate the console attached to the executing
process.
SkillAssure Page 62
C# Programming
SkillAssure Page 63
C# Programming
Overview
C# offers three options for initializing static fields and provides well
defined rules for the execution order of the various options. The first
initialization option is to simply accept their default values. The default
value depends on the type: numeric types get set to zero, Booleans to
false, characters to the null character, and references to null. The
second initialization option is to assign an initial value at the point of
definition. The third initialization option is the use of a static
constructor. All the static initialization code is run only once since their
is only one copy of the static data.
Methods can be static. Code inside a static method can only access the
static parts of the class, instance fields and methods are not available.
Both static fields and methods must be accessed only through the
classname, never through an instance. This rule is sensible because
the statics are associated with the class as a whole and not with a
particular instance.
SkillAssure Page 64
C# Programming
Steps:
SkillAssure Page 65
C# Programming
Steps:
1. Create a class called MathUtils. Add two public static methods Min
and Max. The methods should take two int parameters, perform
their operation, and return the result.
2. Create a driver class with a Main method. Call the Min and Max
methods to test your work.
This interface is not ideal for users who would like one generator that
can easily be shared by their entire application. To support this model
of programming we will create a class that wraps a Random object with
a static method interface.
Steps:
SkillAssure Page 66
C# Programming
17. Add a Driver class with a Main method to test your work.
SkillAssure Page 67
C# Programming
05-Reference Types
Goals:
Overview
Arrays and classes are reference types. This means that two things are involved when
dealing with instances: a reference and the object itself. A reference is used as a handle to
manipulate the object. This organization has a number of implications for how reference
types behave during operations such as assignment and parameter passing.
Reference declaration
A variable declaration of a class type yields only a reference, it does not create an object
of that class. References are often called "handles" or "pointers" since they are used to
manipulate or point at objects.
SkillAssure Page 68
C# Programming
Object creation
Objects are created using the new operator and reference variables are used to access
them. To see this, it is instructive to look at some code where the two steps are done
separately. In the example shown below, first a few Stock references are created and then
objects are allocated using new. Notice how the assignment operator is used to make the
reference variables refer to the newly created objects.
It is also possible to do both the reference declaration and object creation in a single line
of code as shown below.
Reference assignment
Assigning references copies only the reference, it does not copy the data inside the object.
To see this, consider the following code which shows two references each referring to a
separate object.
SkillAssure Page 69
C# Programming
Now consider performing an assignment such as "ibm = sun;". Because ibm and sun
are both references, the assignment copies only the reference. In other words, ibm now
refers to the same object as sun. The result of such an assignment is shown below. Note
that the object ibm used to refer to is no longer accessible since there are no references to
it. We will discuss what happens to such objects later in this module when we talk about
memory management.
null
The link between a reference and an object can be broken by setting the reference to
null. For example, in the code below, the reference ibm is initially referring to an object.
After the assignment, the link has been severed and the reference is no longer referring to
any object.
Attempting to use a null reference is an error. The error is detected at runtime by the
CLR. The CLR stops the operation and throws a NullReferenceException to notify the
program of what went wrong. The try/catch exception handling syntax can be used to
trap and handle the exception. We will discuss exceptions in greater detail in a later
module.
SkillAssure Page 70
C# Programming
It is common to use a reference type as a field. For example, a parent might hold a
reference to each child, a highway would need to keep references to each city it connects,
and a circle must have a reference to its center point. Recall that each field gets assigned
a default value based on its type: numeric fields get set to zero, Boolean fields are set to
false, etc. In a similar fashion, reference fields are set to null when the object containing
them is allocated. The code below shows the case for "circle with center point".
SkillAssure Page 71
C# Programming
Reference parameters
When reference types are passed as parameters, only the reference is passed and the
object is not copied. The figure below illustrates the situation.
SkillAssure Page 72
C# Programming
Passing a reference has two important implications. First, reference parameters can be
quite efficient since only a reference is passed and there is no need to copy all the data
inside the object. Second, because the method holds a reference to the original object, any
changes it makes modify that object. The effects of these changes will be visible to the
client code after the method returns.
Array
Arrays are reference types just like class types so they are implemented as
reference/object pairs. An array declaration creates only a reference. The associated array
object must be created using the new operator. The figure below illustrates the two steps
needed to create an array.
It is common to declare the reference and create the array in a single line of code. This
style is a bit cleaner since the reference is created and then immediately bound to an array
object. The figure below shows the two steps being performed together.
Because arrays are references type, they behave just like class types in operations such as
assignment and parameter passing. For example, assigning one array to another copies
the reference and not the data inside the array. This situation is shown below.
SkillAssure Page 73
C# Programming
As a final variation on arrays, we will look at a slightly more complicated case: an array
of references. Creating an array of a class type with an expression such as " Stock[] s =
new Stock[3];" creates an array of Stock references with each reference initially set to
null. It is important to emphasize that at this point we have only an array of references,
there are no Stock objects in existence yet. As a second step, we need to create the Stock
objects and use the array elements to refer to them. The situation is illustrated below.
Memory management
In the last few modules we have allocated quite a few objects using new but have not
worried about where the memory comes from nor how it gets reclaimed when we have
finished with the objects. Happily, there is not much for the programmer to do since C#
and .NET automatically recycle the memory of unused objects. The situation is similar to
what many children experience during their earliest years. During play time, the child
removes a toy from the toy box and plays with it for a while. When they grow bored they
SkillAssure Page 74
C# Programming
simply set the toy down where they are and return to the toy box for another one. After a
while, there are discarded toys strewn all around the house. At this point, a responsible
adult comes along and collects all the toys and returns them to the toy box where they are
available for reuse.
More technically, memory for objects of reference types comes from an area of memory
called the "managed heap". The .NET heap is called "managed" since the CLR takes
responsibility for managing the allocation and reclamation of the memory. Operator new
allocates memory for reference types from the managed heap and the "garbage collector"
reclaims the memory occupied by unused objects. An object becomes unused when it is
no longer reachable from the program. For example, if the only reference to an object is a
local variable, then at the end of the method the local variable goes out of scope and the
reference disappears. The object is now unused and is eligible to be reclaimed by the
garbage collector.
The garbage collector has quite a difficult job since it must determine which objects are
still active and which objects are no longer in use by the program. To do this, the garbage
collector examines all the "root" references such as live local variables and static fields. It
follows these references and marks any objects it finds as being still active. After all live
objects have been marked, the garbage collector assumes all other objects are no longer
in use and recycles the memory they occupy. The basic idea is illustrated in the figure
below.
SkillAssure Page 75
C# Programming
The CLR controls the timing of garbage collection. The common advice is that the CLR
and the garbage collector are intelligent enough to do the job without any interference
from the programmer. However, the GC.Collect method can be used to force the
garbage collector to run if it becomes necessary.
Despite the sophisticated support for memory management offered by the CLR, it is still
possible for a program to allocate so many objects that it runs out of available memory.
When this happens, the CLR will notify the program by throwing an
OutOfMemoryException. The program can use the exception handling try/catch
construct to trap and handle the exception.
SkillAssure Page 76
C# Programming
Overview
Part 1- Hive
We begin the simulation with a class to represent the beehive. The
main job of the hive is to contain the cells in which the nectar ferments
and becomes honey.
Steps:
1. Create a class Hive. Add a Cells field to represent the cells: the
field should be a reference to an array of int and should be public
to allow the access by the bees. Note that the field is just an
array reference, there is not array at this point.
2. class Hive
3. {
4. ...
}
5. Define two public integer constants Empty and Full to indicate the
state of each cell. Note the use of the keyword const to create
symbolic constants whose value cannot be changed.
6. class Hive
7. {
8. public const int Empty = 0;
SkillAssure Page 77
C# Programming
Part 2- Bee
Next we need a class to represent the bee. The bee's job is to go out
into the countryside, gather nectar, return to the hive, and deposit the
nectar into an empty cell.
steps:
10. The bee's job is to search for nectar, gather the nectar,
return to the hive, and deposit the nectar in one of the cells.
Thus, the bee is always in one of four states: searching,
SkillAssure Page 78
C# Programming
SkillAssure Page 79
C# Programming
30.
31. if (random.Next(5) == 0)
32. state = Gathering;
33.
34. break;
35. }
36. case Gathering:
37. {
38. Console.Write("G");
39.
40. state = Returning;
41.
42. break;
43. }
44. case Returning:
45. {
46. Console.Write("R");
47.
48. state = Depositing;
49.
50. break;
51. }
52. case Depositing:
53. {
54. Console.Write("D");
55.
56. //
57. // choose random cell to deposit
58. //
59. int i = random.Next(hive.Cells.Length);
60.
61. if (hive.Cells[i] == Hive.Empty)
62. {
63. hive.Cells[i] = Hive.Full;
64. state = Searching;
65. }
66.
67. break;
68. }
69. }
70. }
71. ...
}
Part 3- Keeper
The bee keeper will run the simulation. The keeper will create the hive,
create the bees, call the bee's Work method, and periodically collect the
honey from the hive.
SkillAssure Page 80
C# Programming
Steps:
5. Add a method to Keeper to collect all the nectar from a hive. The
method takes a Hive parameter, counts the number of filled cells
inside the hive, sets all the cells to empty, and returns the count
of the number of filled cells. Notice that the parameter is a
reference so any changes made to the parameter will be visible
back in the calling code.
6. class Keeper
7. {
8. public int Collect(Hive hive)
9. {
10. ...
11. }
12. ...
}
SkillAssure Page 81
C# Programming
31. ...
32. }
}
33. Add the following control code to the Run method. Notice
how the bees work for a while and then the keeper collects the
honey and the cycle begins again.
34. class Keeper
35. {
36. public void Run()
37. {
38. ...
39. while (true)
40. {
41. for (int i = 0; i < 20; i++)
42. {
43. for (int j = 0; j < bees.Length; j++)
44. bees[j].Work();
45. }
46.
47. Console.WriteLine("collect {0}", Collect(hive));
48. }
49. }
}
Part 4- Driver
Here we add a simple driver class to start the simulation.
Steps:
SkillAssure Page 82
C# Programming
06-Properties
Goals:
Overview
Properties model the traits of an object or a class. C# provides special syntax to define
and access properties. Programmers are encouraged to use the special syntax to
implement properties because it results in clean client code and makes the intent of the
class designer explicit to both human readers and automated tools.
Motivation
A class is typically a software model of a real world thing. The thing being modeled has
certain properties; for example, a person has a name and an age, a stock has a symbol and
a price, a circle has a radius, and so on. Other common terms for the same concept are
trait, characteristic, or quality. A property can be implemented simply by defining a field
to store the current value as shown below.
Client code often needs access to the property in order to read and/or write the value. The
simplest way to achieve this is to make the fields public. Public data makes the resulting
client code clean and simple. A write operation is accomplished by using the assignment
operator to load a new value into the field and a read operation is a normal field access.
SkillAssure Page 83
C# Programming
Despite its simplicity, public data is not used very often in practice since it has some
disadvantages from a design point of view. One flaw is that there is no way for the class
to perform error checking on the data being loaded into the fields. For example, a client
could give a person a negative age or an empty name and the class would not be able to
detect the error. Another problem is that there is no way to implement a read-only
property since public data is implicitly readable and writeable by all external code. The
common way to avoid the problems associated with public data is to make the data
private and provide public read and write access methods. By convention, the access
methods are typically named GetXXX and SetXXX where XXX is the name of the property
in question. The private data / public access method pattern gives the class designer much
more control than public data since the client no longer has direct access to the fields and
must go through the access methods. This allows the access methods to perform error
checking when new values are loaded. Any attempt to load invalid data can be rejected.
A simple implementation without any error checking is shown below. It would be easy to
extend this solution to include any needed if statements to perform any desired
validation. It would also be trivial to make the property read-only simply by omitting the
Set method.
SkillAssure Page 84
C# Programming
The client code for properties implemented with access methods is not quite as clean as
the public data case. The write operation for a public field uses an expression such as
object.property = value; where the left hand side of the assignment operator is the
property being modified and the right hand side is the new value. This syntax is intuitive
since human readers generally find it easy to identify a line of code containing an
assignment operator as a write operation. However, with an access method, the write
operation is performed by calling the set method and passing the new value in as an
argument. This syntax is arguably more difficult to visually identify as a write operation
since the assignment operator is not used. A similar case can be made for a read
operation. The read operation for a public field is typically a noun phrase such as
object.property. This is intuitive since it agrees with the idea that a property should be
a noun because it represents some trait of the type being modeled. In contrast, the read
operation using a typical access method is a verb phrase such as object.GetXXX. This
conflict can lead to client code that is less intuitive than when public data is used.
SkillAssure Page 85
C# Programming
C# properties
The two goals of access control and clean client code are somewhat in conflict. Public
data yields simple and intuitive client code but does not give the class designer enough
control. Private data / public access methods provide the ability to do error checking and
implement read-only properties but leads to less readable client code. To solve this
dilemma, C# offers special syntax to implement properties that combines the advantages
of both conventional techniques.
Recall that a property definition typically has three components: an instance variable for
data storage, a write access method, and a read access method. A C# property definition
provides only the accessors and does not include any needed data storage. The most
common case then, is to define a field to store the property data. The common wisdom
suggests choosing a name for the field that is similar to the name of the property; for
example, a variable called name for a property called Name (the difference in case is
sufficient to make them distinct in C#).
SkillAssure Page 86
C# Programming
The implementation of the accessors is perhaps the most interesting part of the syntax.
The write accessor for a property is named set. The definition syntax is reminiscent of a
traditional write access method but makes a few assumptions that reduce the amount of
code required. Basically, the entire first line of a traditional write method is missing; that
is, the return type, method name, and parameter list are not specified. This simplification
makes sense because traditional write access methods all have the same form: a single
argument whose type matches the type of the property and void return type. The main
consequence of this minimal syntax is that the programmer does not have control over the
name of the parameter; instead, the C# design team chose the name value for the implicit
parameter. Other than the lack of the method header, the set accessor is much like a
method. The body can contain an arbitrary set of statements to be run whenever the
accessor is invoked.
The read access method for a property is named get. A traditional read access method
takes no arguments and has a return type that matches the type of the property. Again,
since this is so common, the property syntax does not contain these details explicitly. The
body of the accessor specifies the statements to be run when the get accessor is used. The
get accessor has an implied return type that matches the type of the property so the body
is required to return an appropriate value or it is a compile time error. A complete
property implementation is shown below.
SkillAssure Page 87
C# Programming
The client code for C# properties is identical to accessing a public field. The compiler
analyzes how the property is being used and invokes the appropriate accessor
automatically. Specifically, the set accessor is invoked when the property is being used
on the left hand side of the assignment operator while the get accessor is used in all other
cases. Some sample client code is shown below.
The C# property syntax gives much of the power of the private data / public access
methods pattern while retaining all the benefits of a public data implementation. In
particular, a property can be read-only, write-only, or read-write. This is accomplished
simply by providing or omitting the set or get accessors as desired. C# properties also
give the class designer the ability to add any needed error checking code to the set and
get implementations.
SkillAssure Page 88
C# Programming
Static property
A property that applies to an entire class rather than to an instance can be created by
applying the keyword static to the property definition as shown below. As with a static
method, inside the accessors of a static property the instance methods and instance fields
of the class are not accessible. That is, only the static parts of the class are usable and any
attempt to access non-static members is a compile time error.
A static property must be accessed through the type name. It is a compile time error to
attempt access using an instance of the class.
Benefits
The decision to support properties as a first class language feature is an interesting one.
Adding the feature made the language larger and thus more difficult to learn; however,
SkillAssure Page 89
C# Programming
there are several good arguments as to why the special property syntax is worthwhile.
First, the client code using the property syntax is much cleaner than using traditional
access methods. Second, it makes the code more self documenting since otherwise,
maintenance programmers would need to deduce the existence of a property from the
presence of a pair of set/get methods. Finally, the property syntax allows property
information to be carried through compilation and into the resulting IL code. The IL can
be interrogated programmatically using the System.Reflection API and the properties
of a class determined. Visual Studio .NET makes heavy use of this feature to display
Intellisense information and to populate properties windows for GUI components in the
design tools.
SkillAssure Page 90
C# Programming
Overview
Part 1- Set-up
This part sets up the example we will use to experiment with
properties.
Steps:
SkillAssure Page 91
C# Programming
Steps:
Steps:
SkillAssure Page 92
C# Programming
Steps:
Steps:
SkillAssure Page 93
C# Programming
5.
6. public static double InterestRate { ... } // property
7.
8. ...
}
SkillAssure Page 94
C# Programming
07-Indexers
Goals:
Overview
An indexer provides a way for a class to support array-like indexing using the square
brackets operator [ ]. This works well for any class that represents a collection of data
keyed by index.
Motivation
Some classes can be thought of as a collection of other objects. A department at a
company might contain all of the employees that work in that department. An investment
portfolio would contain the current set of stocks owned. A polygon could store all the
points that make up the shape. In each case, it might make sense to allow client code to
access elements of the collection by giving a unique index. For example, an employee
record might be requested from the department by specifying the employee's id number
or name. A particular stock might be extracted from an investment portfolio by giving its
ticker symbol. The points of a polygon might be indexed by an integer representing the
order in which the points are connected.
Each collection class would need some private data structure behind the scenes in which
to store the collection of data. The most common case would be a simple array but more
sophisticated implementations are possible as well. To allow client code to access the
elements of the collection, public access methods would likely be provided. The set
access method would require two parameters: the index and the data. The get access
method would take the index as an argument and return the corresponding value. An
implementation for a simple polygon class is shown below.
SkillAssure Page 95
C# Programming
Clients of the polygon class use the get and set methods for access to the collection of
points.
The private data / public access methods pattern described above works reasonably well.
The clients interact with the access methods while the data store is hidden behind the
scenes. As an alternative, the class designer can supply an indexer instead of traditional
get and set methods. The special indexer syntax allows the square bracket operator to be
used for access instead of regular get and set methods calls. The primary advantage is that
the indexer client code is more concise than analogous code using traditional access
methods.
Basic indexer
Recall that there are typically three components to the implementation of any data access
pattern: the get method, the set method, and the field used to store the information.
Indexers provide a fancy way of writing the get and set access methods but do not give
any help with data storage. Therefore, lurking behind the scenes of any indexer
implementation will be some sort of data store, typically just a private field. The polygon
SkillAssure Page 96
C# Programming
example would likely use an array of points to store the data as shown below.
Indexers provide a special way to write get and set access methods that are invoked using
the square brackets operator rather than traditional method call syntax. The indexer
definition syntax is a bit obscure and fairly lengthy. First, the access level such as public
or private is specified. Next comes the type of data being indexed; for example, our
polygon sample would specify Point while the investment portfolio example would use
Stock. After the type comes the keyword this; there is no option here, the keyword must
be included in the definition of every indexer. Next, placed inside square brackets, comes
the type and name of the index. Lastly comes the body of the indexer with the get and set
access methods defined inside. The syntax is summarized in the figure below.
The get and set accessors are defined inside the body of the indexer. The definition of the
accessors is simplified by the fact that much of the interface information is already
captured in the indexer declaration. In particular, the indexer declaration contains the type
of data being indexed and the type and name of the index itself.
SkillAssure Page 97
C# Programming
Recall that a traditional set method for indexed data would take two arguments: the index
and the data. The implementation of a set accessor does not specify these parameters
explicitly; in fact, the method header is completely omitted and much of the parameter
information is taken from the indexer declaration. The name of the index is determined
by the name in the indexer declaration. The name of the data parameter is the special
symbol value. The term value is imposed on the programmer by the C# language
designers and cannot be changed. The example below shows the definition of the set
accessor for the polygon example.
A traditional get method would take only the index as an argument and return the
corresponding data. The implementation of the get accessor dispenses with this detail and
specifies only the method body preceded by the keyword get. The get accessor must
SkillAssure Page 98
C# Programming
return an object of the type being indexed as expected for any get access method.
The client code that makes use of an indexer is clean and simple. The client applies the
square brackets operator to an object of the class and the compiler invokes the
appropriate accessor automatically. When the indexer is used on the left hand side of the
assignment operator the set accessor is used. In all other cases the get accessor is
invoked.
Index type
Most indexers have a simple integer index; however, the index can actually be of any
type. The code below illustrates this by showing a department class that uses an indexer
to allow employees to be accessed by name. The type of the index in this case is a string.
SkillAssure Page 99
C# Programming
Index number
Indexers support multiple parameters. The indices are placed inside the square brackets
and separated by commas. The code below shows a matrix class with an indexer that
requires two parameters: the row and column.
Indexer overloading
A class may offer multiple indexers as long as each version has a unique set of indices.
The example below shows a matrix class that offers two indexers. The first indexer
allows the user to pass a single index, the row number, to access an entire row of data.
The second indexer requires both a row and a column index and allows access to a single
element of the matrix
Overview
Steps:
Steps:
Steps:
08-Inheritance
Goals:
Introduce the syntax and semantics of inheritance.
Discuss the protected access level.
Show how to call base class methods and constructors.
Overview
Inheritance allows a 'derived' class to be created from an existing 'base' class. The new
class inherits the fields and methods of the base class. In addition, the new class can add
new methods, add new fields, and override existing methods.
Motivation
Inheritance provides a way to model the real world concept of
generalization/specialization. Consider the different types of people at a university
described in the following diagram.
The types at the top of the hierarchy are very general since everyone at the university is a
person. Proceeding down the hierarchy things become more specialized because some of
the people at the university are students and some are employees. Students and
employees have some behavior in common but differ in other ways. The parts they share
are captured in the base person class while their differences appear in the derived student
and employee classes. Similar comments apply lower in the hierarchy since some of the
students at the university are undergraduates and some are graduates. The common term
for this type of generalization/specialization is the "is-a" relationship. One might say "a
student is a person" or "a faculty member is a employee".
The example above intentionally uses types of people in order to demonstrate that the
concept of the is-a relationship is not exclusive to programming. Most humans naturally
create mental categories such as person, fruit, employee, shape, mammal, etc. to help
them deal with the complexity of the real world. The categories capture the elements that
all members of the group share. For example, all employees might have an id number and
make a certain salary, all shapes might have a color and take up a certain area when
drawn on a piece of paper, and so on. This organization makes the world a much simpler
place because an understanding of a category gives a lot of information about the
behavior of each member. In other words, if you understand a basic concept such as
apple, it will help you when you encounter a new member of the group such as Fuji or
Braeburn for the first time.
Inheritance gives developers a way to express the is-a relationship in their code. This is
especially important in object-oriented programming where the goal is to model the real
world accurately. If in the real world, the people at a university are categorized as shown
in the diagram above, then a software model of the university should be sure to capture
those relationships. The common wisdom then, says that whenever the is-a relationship
exists in the application domain, it should be modeled in software using inheritance.
At this point, the discussion of the meaning and benefits of inheritance has been mostly
theoretical. It might help to look at a more concrete advantage as well: elimination of
repeated code. Consider the two classes below and note how several lines of identical
code appears in both.
The disadvantages of repeated code are well known: extra work to program and extra
work to debug and maintain. In the following discussion, we will see how to rewrite the
above example so the repeated code is factored out into a base class called Person. The
Student and Employee classes will be derived from Person to obtain the functionality
they need without having to repeat the implementation of the shared code.
Basic syntax
A class specifies its base class by placing a colon and the base class name as part of its
declaration. The example code below shows a base class Person and two derived classed
Student and Employee. Note how the fields and methods that apply to both students and
employees appear in the base class while the specific members are placed in the derived
classes.
An inheritance hierarchy can be as deep as required. For example, a Faculty class could
be derived from Employee which, in turn, is derived from Person. The Faculty class
inherits the members of all ancestor classes; that is, it inherits from Employee and
Person.
Memory allocation
Creating a derived class object allocates memory for the fields declared in both the base
class and the derived class.
Base services
Public members of the base class are available to clients of the derived class. That is,
client code that is using an instance of the derived class can access any public fields or
call any public methods that are declared in the base class.
Single inheritance
C# offers only single inheritance which means that a class can have only one direct base
class. This restriction is actually imposed by the CLR so single inheritance is the rule for
all .NET languages. The designers of .NET acknowledge that multiple inheritance is
more powerful than single inheritance; however, they decided that the extra
complications and ambiguous situations that arise in the presence of multiple base classes
was not worth this extra power.
Protected access
There is a third access level for class members in addition to the more common public
and private. The protected access level grants access to derived classes but prevents
access from all other client code.
This preceding code has two ambiguities. The base class and derived class have an
accessible field with the same name and they contain a method with the same signature
(name and parameter list). To see how such situations might arise in practice, let's revisit
the Person and Student classes and consider how the development process might have
taken place over time.
Suppose the person class was created first. In most countries, each person is given an id
number by the government so it would make sense for person to have an id field and one
or more access methods as shown below.
Once the basic person class is complete, another team member might begin writing a
derived class such as Student.
At this point in the development process there are no ambiguities. Because everything is
working correctly, the designers of Person and Student decide to release their classes to
the rest of the development team. The other team members are happy because they have
been waiting for these classes in order to move their own parts of the project forward.
Client code such as the following now begins to be written.
Now suppose the Student class designer realizes that most universities assign each
student a unique id number. They return to the class and add an id field and one or more
access methods as shown below.
The addition of the SetId method to the Student class has resulted in an ambiguity since
Student now has two methods with the same signature. Consider the effect on the client
code: which method should be called now, the Person version or the Student version?
It turns out that the derived class method takes precedence over the base class method.
Therefore, the behavior of the client code will change, where previously the Person
version of SetId was invoked now the Student version will be called.
The C# design team considered this type of behavior change too important to ignore;
therefore, ambiguities between base and derived classes generate a warning from the
compiler. When the designer of the Student class rebuilds the code, they will see a
warning and be aware that the addition of the SetId method will change the behavior of
client code. At this point, the Student class designer has a couple of choices. Arguably,
the best decision would be to change the name of the Student version of the SetId
method to something that doesn't conflict with the inherited method. If this is not an
option, they must add the keyword new to the definition of the student SetId method to
acknowledge that the new method will hide the inherited one. Adding new to the method
definition will suppress the compiler warning. Note that the behavior of the client code
did still change, and the Student class designer should probably make an effort to notify
all the clients of the change. The point is that the change did not occur silently.
The keyword new may also need to be applied to fields whenever a derived class field
conflicts with a field inherited from its base class. In the Person and Student example,
new is not needed on the id field of Student even though Person defines a field with the
same name. This is because the person id field is private and so is not accessible to
Student and therefore there is no ambiguity.
If the base and derived class both have a method with the same signature, the derived
class must use the keyword base to get access to the base class version. If the keyword
base were omitted from the call, the derived class version would be invoked instead of
the base class version.
The base class would probably offer one or more constructors to do the required
initialization.
Derived classes inherit all the fields of the base class and add their own. When a derived
class object is created, both the derived class part of the object and the base class part
must be initialized.
The derived class constructor can invoke a base class constructor to initialize the base
class part of the object using the :base(...) syntax. The constructor arguments are
placed inside the parentheses as in a regular method call and the entire construct goes
after the method signature but before the open brace for the constructor body. At runtime,
the base class constructor will be executed first followed by the body of the derived class
constructor. The official name for this technique is a "constructor initializer".
Overview
Steps:
1. Asset class. Create a class named Asset and give it three private
fields: a string for the name, a double for the cost, and an int for
the year the asset was purchased.
2. class Asset
3. {
4. private string name;
5. private double cost;
6. private int yearPurchased;
7. ...
}
Asset. Give it three private fields: a string for the ticker symbol,
an int for the number of shares owned, and a double for the
price.
9. class Stock : Asset
10. {
11. private string symbol;
12. private int shares;
13. private double price;
14. ...
}
25. Art class. Create a class named Art and derive it from
Property. Give it two private fields: a string for the name of the
artist and an int for the year in which it was created.
26. class Art : Property
27. {
28. private string artist;
29. private int yearCreated;
30. ...
}
Part 2- Construction
Here we will practice with constructors in inheritance hierarchies.
Remember that derived classes are responsible for initializing their
own fields and those of their base class. Because of this, constructors
tends to get more parameters farther down the hierarchy.
Steps:
Steps:
8. ...
}
Steps:
3. {
4. public double ComputeValue()
5. {
6. ...
7. }
8. ...
}
Steps:
1. Add a public Print method to each class in the hierarchy. Use base
to allow the derived class versions to chain to the base class
versions. After the call to the base version finishes, the derived
versions can finish the job by printing the values of the fields in
the derived class. Remember to use new in the signature of the
derived class versions.
2. class Asset
3. {
4. public void Print()
5. {
6. ...
7. }
8. ...
}
09-Binding
Goals:
Overview
The most obvious advantages of inheritance are that it helps eliminate repeated code and
allows program structure to mirror the organization of the real world. When inheritance is
combined with reference compatibility and dynamic method binding things get even
more exciting. Clients can then write extremely generic client code that works correctly
for any type in the hierarchy. Dynamic binding ensures the correct code is executed for
the type of object being processed.
Type compatibility
C# inheritance models the "is-a" relationship so a derived class inherits all the behavior
of its base class. To explore the implications of this idea, we will return to the simple
person/student/employee inheritance hierarchy which models people at a university.
Suppose the base class person contains a public method named Birthday and some
supporting fields.
Now we derive a student class from the person class. The student class inherits the
Birthday operation and adds a public SetId method and a supporting field.
Typical client code using the student class might look like that shown below. A Student
object is created and a Student reference is used to refer to the object. Using the
Student reference, the client code can invoke the inherited Birthday method and the
student SetId method.
Things get more interesting when we consider the concept of type compatibility. Type
compatibility is a natural consequence of the fact that inheritance models the is-a
relationship. The formal way to express this idea is to say something like "a derived class
object has all the behavior of its base class and so can be used in any situation where a
base class object is expected." In practice this means that we are able to use a base class
reference to refer to any derived class object. In our person hierarchy, we could use a
Person reference to refer to a Student object. The assignment is legal because a
Student is-a Person.
Let's put aside for the moment the major applications of this technique and simply
examine the power and limitations of the code. The major advantage of using a base
reference is that our code becomes a little bit more generic since only the type of the
object is hardcoded. If we later decided to use a different type of object, say an
Undergraduate, we would only need to modify the new statement where the object is
created while the type of the reference used to manipulate the object would not have to
change. The key limitation is that we are only allowed to access the Person part of any
object when using a Person reference.
This is a key concept that shows up in many different situations so it's probably worth
repeating. The type of the reference determines what access is allowed to the object. If
the reference is of type Person, then only Person members can be accessed regardless of
the actual type of the object. The object might be an Undergraduate that supports many
other useful operations; however, they will not be available through a Person reference.
Despite this limitation, the use of a base class reference is an important and powerful
technique. To begin to see the benefits, consider the following CheckAge method which
has a parameter of type Person. Because the parameter is a base class reference, any
derived class object can be passed as the argument. The CheckAge method is said to be
generic because it works for all people.
Method binding
Classes from an inheritance hierarchy often contain methods which logically perform the
same operation. For example, every class in a hierarchy of different types of employees
would naturally offer a Promote method.
Each class would implement their Promote method as appropriate to their type.
Ironically, the Employee version would likely be the trickiest to code since it is difficult
to know how to promote a generic employee. We'll solve this problem by simply putting
an empty body for the implementation.
The Faculty promote method would of course involve a salary increase. In addition,
faculty members might have their level increased each time they are promoted. When
they reach a certain level they are rewarded with tenure so they can pursue long term
research without the need to continuously publish.
The Staff version of promote would probably be quite simple with only a small increase
in salary.
Now things get quite interesting since Faculty and Staff objects have two Promote
methods, the one they inherit from Employee and their own version. To see the issue,
consider the following client code. The Evaluate method has a parameter of type
Employee. Type compatibility means we can pass an Employee, a Faculty, or a Staff
member. Inside the method, the Promote method is called. Which version should be
invoked?
The process of deciding which method to invoke is called binding. There are two types of
binding which are called different things in different programming languages. The first
type is called static binding in the .NET world. Other names for this type of binding are
early binding and compile time binding. Static binding decides which method to call
based only on the type of the reference. In the Evaluate example static binding would
always choose the Employee version of Promote. The other type of binding is called
dynamic binding in .NET. Other names for this concept include runtime binding or late
binding. Dynamic binding decides which method to call based on the actual type of the
object involved. In essence, dynamic binding puts off the binding decision until runtime
when the actual type of the object is known. At runtime, the dynamic binding mechanism
determines the type of the object and invokes the appropriate version. In the Evaluate
example, if the parameter happens to be a Faculty member then the Faculty version
gets called, if it is a Staff member then the Staff version would be invoked.
The employee example has been carefully constructed so that dynamic binding is
appropriate for the Promote operation. The key observation is that the Promote methods
that appear throughout the hierarchy are all version of the same method. That is, the
details of their implementations are of course different but conceptually they all perform
the same operation.
To get static binding we need do nothing since it is the default. For dynamic binding we
need to apply two new keywords: virtual and override. The keyword virtual goes
on the method at the top of the hierarchy where the operation first appears. The derived
class versions are labeled with override to indicate they are the more specific version of
the inherited virtual method. The implementation for the employee hierarchy is shown
below.
Calls to the Promote method will now be dynamically bound. The behavior is illustrated
by the code below.
The thing that's so cool about all this is that the Evaluate method automatically adapts
its behavior to whatever type of object is passed to it. We can even create new derived
types of Employee and pass them to Evaluate and have their version of Promote called.
This type of dynamic behavior is often called polymorphism (literally "many shapes" or
"many forms"). Polymorphism lets us write generic code such as the Evaluate method
that works correctly for all types in an inheritance hierarchy.
Abstract class
Some classes represent abstract concepts. Classic examples of this type of class include
person, student, employee, shape, fruit, mammal, etc. These are abstract concepts because
they represent only categories and not real concrete types. To model this in code, C#
provides the abstract keyword. Any class that represents an abstract concept should be
labeled abstract to make the intent of the class clear to the compiler and to other
programmers. In the university people hierarchy, it is likely that the Person, Student,
and Employee classes would all be abstract.
Marking a class abstract prevents objects of that class from being created. This makes
sense because objects of the type do not exist in the real world anyway since the class is
modeling an abstract concept.
References are allowed even if the class is abstract. The references will be used to refer to
Abstract method
Sometimes there is no sensible implementation for a method in a base class. The Promote
method in the Employee class provides a good example since there is no way to promote
a generic employee. A meaningful implementation is only possible in a concrete derived
class such as Faculty or Staff. The abstract keyword is applied to a method to
indicate it is an abstract operation. Abstract methods are declaration only and cannot
contain an implementation. The signature of an abstract method is followed only by a
semicolon where the method body would normally appear.
An abstract method acts as a specification or contract that must be fulfilled by the derived
classes. Derived classes need to provide an implementation for the inherited abstract
method or they in turn become abstract classes. The derived class implementations are
labeled with the keyword override.
Downcasting
The type of reference determines what members of an object are available. This is
generally a good thing since it limits generic code to only the operations all types in the
hierarchy share. This is a key reason why writing generic code makes sense. This
limitation can be frustrating when the derived class offers many services beyond those of
the base class. The additional members are, of course, not available through the base
reference.
Sometimes the conversion will succeed (if the passed object is in fact a Faculty) and
sometimes it will fail (if the passed object is not a Faculty). This possibility for failure is
what motivates the compiler to refuse to do the conversion implicitly and to require the
programmer to explicitly ask for it. An analogy might be the signing of a liability waiver.
The compiler forces the programmer to acknowledge the possibility for failure and accept
the risk and consequences of that failure.
The first way to do the conversion is with traditional cast syntax; that is, by placing the
destination type name inside parentheses. If the object is of the specified type then the
cast succeeds and a reference of the desired type is obtained. However, if the object is not
of the right type the CLR will throw an InvalidCastException. The program can
handle the exception using the try/catch syntax.
The other way to do the conversion is with the as operator. If the conversion succeeds
then a valid reference results. If the conversion fails then the reference will be null.
There is one issue that is probably obvious by this time but which might be worth
pointing out anyway. In this kind of type conversion, the thing that is being converted is
the reference and not the object. We are simply obtaining a different reference to an
existing object and the object itself is completely unchanged.
Type testing
It is possible to test the type of an object using operator is. The operator forms a Boolean
expression that evaluates to true if the object is of the tested type and to false otherwise.
Note that the type of the object is being tested and not the type of the reference. This
should be obvious since the type of the reference is of course already known.
Overview
The default behavior is to choose the method based on the type of the
reference. This is called static binding because it can be performed at
compile time (i.e. the type of the reference is known at compile time).
Static binding has a couple of benefits: it is efficient and it is easy for
programmers to determine by inspection which method will be called.
Part 1- Setup
We will use a relatively simple inheritance hierarchy to practice with
dynamic binding: a base class Pet and two derived classes Cat and Dog.
In this first part we just setup the structure of the example. There are
no methods and no dynamic binding yet. Feel free to code this on your
own using the text descriptions or take advantage of the sample code
provided.
Steps:
1. Code a class to represent a Pet. Make the pet class abstract since
it models an abstract concept that should never be instantiated.
Add a public string field to store the name of the pet. Add a
protected constructor that sets the name. Making the constructor
protected gives access to only the derived classes.
2. Derive a Cat class from Pet. Add a public bool field which stores
whether or not the cat is picky. Provide a public constructor that
sets the name and the pickiness of the cat. Call the Pet
constructor to set the name rather than assigning to the
protected field directly.
3. Derive a Dog class from Pet. Add a public bool field which stores
whether or not the dog can fetch. Provide a public constructor
that sets the name and the fetching ability of the dog. Call the
Pet constructor to set the name rather than assigning to the
protected field directly.
4. Create a class called Household. Add a Main method that creates a
cat and a dog.
5. using System;
6.
7. namespace Binding
8. {
9. abstract class Pet
10. {
11. public string Name;
12.
13. protected Pet(string name)
14. {
15. this.Name = name;
16. }
17. }
18.
19. class Cat : Pet
20. {
21. public bool IsPicky;
22.
Steps:
Steps:
1. Add a virtualMove method to the Pet class. Move should take a bool
that indicates whether the pet should move fast or slow. Print a
message that indicates the name and behavior: pets walk when
moving slow and run when moving fast. Notice that we are able
to fully implement the Move method here because there is
reasonable generic behavior: walking and running make sense
for all pets.
2. abstract class Pet
3. {
4. public virtual void Move(bool fast)
5. {
6. ...
7. }
8. ...
}
9. Override the Move method in the Cat class. Change the behavior
from the generic walking and running to something more catlike:
cats pounce when moving fast and slink when moving slow.
10. class Cat : Pet
11. {
12. public override void Move(bool fast)
13. {
14. ...
15. }
16. ...
}
17. Override the Move method in the Dog class. Change the
behavior from the generic walking and running to something
more doglike: dogs bound when moving fast and stride when
moving slow.
18. class Dog : Pet
19. {
Steps:
6. Override the Speak method in the Cat class. Print the pets name
and the string "meows".
7. class Cat : Pet
8. {
9. public override void Speak()
10. {
11. ...
12. }
13. ...
}
14. Override the Speak method in the Dog class. Print the pets
name and the string "barks".
15. class Dog : Pet
16. {
17. public override void Speak()
18. {
19. ...
20. }
21. ...
}
Steps:
downcasting
Code that uses a base reference is restricted to accessing only the
base class parts of the object. To get around this restriction, a
downcast can be used to obtain a more specific reference. The
resulting code has greater access to the object but is less generic.
Steps:
1. Add the following Play method to the Household class. Since the
parameter type is Pet reference, either a Cat or a Dog can be
passed. It is possible to determine the actual type of an object
using operator is. The operator yields true if the object is of the
tested type and false otherwise. Code the Play method to
determine whether the parameter is a Cat or a Dog and print a
message stating the type.
2. static void Play(Pet p)
3. {
4. if (p is Cat)
5. ...
6. ...
}
cast syntax for cats and the as operator for dogs. After the
downcast, use the specific references to determine if the passed
cat is picky or if the dog can fetch. Implement the Play algoritms
appropriately.
Cat c = (Cat)p;
...
Dog d = p as Dog;
10-Interfaces
Goals:
Introduce the concept of interface.
Show how to define an interface.
Show how a class implements an interface.
Show how to implement two interfaces that contain methods with the same
signature.
Discuss how to use interface references to write generic code.
Overview
Interface definition
An interface is defined using the keyword interface, specifying a name, and providing
a body enclosed in curly braces. It is common practice to prefix the name of an interface
with an I but is not required.
An interface body is limited to declarations for methods, indexers, properties, and events.
Fields, constructors, constants, statics, etc. can not be included in an interface. Interface
members are implicitly public and it is a compiler error to specify the access level
explicitly. No implementations are allowed in an interface. Methods are specified by
giving only the return type, name, and parameter list followed by a semicolon. Indexer
declarations specify type, indices, and get/set accessor declarations. Property declarations
list the type, name, and get/set accessor declarations. Indexers and properties can be read-
Interface implementation
A class implements an interface by including the interface name in its list of base classes
and coding the interface members. The public access level for interface members is
implied in the interface but must be explicitly specified in the implementing class.
A class can implement multiple interfaces by listing each interface in its base class list
and coding all the elements of each interface.
If a class has a base class and implements interfaces, the base class must appear first in
the list of base classes or it is a compile time error.
Interface reference
It is possible to declare a reference of an interface type.
Interface references are used to refer to objects of types that implement the interface. For
example, if a Soldier class implements the IFighter interface, then an IFighter
reference can be used to refer to a Soldier object.
As always in C#, the type of the reference determines what access is allowed. Using an
interface reference restricts the client code to only the members that are part of the
interface.
Generic code
Using an interface reference to access implementing class objects is an important
technique because it allows the writing of generic code. Consider the WarmUp method
shown below which uses an interface reference of type IFighter as its parameter.
An object of any class that implements IFigther can be passed to the WarmUp method.
At this point, the example is not terribly compelling since we have only one class that
implements IFighter. As the number of implementing classes grows, the benefits get
magnified since the WarmUp method works unchanged with all the new types.
Type testing
The is operator can be used to determine if an object implements an interface. The
operator creates a Boolean expression that evaluates to true if the object being tested
implements the specified interface.
Interface inheritance
One interface can be derived from another in a process very similar to inheritance with
classes. The derived interface inherits all the contents of it base interface plus it can add
new members.
Interfaces are also allowed to have multiple base interfaces. The derived interface inherits
all the members of every base interface and can add new members as well.
A class that implements a derived interface is required to code all the members of the
base and derived interfaces.
Ambiguity
It sometimes happens that two interfaces contain a method with the same signature (name
and parameter list). For example, both an IFighter and an IWrestler interface might
contain a method called Block. The duplicate method causes a problem for any class that
C# provides two ways to overcome the ambiguity. In the first technique, the class
implements a single method to fill both roles. The main problem with this technique is
the difficulty of deciding what code to put in the single method. Do you combine the
code that would normally go in the separate methods together in the single
implementation? Do you choose just one behavior and ignore the other one? Because of
the difficulty in choosing an implementation, this solution is not used too often in
practice.
The second technique allows the programmer to code the two methods separately. Since
the two methods have the same signature there is a bit of special syntax that must be used
to specify which method belongs to which interface. Each method implementation is
qualified with the interface name in order to provide the needed clarification. The official
name for this technique is "explicit method implementation."
The basic idea behind calling an explicit method implementation is quite simple. There
are two methods with the same signature so we somehow have to indicate which of the
two we would like. Simply using a reference of the class type and attempting the call will
not work since it is ambiguous.
The right way to call an explicit method implementation is through an interface reference.
The type of the reference is used to determine which version gets called. For example,
using an IFighter reference would invoke the IFighter version while going through an
IWrestler reference would call the IWrestler implementation.
Before we finish, let's examine one minor syntactic detail with explicit method
implementation. Look back at the two Block methods inside the Soldier class and
notice that there is no public access modifier anywhere on the implementations. This is
a bit of an odd situation since normally omitting the access modifier would mean the
methods default to private. If the methods were truly private we would be violating one
of the basic rules of interfaces (that the contents are public). So we have what is
affectionately known in the industry as a "special case". The compiler considers the
methods to be private when a Soldier reference is used but public when an interface
reference is used.
Write an interface.
Code classes that implement an interface.
Show how inheritance and interfaces are used together.
Resolve ambiguity using explicit method implementation.
Overview
Part 1- Inheritance
Implement an inheritance hierarchy for the different types of
employees at a company. The abstract base class will represent a
generic employee. The three derived classes represent the various
types of concrete employees: programmers, managers, and interns.
Each class has some fields, a constructor, and a print method. Feel
free to code this on your own using the text descriptions or take
advantage of the sample code provided.
Steps:
virtual Print method to chain to the base class version first and
then print out the derived class fields.
5. using System;
6.
7. namespace Interfaces
8. {
9. abstract class Employee
10. {
11. protected string name;
12. protected double salary;
13.
14. protected Employee(string name, double salary)
15. {
16. this.name = name;
17. this.salary = salary;
18. }
19.
20. public virtual void Print()
21. {
22. Console.WriteLine("Name : {0}", name);
23. Console.WriteLine("Salary: {0}", salary);
24. }
25. }
26.
27. class Programmer : Employee
28. {
29. protected double averageOT;
30.
31. public Programmer(string name, double salary, double
averageOT)
32. :base(name, salary)
33. {
34. this.averageOT = averageOT;
35. }
36.
37. public override void Print()
38. {
39. Console.WriteLine("Programmer");
40. base.Print();
41. Console.WriteLine("Average OT: {0}",
averageOT);
42. }
43. }
44.
45. class Manager : Employee
46. {
47. protected string secretaryName;
48.
49. public Manager(string name, double salary, string
secretaryName)
50. : base(name, salary)
51. {
Part 2- Interface
To promote our employees we will write an interface containing a
Promote method. Programmers and Managers will implement the
interface. We will then write some generic code to work with a group
of employees and promote only those that are eligible.
Steps:
Steps:
11-Exceptions
Goals:
Overview
Exception hierarchy
Exceptions must be classes derived from the library class Exception. There are two
broad subcategories, one for exceptions generated by the CLR and the .NET Framework
(the system exceptions) and one for user defined exceptions (the application exceptions).
The Exception class offers some basic services. The two most generally useful features
are the read-only properties Message and StackTrace. The error message is set by the
code that creates the exception while the stack trace is filled in automatically. All
exception types inherit the services of the Exception class, plus they are free to add more
information specific to the type of condition they represent.
The .NET Framework class library defines many exception types for common error
conditions. These exceptions are typically used by the CLR or library classes to report
errors back to the application code. By convention, the class names all end in Exception.
Handling an exception
The CLR and .NET Framework Class Library throw exceptions to indicate errors. For
example, the CLR will throw an IndexOutOfRangeException when an invalid array
index is used. To handle an exception, the client must place their code inside a
try/catch construct. The real work goes inside the try block while the error handling
code is placed in the associated catch block. The control flow through a try/catch is
fairly straightforward. When an exception is thrown, the normal control flow is stopped
and the remainder of the try block will be skipped. Control jumps to the catch block
and the statements in the block are executed. When the execution of the catch block has
finished, linear control flow resumes from the line of code after the catch block. There is
no way to automatically return to where the exception occurred and continue from that
point.
A few more details about what goes on might be of interest. When the CLR or library
code throws an exception, they actually create an object of the appropriate type and stuff
it full of interesting information about what went wrong. In the example above, the CLR
would instantiate an IndexOutOfRangeException object. A reference to this object is
passed to the handler and appears in what looks like a method parameter. Glance back at
the catch block shown above and notice the thing named xcpt. That is a reference to the
exception object that was passed to the handler and is used to access the information
inside the exception object. There is nothing special about the name xcpt, the naming of
the reference is completely up to the programmer who codes the catch block.
Multiple catch
A try block may have multiple associated catch blocks. Each catch block includes a
parameter that specifies the type of the exception that it is designed to handle. catch
blocks are always chosen based on the type of the exception, so if an
IndexOutOfRangeException is active, then the catch block for that type will be
executed. Only one catch block will be executed from the set. After a catch has been
run, the others will be skipped and linear control flow will resume past the end of the
entire try/catch construct.
Generating an exception
The keyword throw is used to raise an exception. The object that is thrown must be of a
derived type of the library Exception class. In the following Divide method, we
generate a DivideByZeroException if the denominator passed to the method is zero.
Notice how a string is passed as the constructor argument when the exception object is
created. This string will become the error message that can be retrieved by clients using
the Message property. When a throw statement is executed, normal control flow is halted
and the search for a matching handler begins.
Locating a handler
Suppose our program begins execution in Main. The Main method then calls a method
named One. One calls a method named Two. Two calls a method named Three. Three calls
Divide and passes zero for the denominator argument. The Divide method will throw a
DivideByZeroException and the search for a handler will begin. This situation is
summarized in the figure below.
The search for a handler will begin within the Divide method itself. The code for Divide
is shown below. Notice that there are no try/catch constructs so no handler is available.
Since no handler was found, the Divide method will be exited and all the memory used
for its execution will be reclaimed (parameters, local variables, etc.). The search for a
handler will continue one level up the call chain in the method Three.
The story inside the method Three is similar to Divide in that there are no try/catch
constructs so no handler is available. Once again the search must move up the call chain
to the method Two.
Two looks more promising since it at least has a try/catch construct. However, the
catch is not of the correct type so it is ignored and the search continues with the method
One.
One contains a try/catch construct and has a catch of the correct type. The matching
handler is executed and the exception is finished.
If the search had unwound the call chain all the way to the top without finding a matching
handler, the program would have been terminated. Many systems have a default handler
at the top level that will print out some of the information in the exception such as the
message and/or the stack trace.
When a catch clause specifies a base class, the handler will match that base class and all
its derived classes. This is another example of the is-a relationship in action.
A catch block can be made generic by specifying an Exception reference as the type in
the handler. Since Exception in the root of the exception hierarchy this will catch all
exceptions.
Finally
Some resources require cleanup. The classic example is a disk file where the usage
pattern is the familiar open/access/close. It is sometimes difficult to use this type of class
in the presence of exceptions since an exception being thrown could cause the close
operation to be skipped. This situation is shown in the example code below which makes
use of the .NET Framework Library class FileStream. Notice that there is no handler in
the method so any exception that is generated will immediately leave the method in
search of a handler. When the exception leaves the method, the rest of the code will be
skipped and the close method of the file will not be called.
The keyword finally is used to create a block of code that gets executed regardless of
whether or not an exception is generated. This makes it perfect for any needed cleanup
code such as closing disk files. A finally block is placed at the end of the try/catch
construct. The classic setup is a try block, several catch blocks, and then a finally
block. It is also possible to have a try/finally construct without any catch blocks. The
sample code below demonstrates the syntax.
The control flow through a try/catch/finally construct can get a little tricky since
there are a surprising number of cases to consider. The main thing to keep in mind
though, is that the code in the finally block is always execute when control leaves the
try/catch above it.
To avoid getting bogged down in too much detail, let's just examine two cases based on
the try/finally code above. First case: the try block completes normally without
throwing an exception. In this case, after control leaves the try block, it jumps down and
executes the finally. After executing the finally block, normal linear control flow is
resumed with whatever code follows the finally block. Second case: part way through
the try block an exception is thrown. In this case, control skips the rest of the code in the
try block and jumps down to the finally block. After finishing the finally block
control will leave the method in search of an handler for the exception. There are a
number of other cases, but they should be relatively simple to construct and test if
needed.
Custom exception
To create a custom exception type, simply write a class derived from the library class
ApplicationException. Convention dictates that the class name should end in
Exception.
By convention, a custom exception should offer a constructor that takes a string which
represents the error message. The string can be passed to the base class constructor for
storage by the Exception class. The custom exception type can also contain any fields
needed to store addition information that might be of interest to the client code that will
handle the exception.
Overview
All exception types descend from the common base class Exception
defined in the .NET Framework Class Library. There are two interesting
derived classes of Exception: SystemException and ApplicationException. The
CLR and the library define many exceptions derived from
SystemException which they use for common error conditions. Users can
define custom exception types by deriving from ApplicationException. In
this exercise, we will begin by using SystemException types to practice
with the basics mechanics of try, catch, throw, and finally. After that we
will define and use a custom exception type.
Steps:
Steps:
Steps:
1. In your main program, create a try block that may generate two
different types of exceptions. For the first exception type, read
an array index from the user and apply it to an array. This may
generate an IndexOutOfRangeException. For the second exception
type, read two integers from the user and divide them. This may
generate an DivideByZeroException if the denominator is zero.
2. Place two catch blocks after the try, one for
IndexOutOfRangeException and one for DivideByZeroException.
3. Run the program several times and enter values that force both
exceptions to occur.
Steps:
Part 5- Finally
A try block can have an optional finally block. The code in the finally
block is always executed, regardless of how control leaves the try
block.
Steps:
Steps:
1. In this first step, we set up a bank account class that we will use
to demonstrate custom exceptions. The bank account stores an
int account number and a double for the account balance. The
constructor sets the account number and the initial balance. A
Withdraw method reduces the balance by the specified amount.
Feel free to code the class yourself or take advantage of the
sample code provided.
2. using System;
3.
4. namespace Exceptions
5. {
6. class SavingsAccount
7. {
8. private int accountNumber;
9. private double balance;
10.
11. public SavingsAccount(int accountNumber, double
balance)
12. {
13. this.accountNumber = accountNumber;
14. this.balance = balance;
15. }
16.
17. public void Withdraw(double amount)
18. {
19. balance -= amount;
20. }
21. }
22.
23. class SavingsAccountTest
24. {
25. static void Main()
26. {
In this lab we will force an exception to occur and observe the runtime
behavior when we fail to catch the exception.
Steps:
12-Namespaces
Goals:
Overview
A namespace provides a container for a group of types. Related types are placed inside
the same namespace to produce a clean and logical organization of code. Namespaces
help to avoid type name collisions by creating distinct scopes.
It sometimes happens that two programmers use the same type name. The chance of this
increases as the project size grows or when multiple libraries from third party vendors are
utilized. Two types with the same name cannot exist in the same scope. The code below
shows the problem. It will be a compile time error to use the two Shape classes in the
same program.
Placing the two Shape classes in different namespaces will resolve the issue.
Creating a namespace
The most basic way to create a namespace is to use the keyword namespace, give the
name of the namespace, and then define the namespace body. The body contains a group
of types inside curly braces. The figure below demonstrates the basic syntax by creating a
Shapes namespace containing four classes.
Defining an entire namespace in one place forces all the types to be defined in a single
file which makes it difficult for multiple programmers to work on the code. It is more
common to define the namespace in many separate parts and let the pieces be logically
merged together into a single namespace by the compiler. The syntax is shown below.
Notice how each class is defined in its own file and placed inside its own namespace
declaration. This way of defining a namespace is logically equivalent to defining the
entire namespace at once.
two cases to consider because the client code could either be inside the same namespace
or outside. The following logical representation of the Shapes namespace summarizes the
issue.
If the client code is inside the same namespace then there is no extra work to do and the
short name of the class can be used.
If the client code is outside the namespace, then the fully qualified name of the class is
used. The fully qualified name is formed from the namespace name, the dot operator, and
the short name of the class.
Typing fully qualified names quickly becomes tedious. A using directive eliminates the
need for fully qualified names by bringing all the types from a namespace into the local
scope.
Using alias
The keyword using can also create an alias for either a class or a namespace. This
technique is not common, but it is occasionally useful to resolve ambiguities or to
provide a way to easily switch between different namespaces so it is worth a quick look.
Let's first examine a using alias for a class. The syntax to create an alias is " using
newName = oldName;". The new name can then be used in place of the old name.
An alias for a namespace can be created with the syntax "using newNamespace =
oldNamespace;". The new name can then be used as a namespace name. The compiler
will translate the alias into the namespace name it really represents.
Nested namespace
Namespaces that are only one level deep are not generally sufficient to logically organize
code. Consider the Shapes namespace. Should all geometric shapes be placed in the
single namespace or might it be better to subcategorize the geometric types according to
some other property such as whether they are a two or three dimensional shape?
Furthermore, if two different programmers accidentally use the same namespace name
then the names will collide. To better guarantee uniqueness and to more finely categorize
their contents, namespaces can be nested.
The most basic syntax to create a nested namespace is to define one namespace inside
another as shown below. This syntax is considered a bit verbose and is not used very
often in practice.
The preferred way to define a nested namespace is the shorthand syntax using the dot
operator.
The fully qualified name for a member of a nested namespace consists of each namespace
name and the class name with each component separated by the dot operator. It is
common to encounter five or six levels of namespace nesting so a using directive is often
the better choice.
Global namespace
Types that are not defined inside any namespace are placed in the "global namespace".
Members of the global namespace are available for use in all other namespaces with no
prefixing required. The common wisdom says to minimize your impact on the global
namespace because each symbol you add to the global namespace increases the
likelihood of a name collision.
Overview
Steps:
class Photo
{
public string Title;
}
class Album
{
public Photo[] Photos = new Photo[10];
}
class Album
{
public string Artist;
public string Title;
}
class Agent
{
public string Name;
public double Commission;
}
Steps:
Steps:
Steps:
Overview
A delegate is a proxy for a method. Clients interact with the delegate and the delegate
forwards the operation to the method it represents. The advantage of this indirect
arrangement is that the client code is relatively independent of the target method. The
client sees only the delegate and does not need to know much about the method the
delegate represents. Delegates are used in many places throughout the .NET Framework
Class Library. Classic examples are as callbacks in user interface programming and as the
way to specify the work to be performed by a thread.
We must have an object of the Stock class available in order to call the Buy method. The
object will become the hidden parameter this inside the method.
Notification
Objects typically store information in their fields. The formal way to say this is that the
object maintains an internal "state". The state changes over time as user input is received
or as client code calls methods. The Student class shown below models a student at a
university and demonstrates the pattern. The student maintains a numerical rating of their
scholarship in the form of a "gpa" (grade point average). Their gpa changes as they take
courses and receive grades. Each time they finish a class, the RecordClass method will
be called and their grade passed in (an A is worth 4 points, a B worth 3, and so on). A
new value for the gpa will be calculated to take into account the newly received grade. In
the example, we have made the simplifying assumption that each class is worth one unit.
Various parties might be interested in tracking the student's performance. Certainly the
student's parents come to mind since they might be paying the tuition fees and want to
stay informed of their child's progress. Other likely candidates are the school registrar,
the dean of the college, any honor society for which gpa is a criterion of membership, etc.
The student will be responsible for notifying all interested parties when the gpa changes.
The situation is summarized in the diagram below.
Notification typically involves "registration" and "callback". The targets that would like
to receive notification must register with the caller. The caller will notify all registered
clients whenever the state changes. Another name for this pattern is "publish/subscribe".
In our example, the parents and the registrar would need to register with the student. The
student would notify them at the end of each class when their gpa is updated. The idea is
captured in the figure below.
Delegate definition
.NET uses delegates to implement callbacks. A delegate acts as an intermediary between
the caller and the target. The target creates a delegate and registers it with the caller. The
caller invokes the delegate which forwards the call to the target. The main advantage to
this approach is that the caller and the target do not communicate directly so the caller
never needs to know the type of the target or the method that it is calling.
The caller and the target do need to know a little bit about each other since the caller is, in
effect, making a method call on the target. The caller and target need to agree on the type
of information that flows between them. In the student example, it makes sense for the
student to send the new value for their gpa to a target such as a parent. We'll keep things
simple and assume the parent doesn't need to send any information back to the student. If
we think of these two pieces of information in terms of a method signature, we would
picture a method that had an argument of type double (the gpa) and a return type of void
(nothing gets passed back to the student). The information flowing between the student
and their parent is described in the figure below.
For the student and parent to interact, they need a delegate that describes the information
flowing between them. A delegate is defined using the C# keyword delegate in front of
what otherwise looks like a normal method signature. The name of the delegate goes in
the position normally occupied by the method name. The argument list and return type
use the standard method definition syntax. A delegate for the student/parent interaction is
shown below.
The compiler does a lot of work behind the scenes to process a delegate definition
(typically generating an entire class from the delegate). The result of all the compiler's
work is that a delegate name is actually a type name and we can declare references of that
type and create objects.
Delegate use
Examine the diagram below that shows the links between the student, the delegate, and
the parent.
The student needs to hold a reference to the delegate. To accomplish this, we simply add
a field to the student class of the delegate type.
The parent needs to define a method for the student to call (indirectly through the
delegate of course). The student and parent have agreed that the method should take a
double argument and return void and this interaction was formalized in the delegate
definition. The parent must then define a method that conforms to the required signature.
The parent is free to name the method anything they like, only the parameter list and
return type are constrained.
Next we write the client code that puts everything together. We need to create three
objects: the student, the parent, and the delegate that links them. Creating the student and
the parent are both straightforward operations. Creating the delegate is much more
interesting. When a delegate is created it needs to be given the target object and the target
method since both are necessary to make a method call in an object-oriented system. The
target information is passed to the delegate constructor and will be stored inside the
delegate object. Once the delegate is created, we register it with the student by assigning
to the student's delegate field.
The student needs to call through the delegate whenever their gpa changes. To
accomplish this, we modify the student's RecordClass method to include the invocation.
The call is made using regular method call syntax on the delegate. Internally, the delegate
calls the target method on the target object and passes along the gpa.
Null reference
A delegate is a reference type so delegate fields will default to null. It is common
practice to test delegate fields to make sure they are not null before attempting to make a
callback. The CLR will throw a NullReferenceException if the call is made when the
reference is null.
Static methods
A static method can be registered using a delegate. When the delegate is created, simply
pass ClassName.MethodName as the constructor argument. Because static methods do not
have a this reference, a target object is needed.
Multiple targets
Multiple targets can be registered with a delegate. Each delegate stores an "invocation
list" of targets and the + and += operators are used to add new targets to the list. All
targets in the list get called back when the delegate is invoked. The targets will be called
in the order they were added to the invocation list. The student class might use this
feature to notify both their parents whenever their gpa changes. One minor point is worth
mentioning. Notice that += is used to register both targets in the example below. This is
ok even though when the first target is registered the delegate will be null. The special
case is automatically handled to help make the client code simpler.
The - and -= operators are used to remove a target from an invocation list.
Events
There are several operations that can be performed on a delegate: assignment, registering
a target with +=, removing a target with -=, and invocation. The common wisdom says
that external code should be able to register a new target or unregister an existing target
and should have no additional control over a delegate field. We do not want to allow
external code to assign to a delegate because a careless client could accidentally remove
all previously registered targets simply by using = instead of +=. We do not want to allow
invocation because the external code is not in a position to know when conditions are
right for a callback. The object internally should make this decision whenever its state
changes. So we have a problem. If we make the delegate field public, the client code gets
full control including the power of assignment and invocation. On the other hand, if we
make the delegate field private the client code has no access at all and cannot even
register or unregister targets.
This discussion is reminiscent of the private data / public access method pattern. The
class designer could make the delegate field private and code public Register and
Unregister methods. This would give the desired interface since clients could only add
and remove targets and would have no additional power. The designers of C# decided
that requiring programmers to constantly code that pattern was too much busywork. To
solve the problem they introduced the event keyword. Adding event to a public delegate
field gives exactly the desired behavior. External code can still use the += and -=
operators but invocation and direct assignment will not compile.
Applications
The .NET Framework Class Library makes extensive use of events. For example, when
creating a new Thread, the client must pass a ThreadStart delegate to the constructor.
The delegate is used to specify the work that the new thread should perform. Another
common application is in user interface programming. Controls such as buttons, text
boxes, list boxes, etc. offer events. Clients create delegates and register them with the
controls. When the event is triggered, the client will be called back through the delegate.
To supply a concrete example of delegates and events in use, we will conclude our
discussion here by taking a closer look at a few of the library delegate types and their use
with GUI programming.
The .NET Framework Class Library defines a delegate named EventHandler which is
used throughout the library. The EventHandler type is actually defined in the System
namespace which indicates it is intended to be applicable to many areas of the library.
The definition of the EventHandler delegate is shown below. Note how the delegate
captures the information flowing between a publisher and a subscriber. A publisher
passes itself as the first argument and an EventArgs object as the second argument.
Passing itself to the handler method lets the client code easily determine which object
fired the event. The EventArgs object is used to pass details of the event.
The data flow through an EventHandler delegate is summarized in the figure below.
Clients that would like to register for the Click event must define a method with the
appropriate signature: a first parameter of type object, a second parameter of type
EventArgs, and a return type of void. They then create an EventHandler delegate and
use the += operator to register with the event. The registration process is shown in the
figure below. When the user clicks on the ok button in the GUI, the callback method
will be invoked.
The Click event is a good introduction to the topic of GUI event handling but it is a little
too simple to show the full power of the model. The main reason the Click event is not
representative is that there is generally no need to pass any information from the control
to the event handler for something as simple as a Click. That is, the only thing the
handler needs to know is that the button was clicked and there is typically no need for
more detailed data to be sent. In fact, the EventArgs class does not even contain any data
so the publisher could not pass any additional information if it wanted to.
Other types of events do need to pass additional information. For example, a mouse event
would likely need to send the coordinates of the mouse click and the identity of the
mouse button the user pressed. To accommodate this type of interaction there are more
specific delegate types that pass derived types of EventArgs as parameters. The derived
types contain the detailed event information. A delegate definition for a mouse event is
shown in the figure below. Notice how the second parameter is now MouseEventArgs
rather than the more general EventArgs.
The MouseEventArgs class is derived from EventArgs and adds information specific to
mouse events. Part of the class definition is shown below.
The Control class offers mouse events such as the MouseDown event shown in the next
figure. Notice that the type of the event is MouseEventHandler rather than the generic
EventHandler.
A client that wishes to subscribe to the MouseDown event must code a method that fits the
MouseEventHandler pattern. The process is shown in the figure below.
There are other interesting events as well such as those for keyboard activity and
painting. They all fit the same basic pattern: a specific delegate type with a derived class
of EventArgs to transmit the event details.
Overview
Part 1- Basics
In this lab we explore the basic definition and use of delegates and
events. There will be three main pieces: the delegate definition, the
publisher that invokes through the delegate, and the subscriber that
receives the call.
Steps:
Invoke the delegate from the driver code. Again, this is allowed
because the delegate is public. This is also considered poor style
since the callbacks should only be made when conditions in the
publisher warrant it.
In this lab, we recode the Publisher class Dispatch method from the last
exercise so it manually invokes each target.
Steps:
Notes:
In this lab we will code a histogram class that uses a delegate for the
data source of each entry. To keep things simple we will make a
Notes:
Each entry will have only a magnitude, text labels will not be
supported.
Magnitude will be an integer.
Drawing will be done using text: the magnitude will be
represented by the corresponding number of "*" characters.
The histogram will be drawn "sideways" with each entry on one
line.
Steps:
20. }
}
Overview
In this sample you will be creating two projects within a single solution in VS.NET. One
project will be a console application as before while the other will be a library application
that we will version and deploy in the Global Assembly Cache (GAC).
Criteria:
Create a console application and add a library application to the solution. Feel free
to name them whatever you want.
Notice that in both projects the VS.NET templates used provide several files for
you, one of them is the "AssemblyInfo.cs" file. If you open this file up on the
library project and take a look inside you will notice a bunch of attributes. We
will be modifying the AssemblyKeyFile and the AssemblyVersion attributes.
In the library application create a simple function that returns some data, then
reference this project from your console application and add code in the Main
method to create an instance of this library and call the function.
Test your application to verify everything works as expected. The code for the
library and the client should look something like the following:
//client code
class Client
{
static void Main(string[] args)
{
TheWidget wg = new TheWidget();
Console.WriteLine(wg.Foo());
}
}
//library code
public class TheWidget
{
public string Foo()
{
return "data from foo";
}
}
Once you have tested your application close the console application and open
only the library project. Make a change to the data returned in the library method
and then recompile the library and place the new dll in the same folder as the
client .exe file. The exe can be found under projectname\bin\debug. Run the
client.exe from a command prompt and notice that the client exe now displays the
new data from the library dll without being recompiled! In fact there is no version
checking or anything being performed on the client.exe. This is a problem! Now
we want to put version information on our library and make sure that this type of
upgrade can't happen by mistake.
Notes:
This part of the sample demonstrates calling an assembly that has no versioning
information on it and can be easily changed right under the client with no
complaints.
Criteria:
Open the library application we have been working with and in the solution
explorer open the AssemblyInfo.cs file. We need to modify the AssemblyKeyFile
attribute found near the bottom of the file. Add a filename to the empty string in
the attribute so that it looks something like the following [assembly:
AssemblyKeyFile("mykey.snk")]. This instructs the compiler to
look for a file called mykey.snk for key information to use in order to sign the
assembly when compiling. We now need to create this file and place it
somewhere the compiler can find it.
Open a the Visual Studio Command Prompt, located on the programs menu with
VS.NET, and then navigate to your client project folder. We need to generate the
mykey.snk file.
Type sn.exe -k mykey.snk this will invoke the strong name utility and
tell it to generate a public/private key pair in a file named mykey.snk.
Now that we have generated the file we need to compile our library application.
VS.NET by default looks in the projectfolder\obj\debug folder to
locate mykey.snk. So, we can either put the file there or we can instruct VS.NET
to look in the root by changing the attribute to the following: [assembly:
AssemblyKeyFile(@"..\..\mykey.snk")] , you decide what
works best for you. Just realize that if the compiler can't find mykey.snk you will
get an error and your assembly will not be signed.
Once you are sure everything is compiling fine with the library go ahead and open
the client and recompile it, being sure to use the new signed assembly.
Now test the client.exe by running it from a command prompt, it should work
fine. Then go back and change your library component and recompile ONLY the
library. Drop the new library.dll in the same folder as the client.exe as we did
earlier, over writting the one currently there, and then test the client.exe again. It
should fail this time as the client.exe is expecting/demanding the older signed
version of the assembly which is no longer available. You now have version
control.
Where did the version numbers come from? If you look in the AssemblyInfo.cs
file of the library application you will notice that there is an AssemblyVersion
attribute that looks like the following: [assembly:
AssemblyVersion("1.0.*")], this tells VS.NET to simply increment
the build and revision numbers everytime you compile, which gives us a new
version each time. If you want to manually control when you get a new version
simply change the attribute to something you want like: [assembly:
AssemblyVersion("1.0.0.0")].
Notes:
You might also inspect the library.dll and client.exe with ILDASM.EXE before
and after setting a strong name on your assemblies to see the added information in
the manifest when signing.
Criteria:
Now that you have created a strong named assembly you can deploy it in the
GAC. To install the assembly in the GAC use the gacutil.exe tool.
Open the VS.NET Command Prompt and navigate to the folder where your
library.dll is located and then type gacutil -i yourlibrary.dll. This will install the
dll in the GAC. Open up explorer.exe and look under Windows\Assembly to find
your dll. Once you have verified it installed into the GAC go to your client.exe
and remove any local copies of the dll found in the folder with the client.exe and
then try running your client.exe. You should notice that the client.exe works fine
because it is using the assembly found in the GAC
Now go build another version of your library.dll and install it in the GAC. How
do we get our client application to use this new version in the GAC? If you delete
the old version from the GAC the client application will simply stop working.
What we need to do is build an application configuration file.
Create an xml file with the same name as your client.exe and tac .config to the
end of it. Your file name should look something like client.exe.config.
In the xml file you need to add an assembly redirect. The following sample will
help you:
<?xml version="1.0" ?>
<configuration xmlns:asm="urn:schemas-microsoft-com:asm.v1" >
<runtime>
<asm:assemblyBinding>
<asm:dependentAssembly>
<asm:assemblyIdentity name="Widget"
publicKeyToken="f496b453d02f293f"
/>
<!-- one bindingRedirect per redirection -->
<asm:bindingRedirect oldVersion="1.0.0.0"
newVersion="2.0.0.0" />
</asm:dependentAssembly>
</asm:assemblyBinding>
</runtime>
</configuration>
You will need to make sure the name, publicKeyToken, oldVersion and
newVersion attributes in the application configuration file match your information
in order for things to work.
Notes:
This sample is a bit tedious with respect to putting versions of files in the right
place, so if things don't work right away don't be suprised it's easy to thing
configured wrong. You want to make sure your configuration file is correct and
that the right version you expect is in the GAC and that the folder with the
client.exe doesn't contain any versions of the dll to test clearly.