Sei sulla pagina 1di 966

Introduction to C#

Introduction to C#
Objectives
Learn about the history of C# and .NET. Understand the goals of C#. Provide a context for .NET and C#.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

1-1

Introduction to C#

Welcome to C#
This course teaches you how to build Windows and Web applications using C# and the .NET platform. .NET represents a significant innovation in the development of Windows and Web programming, and C# is the language of choice for use with the .NET platform.

The Goals of C#
C# is designed to be: Simple Safe Object-oriented Internet-centric High performance

C# is a simple language with only 80 keywords and a dozen types. It is a typesafe language, which means that it enlists the compiler in helping you find bugs while you develop your program. C# builds on the lessons learned from C++ and Java to provide a fully objectoriented approach to design and programming. Object-oriented languages allow you to create new types that reflect the objects in your problem domain. This allows you to manage very complex projects and build programs that are easy to maintain and extend. C# is highly Internet-centric; the goal of C# and .NET is to integrate the Web into desktop applications and to facilitate the development of Web applications. Finally, C# does not sacrifice the high performance characteristics of the C family of languages.

Introducing the .NET Platform


The .NET platform was introduced in July 2000 at the Microsoft Professional Developers Conference (PDC). .NET created quite a sensation; here was a revolutionary approach that provided an object-oriented, component-based layer on top of Windows to facilitate desktop and Web application development. This was essentially a whole new API for Windows and for the Web, presented as a tightly integrated object-oriented framework.

1-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Welcome to C#
As time went on we came to discover that .NET was an umbrella for a number of new technologies emerging from Microsoft, including: COM+ ADO.NET ASP+ (now ASP.NET) XML Web Service Protocols SOAP WSDL UDDI

COM+ provides transaction support and messaging for both desktop and Web applications. While the COM+ technology was developed independently of .NET it is fully integrated into the .NET platform. ADO.NET is the successor technology to ADO and provides an objectoriented approach to database interaction. ADO.NET differs from ADO in that it is centered around a disconnected dataset, allowing for greater scalability in the development of database applications. ASP.NET is the replacement technology for ASP, which is Microsofts Web Application development technology. ASP.NET provides a highly scalable, object-oriented, component-based Rapid Application Development environment for building Web applications. XML is the open standards technology for document markup. While all of .NET is built on XML, it is not necessary to become proficient in XML to work with .NET. Visual Studio.NET and the supporting technologies encapsulate and hide the gritty details of XML. Microsoft has made a strong commitment, however, to building .NET on top of open standards such as SOAP: the Simple Object Access Protocolan open, lightweight XML-based standard for the exchange of information in a decentralized and distributed environment.

New Tools
.NET provides a number of new tools, including a suite of new languages (VB.NET, C#, etc.). One of the most powerful new tools is the new integrated development environment: Visual Studio .NET. Visual Studio .NET is an extension of the rapid application development environment previously found in Visual Basic 6, combined with the best elements of FrontPage and the other Microsoft development tools.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

1-3

Introduction to C#
In addition, the new .NET platform provides a comprehensive class library to support many common programming tasks.

New Languages
.NET offers a number of new programming languages, the most important of which are Visual Basic .NET and C#. Visual Basic .NET offers a Visual Basic-like syntax for a fully object oriented language. C# offers a C++-like syntax for the .NET platform. The focus of this course will be on C#. C# is a revolutionary advance in the Clanguage family that previously included C and C++ and, arguably, Java. C# learns lessons from all three, maintaining the performance of C, the overall syntax of C++, and borrowing many innovations from Java, such as garbage collection for memory management. The .NET goal is to provide a language independent development environment. It is possible to write .NET classes in any language that supports the Common Language Specification (CLS). The CLS dictates the characteristics of any conforming .NET language. All CLS languages are integrated with one another, allowing you to create a class in C# and derive from it in Visual Basic .NET, treating both classes polymorphically. In addition, you can throw an exception in a Visual Basic .NET class and catch it in a C# class. All of these concepts: deriving classes, polymorphism, and throwing exceptions will be explained as you go forward.

CTS and CLS


The Common Language Specification (CLS) declares the functionality that any .NET language must support. Part of the CLS is the definition of a Common Type System (CTS). Thus, every language within .NET will support the same set of intrinsic (built-in) types. Currently there are four official languages, though Microsoft expects many more to be forthcoming: C# Visual Basic .NET C++ with managed extensions JScript.NET

Any CLS language can be used to interact with the Framework Class Library. The class library offers a comprehensive suite of useful objects for your 1-4 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Welcome to C#
programming. Youll learn how the Framework Class Library works in great detail throughout this class.

Common Language Runtime (CLR)


The CLR offers an object-oriented platform for Windows and Web development. It is the CLR that interprets your code and acts as an intermediary between your .NET application and the underlying platform. The CLR houses the Just In Time (JIT) Compiler and the intrinsic types as well as support for exceptions.

.NET Architecture
The .NET Framework is built on top of the CLR, which in turn sits on top of the Windows platform, as shown in Figure 1.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

1-5

Introduction to C#

Figure 1. .NET architecture.

The .NET Framework classes are built on top of the CLR and provide extensive low-level support for your application. The Framework Classes provide the plumbing you would otherwise write yourself, including classes for threads, streams, security, and so forth. ADO.NET is the database support technology for .NET. ADO.NET is built on top of the Framework Classes and provides a high level abstraction of database tables and database relationships. Youll learn about ADO.NET in greater detail later in this course. On top of all of this technology lies the support for building Windows applications, Web applications, and Web Services. Web Services are Web applications without a user interface. Web Services provide data and functionality to other Web applications (e.g., a Web Service might provide a stock ticker symbol given the name of a company). C# is the programming language of choice for every layer of this architecture. Much of the CLR, and nearly all of the .NET Framework, is written in C#. C# 1-6 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Welcome to C#
is ideal for writing ADO.NET applications and certainly C# is the right language for building Windows or Web applications.

MSIL
The programs you write in C# are not actually compiled into executable code. Instead your source code (the English text you write) is compiled into an intermediate form called Microsoft Intermediate Language (MSIL) or IL for short. The reason that all .NET languages can interoperate is that the IL code produced by C# is nearly identical to the IL code produced by Visual Basic .NET.

Key Terms
Source code MSIL The English-like text that you type into a file. Microsoft Intermediate Language (MSIL) is the intermediate form your program is transformed into by the compiler. The .NET CLR (Common Language Runtime) transforms the MSIL code into executable code when your program is run.

History of C#
C# was created by Anders Hejlsberg and Scott Wiltamuth. Hejlsberg is the creator of Turbo Pascal and Borlands Delphi. They built C# on the shoulders of C, C++, and Java, consciously borrowing the best aspects of these languages and adding innovations of their own to facilitate .NET development. Because C# was specifically built for .NET and is designed to work in the .NET environment, this course focuses on building .NET applications with this powerful language.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

1-7

Introduction to C#

Hello World
It is traditional in programming books to begin with a program that prints the words Hello World to the monitor. This course will be no exception. Youll start by examining a simple Hello World program.

Source Code
A program consists of source code that you type into a file. You compile the source code file into IL (Microsoft Intermediate Language). You run your program and the CLR turns the IL into executable code. This is known as Just In Time compiling. The compiled code is then cached in memory; the next time you run that block of code it is ready to go. Lets look at a very first program:
namespace HelloWorld { class HelloWorld { static void Main() { // use the system console object System.Console.WriteLine("Hello world"); } } }

Though it is very simple, this program illustrates many concepts in C# programming. Lets take it apart, piece-by-piece. One of the first things you should notice is the keyword class in the third line:
class HelloWorld

A class creates a new type. A type is a thing. The world is filled with things; and we as humans cant help noticing them. Your desk, phone, and chair are things. Your dog, sister, and children are things. The sky is a thing, the idea of 1-8 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Hello World
love and honor are things. We are a thing-oriented species, and C# is a thingoriented language. Some things are concrete, like dogs and desks, and some things are abstract, like love and freedom. C# is able to represent both concrete and abstract things. A class defines a new thing. An object is an instance of a class. Just as dog describes an idea but Fido is an instance of a dog, a class describes a type and an object is an instance of that type. The class button tells you what buttons are like, the edit, cancel, and delete buttons are instances of button, in other words, they are objects. Classes defined new types. From a C# perspective, a type describes the size and capabilities of objects. C# provides a number of built-in types such as int, long, and double, and you are free to create your own types.

Methods
Lets look inside the body of the HelloWorld class:
static void Main() { // use the system console object System.Console.WriteLine("Hello world");

Every class has properties and behaviors. In fact, in C# everything that happens, happens within a class. So class is not only a keyword, it is a key concept. The behavior of a class is defined by its methods (member functions). Methods are typically given action names such as AddNumber or WriteLine. Every program must have a special method, Main. Main does not have an action name, but it does have a special role in every program: it is the entry point. That is, the program begins by calling the Main method. Main must be marked with the keyword static. Youll understand the meaning of the static keyword later in this course; for now you just need to know that Main() must be marked as static. A method has a name, parameters, and a return type. If the method does not return a value it is marked void. You call a method and when it completes, it returns.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

1-9

Introduction to C#
For example, if you add a method, myMethod, you will indicate its parameters within parentheses, and its return type before the name of the method.
void myMethod(int param1, string param2) { // do work here }

The method name is myMethod. The return type is void. This method takes two parameters. The first is an int (integer) and the second is a string. Somewhere else in your code you might invoke that method.
int x = 5; string myString = Hello; myMethod(x,myString);

You declare a couple of local variables: x of type int and myString of type string and then pass these variables as parameters to the method MyMethod. You can declare local variables within any method. These variables are local to the method itself and are destroyed with the method ends. In addition, you can pass in parameters, as you saw here, and these parameters act just like local variables within the method. Youll learn about variables, strings, integers, and parameters in great detail later in this course. The key concept, however, is that methods are useful when you want to provide behavior for your class. Typically, methods are used to manipulate the class data or to interact with other objects.

Comments
In this simple example, you see that there is one line that begins with a pair of slashes:
// use the system console object

This is a comment, which is a note to the programmer that does not affect the program in any way. C# supports three styles of comments: C-style comments /* */ C++ style comments // XML documentation comments ///

1-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Hello World
C-style comments begin with a slash-star /* and end with a star-slash */. Everything between the open and end comment marks is commented out. Cstyle comments are very useful for marking large blocks of source code as comments, and most C# programmers reserve them for commenting out code they want to temporarily disable. The most common type of comment used for documenting C# code is the C++ style double slash //. Everything from the double slash to the end of the line is a comment. C++ comments can start in the middle of a line, allowing you to add a comment to the same line as the code itself. The third type of comment is the XML documentation style comment. Youll learn more about this powerful feature later in this course. Comments are a very powerful technique when used well. The key to commenting your code is to write about the purpose of a given line of code, rather than restating what the line of code does. Well-written code is also well commented. Youll be surprised how often youll return to code you wrote and scratch your head thinking, What the devil was I trying to accomplish here? Comments are also very useful for the programmer who inherits your program and needs to understand how it works. Over time you will find that a programmer can never add too many comments.

Console
The last line of the HelloWorld program does all the work:
System.Console.WriteLine("Hello world");

The first thing to note is that this example is a console application. Console applications run in a console window, often called a DOS box. The console is your monitor and a console application simply writes to your console. Console applications are very simple, with virtually no support for Graphical User Interfaces (GUIs). Youll use Console applications a lot early in this course, because they greatly simplify development and allow you to concentrate on the issues at hand, rather than fussing with complex UI issues. In C# the console (monitor) is managed by a console object. WritLine is a method of the Console class. You pass a string as a parameter to the WriteLine method, which in this case is the string Hello World. This string is then printed to the monitor.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

1-11

Introduction to C#

Namespaces
Notice that Console is preceded by System. System is the namespace within which the Console class resides. The Framework Class Library has thousands of useful classes, and each class has a number of methods. That makes for tens of thousands, perhaps hundreds of thousands of names of methods and classes. Namespaces help divide the world of these names to simplify working with classes and to prevent clashes between the names of methods in different parts of the Framework. The dot operator (.) is used to access classes within a namespace. For example, you access the Console class within the System namespace with System.Console. The dot operator is also used to access methods within a class. For example, you access the WriteLine method within the Console class with Console.WriteLine().

1-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Building The Application

Building The Application


There are two ways to build the Hello World application. The easiest way is within Visual Studio .Net. Youll be spending a lot of time within the Visual Studio .NET integrated development environment (IDE), but to prove that there is nothing magical about the IDE youll build this first application from the command line.

Try It Out!
1. Open Notepad and copy in the program as shown in Figure 2.

Figure 2. Notepad with Hello World.

2. Save the program as HelloWorld.cs. 3. Open the Visual Studio .NET command prompt from the Start window. This opens a command window set up properly for the .NET compiler. 4. Navigate to the directory in which you stored your program. 5. Enter the command csc HelloWorld.cs This builds the application. 6. Type dir to see a directory of the current folder. You should see at least the following two files: HelloWorld.cs HelloWorld.exe

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

1-13

Introduction to C#
7. Enter HelloWorld from the command prompt. The program should run and print the words Hello World! to your monitor.

Building Applications in Visual Studio .NET


Youve built the application in Notepad and compiled it at the command line. Next you will build the same application from within Visual Studio .NET. With such a simple application as this, Visual Studio .NET does not provide much obvious additional value; and the code it will create will be a bit more complicated. But Visual Studio .NET does offer tremendous advantages with anything but the simplest of code. 1. To get started, open Visual Studio .NET by clicking the Start button. 2. Click on New Project. This opens the New Project dialog box as shown in Figure 3. Be sure to select a Visual C# project in the Project Types window and a Console Application in the Templates window. You may name your program anything you like, and store it in whatever location is convenient for you. When you are ready, click OK.

Figure 3. The New Project dialog box.

1-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Building The Application


3. Visual Studio .NET sets up your application and opens a series of Windows to facilitate writing your code, as shown in Figure 4. Notice that the code window offers a starting point for the Main method.

Figure 4. The Hello World application.

4. Within the body of Main, delete the comment and add the following code:
Console.WriteLine("Hello World");

5. Notice that Visual Studio .NET attempts to help you choose the right method from Console, and offers supporting information about the parameters expected for WriteLine. Your code should now look like Figure 5.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

1-15

Introduction to C#

Figure 5. Hello World.

6. Note that the Main method is written somewhat differently by Visual Studio .NET. In this version of Main() there is an argument named args. Youll ignore this for now. This is useful when calling a C# application from the command line, but is not relevant to the current program. 7. Save this program by pressing CTRL+S or by clicking the Save button, as shown in Figure 6.

Figure 6. The Save button.

8. Build this program by pressing CTRL+SHIFT+B or by selecting Build>Build from the menu. You should see Build: 1 succeeded, 0 failed, 0 skipped in the Output window. 9. Run the program by pressing CTRL+F5 or selecting Debug->Start Without Debugging from the menu. You should see a console window open and the text display, as shown in Figure 7.

Figure 7. Running the program.

1-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Building The Application


10. Notice that Visual Studio .NET keeps the window open and adds the prompt Press any key to continue. This prompt is provided for you by the IDE, and you do not need to modify your program to create this prompt.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

1-17

Introduction to C#

Summary
The .NET platform supports a Common Language Specification detailing how languages must behave. The CLS includes a Common Type Specification (CTS) detailing the types for all .NET languages. C# is ideally suited for work with the .NET platform. The Hello World program illustrates the fundamentals of writing a C# program. The steps are: write the program, compile it and run it. The output of compiling the program is Microsoft Intermediate Language (MSIL). The Common Language Runtime (CLR) turns the IL code into an executable program in a process known as Just In Time Compiling.

1-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Building The Application

Questions
1. What is the Common Language Specification? 2. What is ADO.NET? 3. What is MSIL? 4. What are the three types of Comments?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

1-19

Introduction to C#

Answers
1. What is the Common Language Specification?
The Common Language Specification (CLS) declares the functionality that any .NET language must support

2. What is ADO.NET?
ADO.NET is the successor technology to ADO; it provides an object model for interacting with back end data providers such as relational databases

3. What is MSIL?
MSIL is Microsoft Intermediate Language. When you compile your program what is produces is an MSIL file that is then compiled by the Just In Time (JIT) compiler. The MSIL produced by C# is virtually identical to the MSIL produced by other CLScompliant languages

4. What are the three types of comments?


C# supports C-style comments (/* */) and C++ style comments (//) as well as the new XML documentation comments (///)

1-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Language Fundamentals

Language Fundamentals
Objectives
Learn about variable declaration, assignment, and initialization. Understand literal, symbolic, and enumerated constants. Discover the meaning of type. Understand statements and expressions.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

2-1

Language Fundamentals

Variables
Variables are, technically, named typed storage locations. What this means is that you can create variables to hold values of a specific type, and you can give these variables names. For example, you can declare:
int myAge; // declare an integer variable

myAge = 25; // assign a value to the variable

Here youve created a variable named myAge and youve declared that it will hold an int. An int is an integer variable. You then, subsequently assigned the value 25 to that variable. Variable values can be changed as the program progresses. You can assign new values to a variable at any time.
myAge = 46; // how did that happen?

Declare, Assign, Initialize


When you allocate space for the variable, you are said to declare the variable. When you give that variable a value, you are said to assign to it. You can combine these two steps into one; this is known as initializing the variable. Rather than writing,
int myAge; // declare an integer variable

myAge = 25; // assign a value to the variable

you can combine these steps into a single initialization:


int myAge = 25; // initialize an integer variable

You can declare more than one variable on a line at a time, as long as they all have the same type:
int myFirstVar, mySecondVar, myThirdVar;

2-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Variables
You can even initialize more than one variable on a line at the same time; again assuming they all are of the same type:
int myFirstVar = 5, mySecondVar = 7, myThirdVar = 9;

WriteLine Substitution Parameters


When you call WriteLine to print a value, you can use substitution parameters. These are numbers, starting with 0 and counting up, enclosed in braces {}. You place the value to substitute for the parameter after the close quote, separated by commas:
Console.WriteLine(My Age is {0}, myAge);

In this example, {0} is the substitution parameter, and the value in the variable myAge will be substituted, causing the following to print:
My Age is 25

See Substitution Parameters.sln

You can have more than one substitution parameter, as long as you count up from zero, and you ensure that the substituted values are listed in the order they are numbered.
int intOne = 5, intTwo = 7, intThree=9; Console.WriteLine( "one: {0}, two: {1}, three: {2}, one again: {0}", intOne, intTwo, intThree);

In this WriteLine statement there are four substitutions, using only three substitution parameters. The first, {0} is repeated at the end of the statement, and continues to refer to the very first substitution parameter (intOne). The result of running this code is shown in Figure 1.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

2-3

Language Fundamentals

Figure 1. Substitution parameters.

Notice that the first value (5) is shown twice. The {0} substitution parameter refers to the first value after the string (intOne) and you may use that as often as you like within the statement.

Definite Assignment
While you are not required to initialize or even assign to your variable, you may not use a variable that has not been either initialized or assigned a value. That is, you may not pass that variable to a method, nor may you assign that variable to another. See Definite Assignment.sln In the next listing, you create a variable and dont initialize it. You then try to pass that value to Console.WriteLine, but this generates an error, because you cant use a variable doesnt have a value assigned to it.
namespace DefiniteAssignment { class Class1 { static void Main() { int firstValue; int secondValue;

// unassigned local variable used - compile error Console.WriteLine("FirstValue: {0}", firstValue);

// unassigned local variable used - compile error secondValue = firstValue;

Both the call to Console.WriteLine and the assignment to secondValue will fail because firstValue does not have a value assigned to it. This is a great feature of the language. Assigning an uninitialized value can make it difficult 2-4 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Variables
to find bugs, but having this problem found by the compiler makes your life much easier. The error you will receive when you compile is shown in Figure 2.

Figure 2. Compile an error using an unassigned variable.

You can comment out the two offending lines, and then add the following code to test whether the program compiles:
// assign a value firstValue = 7;

// pass assigned value to WriteLine Console.WriteLine("FirstValue: {0}", firstValue);

// okay now to assign secondValue = firstValue; Console.WriteLine("SecondValue: {0}", secondValue);

By assigning a value to firstValue you can then use that variable both in method calls and on the right-hand side of an assignment statement.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

2-5

Language Fundamentals

Constants
Constants allow you to name a value that will not change while your program is running. C# supports three types of constants: Literal constants Symbolic constants Enumerations

Literal Constants
A literal constant is just a number you type into your code. For example:
int x = 46;

The value 46 is a literal constant. The numerals 46 represent the value forty-six and they cannot represent any other value. You cant define 46 to represent the value twenty-eight, no matter how hard you try. C# programmers try to avoid using literal constants. They are often referred to as magic numbers. They are magic because they cause magical bugs in your code. Often youll use the same value in more than one place in your code. If you use literals, and change the value in one place, you must remember to change it in all the others or magical bugs begin to appear. The alternative to literal constants is symbolic constants.

Symbolic Constants
Symbolic constants are very much like variables, except that their value never changes. Like a variable, a symbolic constant is a named, typed value, but the point of a symbolic constant is to take the place of a literal constant. Removing the magic numbers from your program makes your program easier to maintain and understand. Instead of writing the following:
int x = 100;

You might write: 2-6 C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Constants
// declare a constant const int BoilingPointCelsius = 100; // int x = BoilingPointCelsius;

There are a number of advantages to using the constant. First, if you use the value many times in your code, you can change the value of the constant and be assured that the new value will be used throughout the program once you recompile. Second, a well-named symbolic constant is itself a form of documentation. It is far more informative to state that you are assigning the BoilingPointCelsius constants value, then to simply state that you are assigning the value 100. In fact, if you choose your variable name well, the code is entirely transparent:
int maxTemperature = BoilingPointCelsius;

The well-named variable and the symbolic constant make the intent of the code nearly self-evident. You can clarify obscure code with comments, but maintaining comments is difficult and error prone (the code changes and you forget to update the comments). It is far better for the code itself to be crystal clear.

Symbolic Constants Must Be Initialized


Remember that constants cannot be assigned while the program is running. You must, therefore, initialize the constant when you create it. The following will not compile:
const int BoilingPoint; BoilingPoint = 100; // nope, must initialize

The only way to change the value for a constant is to stop the program, edit the source code, and then recompile.

Enumerations
An enumeration is a named set of constants. Each constant is assigned a value. Enumerations are typically based on int (and if you dont specify otherwise, int is used by default). You name an enumeration and put the enumerated constants within braces, as shown here: C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

2-7

Language Fundamentals
enum Color { Red, Orange, Yellow, Green, Blue, Indigo, Violet }

A comma separates each value. If you do not assign a value, the first enumerated value will be 1, and each subsequent value will count up. You are free to set values explicitly for some or all.
enum Temperature { FreezingPoint = 0, WickedCold = 10, WayCold, RoomTemperature = 72, Boiling = 100 }

In the Temperature enumeration, all the values are assigned except for the enumerated value WayCold, whose value is set to 11 (one more than the previous). You can use enumerated constants in many of the places you would use a symbolic constant, but if you want to get the underlying integer value you must cast to an int:
Console.WriteLine(Freezing point: {0}, (int) Temperature.FreezingPoint);

Notice that you access the FreezingPoint enumerated value by specifying the enumeration Temperature, just like accessing a property of a class. The enumeration Temperature creates a scope, or context, for the enumeration.

2-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Constants

Try It Out!
1. Open Visual Studio .Net. 2. Click New Project. Set the location to someplace convenient for your test program. 3. Choose Visual C# Projects from Project Types and then choose Console Application from Templates as shown in Figure 3.

Figure 3. Creating new project.

4. Name your test program VariablesAndConstants and click OK. 5. Navigate to the Main() method, where youll find the following comment:
// // TODO: Add code to start application here //

6. Remove the comment and type in the following code:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

2-9

Language Fundamentals
int numberDogs = 1; int numberCats = 3; int numberAnimals = numberDogs + numberCats;

Console.WriteLine("numberDogs: {0} + numberCats: {1} = numberAnimals: {2}", numberDogs, numberCats, numberAnimals);

Youve created three variables, assigned a value to each of the first two, and then assigned their sum to the third. Finally, youve printed out these values using WriteLine substitution parameters. 7. Add the following code:
const int maxAnimalsAllowed = 10; int numberAnimalsWeCanStillGet = maxAnimalsAllowed numberAnimals; Console.WriteLine("We can still get {0} more animals", numberAnimalsWeCanStillGet);

Youve now added a constant value, maxAnimalsWeCanGet and used that constant to compute the value of the variable numberAnimalsWeCanStillGet. While these variable names are long, they are self-explanatory and worth the extra typing. 8. Add the following enumeration within the class declaration, but before the Main method.

2-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Constants
class VariablesAndConstants { enum Temperature { WickedCold, OneDegreeWarmer, FreezingPoint = 32, Comfortable = 72, BoilingPoint = 212 } static void Main(string[] args) {

This adds the enumeration Temperature, which you can then refer to within the program. 9. Add the following code to use the Temperature enumeration:
Console.WriteLine( "Wicked cold: {0}, one degree warmer: {1}", (int)Temperature.WickedCold, (int)Temperature.OneDegreeWarmer);

Console.WriteLine( "The difference between boiling and freezing: {0}", Temperature.BoilingPoint Temperature.FreezingPoint);

Here youve used the enumerated constants both for displaying to the monitor and also for computation.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

2-11

Language Fundamentals

Types
C# is strongly typed. That means that when you create an object, you must declare its type. The type tells the compiler the size of the object in memory (i.e., how many bytes of memory the object occupies). The type also tells you what values the object may hold and it tells you the objects possible behavior. A strongly typed language does not allow you to just assign values to a variable willy-nilly; each variable has a type, and the values you assign must be appropriate to that type. This is a terrific feature in a language, because it enlists the compiler in helping you find bugs. If you declare that myAge will hold an int (integer) and then try to assign a date to that variable, the compiler will catch the error. Compiler errors are preferred over run-time errors.

Compile Errors vs. Run-Time Errors


Compile errors occur each time the program is compiled. If the error exists, the compiler will find it. Run-time errors get past the compiler, but show up when the program is running. Run-time errors are not reliable; they may occur with some runs of the program, but not with others. Compile errors (sometimes referred to as compiletime errors) are caught by the developer. Your Quality Assurance team may catch runtime errors, but if not, they will certainly be caught by your customers. Compile time errors are easier to find, easier to fix, and less embarrassing.

C# offers a number of built-in types, and you can create your own types using the class keyword (covered later in this course). The built-in, or intrinsic types are shown in Table 1. Notice that each C# type corresponds to an underlying .NET type. The .NET type is part of the Common Type System and is consistent across all .NET languages. NOTE Boolean values are always true or false, and ints are always 4 bytes.

2-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Types
Type byte char bool sbyte ushort int uint float double decimal long ulong 1 1 1 1 2 4 4 4 8 8 8 8 Size byte char Boolean Sbyte Uint16 Int32 UInt32 Single Double Decimal Int64 Uint64 Table 1. The intrinsic types. .NET Description Unsigned (0-255) Unicode characters true or false (note, no other values!) Signed (-128 to 127) Unsigned (0 to 65,535) -2,147,483,647 to 2,147,483,647 Unsigned (0 to 4,294,967,295) ~+/- 1.5*10-45 to ~+/-3.4*1038 ~+/-1.5*10-324 to ~+/-1.7*10308 (15-16 significant digits) Fixed-precision 28 digits and position of decimal point Signed 9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 Unsigned from 0 to 0xFFFFFFFFFFFFFFFF

Strings
In C# (unlike C and C++) a string is a first class member of the language. A string is a set of letters, typically a text sentence or phrase. You can declare strings and assign to them just as you would any other type. You can also initialize strings, combining the declaration and assignment in a single step:
string myString; myString = Hello; string myOtherString = World; // declare // assign // initialize

Strings are used throughout C# programs. Console applications use strings for output, while Windows applications often assign strings to the text field of labels, text boxes, and other UI controls. Strings are also used to store text provided by the user, and can be stored in a database in character, text, or memo fields.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

2-13

Language Fundamentals

Writing C# Programs
A C# program consists of source code. The source code is, essentially, stylized text files that follow precise rules. Even if you dont know C#, if you have any programming experience at all you can typically read through a C# program and guess at a great deal of what is going on.

Identifiers
C# programs abound in identifiers. An identifier is just the name you provide for types, methods, variables, constants, objects, and so forth. There are strict C# rules for which characters can be used in identifiers, but you can avoid problems and also make programs that are easier to read by limiting yourself strictly to characters, and when required, numerics. The convention, in C# is to use camel notation for variable names and to use Pascal notation for methods and most other identifiers.

Camel Notation
In camel notation, multiple words are put together into a single word. Each of the original words begins with a capital letter, except the first letter. Examples of camel notation include myInt, yourName, temperatureOutside, and allTheWorld.

Pascal Notation
Pascal notation is just like camel notation, except that the first word is also capitalized. Examples of Pascal notation include DrawWindow, MoveLeft, and DeleteContents.

Hungarian Notation
Hungarian Notation is named after Charles Simonyi, a Hungarian who was a senior programmer at Microsoft. The idea of Hungarian notation is to prepend variable names with one or more letters to indicate type. For example, you might write: 2-14 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Writing C# Programs
int iAge, iWeight;

The lowercase i at the beginning of each of these identifiers indicates that the value is an integer. Hungarian notation can be useful in simple procedural languages, but in object-oriented languages such as C#, in which you will be defining new types, Hungarian notation begins to break down. Many C# programmers use a limited form of Hungarian notation to indicate the type of user controls when building Web or Windows applications.
TextBox txtCompanyName; Button btnCommand; RadioButton rbYes, rbNo;

This can be helpful when working with many controls on a Windows or Web form, but is not a standard convention and can cause confusion when programmers evolve different conventions.

C# Statements
In C# any complete instruction is a statement. All statements must end with a semicolon. The following are valid C# statements:
int x; x = 23; int y = x; // a definition // an assignment // initialization

int z = SomeMethod(x); // a method call a = b = c = SomeOtherMethod(); // multiple assignments

Anywhere you can use a statement, you can use a block. A block is a set of statements surrounded by a pair of braces. For example, the if statement is followed by a statement:
if (someCondition) x = 5; // statement

The if statement can also be followed by a block:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

2-15

Language Fundamentals
if (someCondition) { x = 5; y = 7; SomeMethod(); z = 18; }

The syntax of the if statement is covered later in this course, but the key point here is that wherever a statement is legal you may use a block instead.

Expressions
Any statement that evaluates to a value, is called an expression. It can be very surprising how many statements are actually expressions as well. For example, every assignment or initialization is an expression; the statement evaluates to the assigned value.
x = 57; // evaluates to 57

int y = x; // evaluates to the value of x int a = b = c = x; HERE HERE HERE

This last statement consists of a series of expressions as shown in the following:


c = x;

evaluates to the value of x. That value is then assigned to b and again evaluates to the value of x. Finally, that expression is assigned to a, and again the result evaluates to the value of x. It is because the assignment evaluates to the value of whatever is on the right-hand side that you can chain the assignments together. It is as if you wrote:
c = x; b = c; int a = b;

2-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Writing C# Programs
All of these may be combined to a single statement, precisely because each is also an expression.

Whitespace
Whitespace is any separator within your code, such as a space, tab or new line. It is called whitespace because it is the whitespace between the black characters on a printed page. The C# compiler ignores extra whitespace, so anywhere you can use a space, you can also use a series of spaces, tabs, or new line characters. As far as the C# compiler is concerned, the following two statements are identical:
int myAge = 5; // statement 1 int myAge 5; = // statement 2

The extra spaces, tabs, and new lines are totally ignored by the compiler. Judicious use of whitespace can make your code much easier to read. While you may not yet know how a for statement works, you can easily see that the second of the following examples is easier to understand and read, than the first:
// statement 1 for (int x=5;x<10;x++){Console.WriteLine(x: {0},x);y++;}

// statement 2 for ( int x = 5; x < 10; x++ ) { Console.WriteLine(x: {0}, x); y++; }

Whitespace can help with readability and can help create programs that are easier to maintain.

Not All Spaces Are Optional


While you can choose to add additional spaces between identifiers, some spaces are not optional. The compiler will be confused and will issue an error C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

2-17

Language Fundamentals
if you do not add a whitespace after a type declaration, for example. The following two lines are not identical as far as the compiler is concerned:
int x = 5; // declare an int intx=5; // need space after int

Because the word int is a keyword, it needs a space after it. Whitespace in quoted strings is also not ignored. The following two lines will not print identical strings:
Console.WriteLine(Hello world); // note space Console.WriteLine(Helloworld); // note no space

The first of these will print Hello world, while the second will print Helloworld. Which one is considered to be the error is entirely up to the designer, but they are not interchangeable.

2-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Writing C# Programs

Summary
Variables are named typed storage locations. Each variable must have a type and the values assigned to that variable must be consistent with the type. You can combine declaration and assignment in initialization. C# requires definite assignmentvariables must have a value before they are used. Constants come in three flavors: literal, symbolic, and enumerated. Literal constants are just numbers (e.g., 52). Symbolic constants are like variables, except they must be initialized and they cannot change their value while the program is running. Enumerated constants allow you to create a set of related constants under a common enumeration identifier. A type tells you the size in memory and the behavior of an object. C# is strongly typedobjects have a specific type and the type limits the values they can store. Strings in C# are first class types, and are fully supported by the language. Identifiers are the names you assign to variables, constants, methods, etc. By convention, variables are identified with camel notation and virtually everything else with Pascal notation. C# instructions are called statements. Any statement that returns a value is an expression. The C# compiler ignores most extra whitespace.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

2-19

Language Fundamentals

(Review questions and answers on the following pages.)

2-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Writing C# Programs

Questions
1. What is a variable and when would you use one? 2. What are the three types of constants? 3. What is the difference between a symbolic constant and a variable? 4. What is the difference between an expression and a statement? 5. When does the compiler not ignore whitespace?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

2-21

Language Fundamentals

Answers
1. What is a variable and when would you use one?
A variable is a named memory location in which you can temporarily store a value. You might use a variable to hold a value entered by the user.

2. What are the three types of constants?


Literal (a number), symbolic (a name), and enumerated (a set of constants with a shared enumeration name)

3. What is the difference between a symbolic constant and a variable?


A symbolic constants value may not change while the program is running.

4. What is the difference between an expression and a statement?


An expression is a statement that evaluates to a value.

5. When does the compiler not ignore whitespace?


The compiler will not ignore whitespace that separates a keyword from surrounding text, and will not ignore whitespace within a string.

2-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Writing C# Programs

Lab 2: Language Fundamentals

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

2-23

Lab 2: Language Fundamentals

Lab 2 Overview
In this lab youll learn how to create variables, use constants, and display these values to the console. To complete this lab, youll need to work through two exercises: Using Variables Using Constants

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

2-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Using Variables

Using Variables
Objective
In this exercise, youll create a new Console application and create some variables. You will then display the variables to the console.

Things to Consider
What is a variable? How might the value in a variable change during the execution of the program?

Step-by-Step Instructions
1. Open Visual Studio .NET. 2. Click New Project. Set the location to someplace convenient for your test program. 3. Choose Visual C# Projects from Project Types and then choose Console Application from Templates as shown in Figure 4.

Figure 4. Creating the new project.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

2-25

Lab 2: Language Fundamentals


4. Navigate to the Main() method. Remove the comment and type in the following code:
int numberDeskTops = 8; int numberLapTops = 2; int numberComputers = numberDeskTops + numberLapTops;

Console.WriteLine("This office has {0} computers.", numberComputers); Console.WriteLine( "Of them {0} are desktops and {1} are laptops", numberDeskTops, numberLapTops);

5. Compile and build the application.

2-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Using Constants

Using Constants
Objective
In this exercise, youll create a program using a constant and an enumeration.

Things to Consider
When would you use an enumeration rather than a simple constant? When would you use constants rather than variables?

Step-by-Step Instructions
1. Create a new console application named UsingConstants. 2. Create an enumeration for shapes.
enum numSides { circle = 0, triangle = 3, square, pentagon, octagon = 8 };

3. Add the following code to Main.


const int myAge = 46; Console.WriteLine( "I am {0} years old and I know that...", myAge); Console.WriteLine( "A figure with 3 sides is a {0}, " + " but if it has 5 it is a {1}", numSides.triangle, numSides.pentagon);

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

2-27

Lab 2: Language Fundamentals


Compile and run the application. It should look like Figure 5.

Figure 5. Running the lab.

2-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Branching

Branching
Objectives
Differentiate between conditional and unconditional branching. Understand how method calls interrupt the execution path unconditionally, and how methods return values. Understand how the if statement allows conditional branching. See how the else statement and nested branching add flexibility and how the switch statement adds clarity. Discover how looping works in C# and see how the various looping constructs facilitate writing code that is easy to understand.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

3-1

Branching

Unconditional Branching
The normal course of events is for a program to execute in the order it was written. Each statement is executed, one after another. In tiny sample applications, that may be the entire story: the program begins, each statement is executed, and then the program ends, but if that were all there was, programs would be very limited and nearly useless. In any real program, the logic of the program branches. That branching can happen unconditionally, when a method is called, or conditionally, based on the state of some object in the program.

Method Calls
There are a number of ways a program can branch conditionally, and many of these will be discussed later in this course. The most common unconditional branch is a method. The normal course of events is for a program to execute in the order it is written. A method begins and each statement is executed in turn until the method ends. NOTE The terms method and function can be used interchangeably. C programmers tend to use the term function, while C++ and Java programmers tend to use the term method.

When a method is called, or invoked, the processing of the program branches to the start of the new method. When the method returns (finishes), processing resumes on the line immediately following the invocation of the method, back in the original calling method. Figure 1 illustrates that when the method returns, processing resumes on the line after the method call. Processing begins with the execution of Statement1 and then continues to the next statement, which is a method call for Function2. At this point, execution branches to Function2.

3-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Unconditional Branching

Figure 1. A branching diagram.

Within Function2 the first statement executes and then processing goes to the second statement, which again is a method call to Function3. In Function3 the first statement, Statement1 executes, followed by Statement2 and Statement3, in that order. When the function ends or when it hits a return statement, execution returns to the calling method, in this case Function2. Within Function2 execution resumes on the line immediately following the call to Function3; in this case Statement3. When Function2 ends, again execution resumes in the calling method, in this case Function1. Statements 3 and 4 are executed and then processing branches on the call to Function4. Each statement in Function4 executes in order. Statement1 is followed by Statement2, which in turn is followed by Statement3 and then Statement4. When Function4 completes, execution returns to Function1 at Statement4.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

3-3

Branching

Try It Out!
You can see the order of execution illustrated with a simple example. 1. Open a new project in Visual Studio .NET. 2. Under Project Types choose Visual C# Projects and within Templates choose Console Application. Name the project UnconditionalBranching as shown in Figure 2.

Figure 2. Creating the new project.

3. Replace the comment in Main with the following two lines of code:
Console.WriteLine("I'm in Main!"); FunctionOne();

4. Add the following methods to the program, just below Main, but within the braces for Class1:

3-4

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Unconditional Branching
static void FunctionOne() { Console.WriteLine("I'm in functionOne!"); FunctionTwo(); }

static void FunctionTwo() { Console.WriteLine("I'm in functionTwo!"); }

5. Compile the program with SHIFT+CTRL+B. You should not receive any errors. 6. Execute the program with CTRL+F5. Each method is invoked in turn, as shown in Figure 3.

Figure 3. Running the test branching program.

7. You can see the actual steps of invoking methods by putting a breakpoint next to the first executable line in Main(). You set the breakpoint by clicking in the margin next to the line you want to break on. A red dot appears, and the line is highlighted as shown in Figure 4.

Figure 4. Setting the breakpoint.

8. Execute to the breakpoint by pressing F5.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

3-5

Branching

TIP:

F5 runs the program in the debugger, while CTRL+F5 runs without the debugger.

9. Program executes to the breakpoint and then stops. Depending on how your environment is set up, you should now see a set of windows similar to those shown in Figure 5. In the main code window you see the current line (about to be executed) indicated with a yellow arrow. At the bottom of the screen may be a pair of useful windows. The Locals window shows the value of local variables. Youll learn more about this window later in the course. The lower right-hand corner has the Call Stack window, which shows the list of methods that led to the current method (more about this shortly).

Figure 5. Running to the breakpoint.

10. You can step through this program using F11. Pressing once executes the first statement, which writes to the output console. If you switch to the output console (click on it on the taskbar, or use ALT+TAB to switch among your applications) youll see that the line has been displayed to the console.

3-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Unconditional Branching
11. Step into the call to FunctionOne by pressing F11. The yellow current statement pointer jumps down into FunctionOne and the CallStack now shows two methods listed, FunctionOne() and Main(), as shown in Figure 6. This indicates that you are currently in FunctionOne, called by Main.

Figure 6. Step into FunctionOne.

12. You can continue stepping through the program, executing each line in turn, and branching unconditionally on the method calls.

Returning Values from Method Calls


Methods can return a value to the calling method. To do this, you must declare the type of value the method will return. Void indicates that the method does not return any value at all. So far, all the methods youve seen have returned void.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

3-7

Branching

Try It Out!
Follow these steps to create a method that returns an integer value, and youll use that value in the calling method. 1. Create a new console application and call it ReturnValues. 2. Replace the code in Main with the following:
Console.WriteLine("I'm in Main!"); FunctionOne();

3. Add two methods. FunctionOne returns void, but Doubler returns an int value:
static void FunctionOne() { Console.WriteLine("I'm in functionOne!"); int x = 5; int y; y = Doubler(x); Console.WriteLine("x was {0} and y is {1}", x,y); return; }

static int Doubler(int originalValue) { int newValue = return newValue; } originalValue * 2;

4. Put a breakpoint in Main() where you call FunctionOne and run to that breakpoint, as shown in Figure 7. 5. Step into FunctionOne. You declare a local variable x and initialize its value to 5. You then declare a variable y and assign to y the result of calling Doubler, passing in x. Step until that line is current.

3-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Unconditional Branching

Figure 7. In ReturnValues ready to call FunctionOne.

6. Hover the cursor over the x. Youll see that the value of x is displayed in a small window, and is also shown in the Locals window, as shown in Figure 8. 7. Step into Doubler, and examine the value of the parameter, originalValue. You can see in the Locals window that the value is 5, as youd expect. This is the value of x, the argument to Doubler as called from FunctionOne.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

3-9

Branching

Figure 8. Examining the value of x before invoking Doubler.

8. Within Doubler a new value is created and initialized to twice the parameter. It is this value that is returned in the return statement. 9. Examine the value of newValue before the method returns. Youll see in the Locals window that it is 10. 10. Continue stepping out of Doubler and back into FunctionOne. Step through the assignment to y and then examine y; it is now set to 10. The return value of Doubler was assigned to the local variable y. This is a common idiom with method calls.

3-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Conditional Branching

Conditional Branching
Far more interesting than unconditional branching, is conditional branching. With conditional branching, your program learns to make decisions based on the conditions of the moment. In real life you decide whether or not to open your umbrella based on local conditions. For example, if it is raining, you open the umbrella. If it is not raining, you do not (yes, there are always exceptions, but this is the general rule with umbrellas). In C# you might express this idea with the following code:
if (isRaining) OpenUmbrella();

The if Statement
The if statement tests a Boolean value or an expression that evaluates to a Boolean. Often that expression is a method call that returns a Boolean value. If the value evaluates true, the body of the if statement executes, if not, the statement is skipped. The body of an if statement can be a single statement, or as noted earlier, can be a block of statements enclosed within braces.

else
If statements can also have an else clause. If the expression evaluates true, then the body of the if statement is executed and the body of the else statement is skipped. If the expression evaluates false, then the body of the if statement is skipped and the body of the else statement is executed.
if ( myAge > 40 ) Console.WriteLine(You are over 40); else Console.WriteLine(Youth abounds!);

In this example, if the variable myAge is 50 then the Boolean expression myAge > 40 will evaluate true, and the statement You are over 40 will be displayed. If the value of myAge is 40 or less, however, then the if statement C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

3-11

Branching
will fail, and the else statement will execute, displaying Youth abounds! on the console.

A Nested if
You can nest an if statement within another if statement to test multiple conditions. For example, you can test the ambient temperature to see if water is solid, liquid, or gaseous with the following code:
if ( temperature > freezingPoint ) { if ( temperature < boilingPoint ) Console.WriteLine(Water is liquid); else Console.WriteLine(Water is gaseous); } else ConsoleWriteLine(Water is solid);

The outer if statement tests whether the current temperature is greater than the freezingPoint. If it is not, the else statement executes and the string, Water is solid is displayed. If the outer if statement evaluates true (that is, if the current temperature is greater than the freezing point), then the block of statement executes. Within that block, you see an inner, nested if statement. This inner if tests whether the current temperature is less than the boiling point, and if so it displays the message Water is liquid. Otherwise, if the temperature is greater than the boiling point, the string Water is gaseous is displayed. Nested if statements work well for situations where one condition depends on another. For this particular example, however, you can write code that is easier to understand by using another construct: the switch statement.

Switch
The switch statement allows you to choose among a series of values, taking action on whichever value matches. Switch statements are typically used when you branch, or take other actions, based on one of a series of values. The formal syntax for a switch statement is as follows:

3-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Conditional Branching
switch (expression) { case constant-expression: statement jump-statement [default: statement] }

See Switch Statements.sln

The easiest way to see how a switch statement is used is with an example. In the following example, you will switch on the value entered by a user.
using System;

namespace SwitchStatements { class SwitchStatements { static void Main() { // print the menu of choices Console.Write( "(1) Walk (2) Run [1,2,3,4]: "); (3) Crawl (4) Falling

// read the user's choice from the keyboard string choice = Console.ReadLine();

// convert the choice to an integer int menuChoice = Convert.ToInt32(choice);

// switch on the choice and choose // the appropriate action switch (menuChoice) { case 1: Console.WriteLine("Walking..."); break; case 2:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

3-13

Branching
Console.WriteLine("Running..."); goto case 4; case 3: Console.WriteLine("Crawling..."); break; case 4: Console.WriteLine("Falling..."); break; } } } } // end switch // end Main // end class SwitchStatements // end namespace SwitchStatements

The example begins by printing a menu to the console:


Console.Write( "(1) Walk (2) Run (3) Crawl (4) Falling [1,2,3,4]: ")

The user chooses a value of 1, 2, 3, or 4. You read the users choice with the ReadLine() method of Console, which reads a single line (ended by pressing enter) from the console. You then assign that string value to an integer by calling the ToInt32 method of the Convert object:
string choice = Console.ReadLine(); int menuChoice = Convert.ToInt32(choice);

NOTE

ToInt32 is a static method of the Convert object, as explained later in this course. For now, you can just treat this as a magic ability to convert strings to integers.

You are ready to switch on the menuChoice value, taking the appropriate action depending on the users selection. If the user entered 1 to indicate that he wants to walk, you will match the case where the value is 1. In that case you will take whatever action is appropriate. To keep this example simple, youll print a message to the console:

3-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Conditional Branching
switch (menuChoice) |{ case 1: Console.WriteLine("Walking..."); break;

The keyword break indicates that the handling of the case is completed and processing can fall out of the switch statement. If the user entered 2 to indicate that they want to run, then the first case would be skipped and the second case would match:
case 2: Console.WriteLine("Running..."); goto case 4;

This case also prints a message to the console. This time, however, rather than ending with break this statement ends with goto. Goto indicates that you want to continue as if another case had matched, in this case 4. The result of choosing 2 (run) is that both case 2 and case 4 will execute (in that order). Thus, the output will be Running Falling which is not inappropriate.

Default
The switch statement may have a default case that will execute if none of the other choices do. You can add a default case to the code shown just below the fourth case:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

3-15

Branching
switch (menuChoice) { case 1: Console.WriteLine("Walking..."); break; case 2: Console.WriteLine("Running..."); goto case 4; case 3: Console.WriteLine("Crawling..."); break; case 4: Console.WriteLine("Falling..."); break; default: Console.WriteLine( "I'm sorry, that is not a valid choice"); break; } // end switch

If the user enters an invalid value (e.g., 6), the default case executes and the string Im sorry, that is not a valid choice displays.

3-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Looping

Looping
One of the principal mechanisms for writing useful programs is the ability to loop through a set of instructions, repeating each instruction until a condition is met. C# supports a number of looping instructions, including: goto while dowhile for foreach

Each of these constructs, except the foreach loop is illustrated in the following example. Youll learn more about the foreach loop later in this course when collections are covered.
using System;

namespace Looping { class Looping { static void Main() {

// goto Console.WriteLine("goto..."); int i = 0; repeat: Console.Write("{0} ", i); i++; if (i < 10) goto repeat;

// while Console.WriteLine("\n\nWhile...");

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

3-17

Branching
i = 0; while (i < 10) { Console.Write("{0} ", i); i++; }

// do...while Console.WriteLine("\n\nDo While..."); i = 0; do { Console.Write("{0} ", i); i++; } while (i < 10);

// for Console.WriteLine("\n\nFor..."); for (i = 0; i < 10; i++) { Console.Write("{0} ", i); } Console.WriteLine("\n"); } } }

The best way to analyze this demonstration program is section by section.

goto
The venerable goto statement is not much respected these days (except when used within a switch statement as shown above), but it is the root of all looping. Using goto is quite straightforward, and requires only three steps: 1. Create a label. 2. Perform some work. 3. Goto the label if some condition is met. 3-18 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Looping
Set up your state by initializing the integer variable i to zero. Then create the label: repeat and begin work. In this case, the work is nothing more than printing out the value of i and then incrementing i. The ++ operator adds one to the value of i. Test the value of i, and if it is less than ten, go to the label and loop through printing and incrementing the value again:
// goto Console.WriteLine("goto..."); int i = 0; repeat: Console.Write("{0} ", i); i++; if (i < 10) goto repeat;

The result of running just this block of code is shown in Figure 9.

Figure 9. The goto loop.

To see how this works, put a breakpoint at the initialization of i and step through the loop. Each time through, youll see i incremented and then the test in the if statement. Each time that i is less than 10 the goto executes, returning you to the repeat label. Once i is 10, the if statement fails and the program ends.

The Trouble with goto


Goto has a terrible reputation with computer scientists, and for good reason. If you imagine that there is a line drawn in your code indicating the order of execution, goto statements can cause the line of execution to loop over itself. Repeated goto statements, sprinkled liberally throughout the code can create loops within loops that look not unlike a plate of spaghetti. This is why goto statements are said to create spaghetti code. The truth is somewhat more complicated: goto statements are not inherently evil. You can write fine, structured code containing goto. The problem is that goto

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

3-19

Branching
statements also allow you to write horrible, unstructured code. The solution is to use more structured constructs, such as while, do while, and for loops.

while
The while loop shown in the listing is a good example of how you can accomplish the same goal you did with the goto statement, but in a more structured statement. The semantics of a while statement are: While this condition remains true, take this action. While i is less than ten, print its value.
i = 0; while (i < 10) { Console.Write("{0} ", i); i++; }

NOTE

We use the value i for counting variables in this and many other examples. This is a long tradition in computer programming, and in fact it goes all the way back to an early programming language: Fortran. In Fortran the only legal counting variables were i, j, k, and l. Many programmers continue this tradition of using i, j, k, and l for simple counting variables, even if they dont know why.

What is appealing about this code is that the braces enclose the entire action, and the while statement sets the condition. You can see how this works and there is no jumping all over the code willy-nilly. The condition is tested, and if it evaluates true (i is less than ten) the action within the braces is executed. The result of running the while loop is shown in Figure 10.

3-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Looping

Figure 10. Executing the while loop.

You can see that the output from the while loop is identical to the goto, but the code is somewhat cleaner. In large programs the code is infinitely cleaner.

dowhile
The while loop has one feature that may represent a limitation in certain circumstances. The condition (i < 10) is tested before the loop is executed. Notice that just before testing the while loop, youve assigned the value 0 to i. This is necessary because the previous test of goto left i with the value of 10. If you comment out the assignment just before running the while loop, the results are dramatically different, as shown in Figure 11.

Figure 11. Commenting out the assignment to i.

This time the while loop does not print any values at all, because the test (i < 10) fails, and the body never executes. There are times when you want to ensure that your loop runs at least once. For this, use a do..while loop. A do..while loop works just like a while loop, except C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

3-21

Branching
that the test is executed after the statement executes. In most circumstances, this wont make any difference. If you initialize the i to 0 each time, the loops will run identically, as shown in Figure 12.

Figure 12. While and DoWhile when i = 0.

If, however, you remove the initialization of i to 0 both in the while and the do..while loops, the difference is shown, as seen in Figure 13.

3-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Looping

Figure 13. While and do while when i = 10.

This time i is not set to 0, it is left at 10. The while loop test fails, so nothing is printed. The do..while loop executes once and then the value is tested. Since the test fails, the do..while loop is not executed again.

for Loops
Most loops, in one way or another, replicate these steps: 1. Set an initial condition. 2. Test the condition and if the test evaluates true, execute the body of the loop. 3. Change the condition. 4. Test the condition and if the test evaluates true, execute the body of the loop. 5. Repeat Steps 3 and 4 while the condition remains true. The for loop combines Steps 2 through 4 into a single statement. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

3-23

Branching
The head of the for loop (within the parentheses) is divided into three sections, separated by semicolons. The first section sets the initial conditions. The second section is the test, and the third section is the action to take after the body of the for statement executes.
for (i = 0; i < 10; i++) { Console.Write("{0} ", i); }

In the code shown, the variable i is initialized to 0 in the first section. This section only executes once; the first time the loop is run. The second section is the test; the variable is tested to ensure its value is less than 10. If so, the body of the for loop (within the braces) is run. When the body completes, the third section executes (i++) and then the test is executed again. If the test evaluates true, the body is run again. This repeats until the test fails. You can leave any of the sections blank and can move the initialization out of the for loop:
i = 0; for (; i < 10; i++) { Console.Write("{0} ", i); }

You can move the action into the body of the loop:
Console.WriteLine("\n\nFor..."); i = 0; for (; i < 10; ) { Console.Write("{0} ", i); i++; }

You can, if you are particularly ornery, even move the test into the body of the loop: 3-24 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Looping
i = 0; for (;; ) { Console.Write("{0} ", i); i++; if (i >= 10) break; }

This does somewhat undermine the point of the for loop, but it also illustrates quite dramatically that each part of the for loop is optional. You must hold its place with the semicolons, and you can break out of a for loop (or any loop) with the break keyword.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

3-25

Branching

Summary
Program execution continues in the order of the program statements, unless interrupted by a branching statement. Branching can be conditional or unconditional. Unconditional branching is most often accomplished with a method call. When a method is invoked, program execution branches to the top of the. When the method returns, execution resumes with the statement in the calling method that immediately follows the method invocation. A value may be returned by a method and that value may be used locally within the calling method. Conditional branching can be accomplished with the if statement. If statements may have an else clause and may also have other if statements nested within them. The switch statement may be used to selectively branch based on a particular value. C# programs can loop using a number of constructs. The most primitive looping construct is the if statement, but C# also supports the while, do..while, and for loop.

3-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Looping

Questions
1. What is the difference between a conditional and an unconditional branch? 2. What is the return type to indicate that no value will be returned by a method? 3. When is an else statement executed? 4. What is the disadvantage of goto? 5. Why would you use do..while rather than while? 6. What is the advantage of using a for loop rather than a while loop?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

3-27

Branching

Answers
1. What is the difference between a conditional and an unconditional branch?
A conditional branch depends on the Boolean state of an expression being tested. An unconditional branch occurs no matter what the state of the program.

2. What is the return type to indicate that no value will be returned by a method?
The return type to indicate that no value is returned is void.

3. When is an else statement executed?


An else statement is executed when the expression tested by its if statement evaluates false. You cannot have an else statement without an if statement.

4. What is the disadvantage of goto?


Programs using goto branch n ways that are hard to trace when examining the code, and create code that is difficult to understand and maintain.

5. Why would you use do..while rather than while?


The do..while loop is guaranteed to run at least once, since the test executes after the body of the loop. Use do..while anytime you must ensure that the body of the loop will execute at least once.

6. What is the advantage of using a for loop rather than a while loop?
The for loop combines the initialization, test, and update of the trackng value into a single header statement. for loops are generally cleaner and easier to understand.

3-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Looping

Lab 3: Branching

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

3-29

Lab 3: Branching

Lab 3 Overview
In this lab youll learn how to work with conditional statements and how to work with loops. To complete this lab, youll need to work through two exercises: Branching Your Program Using Conditionals Repeating Work with Looping

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

3-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Branching Your Program Using Conditionals

Branching Your Program Using Conditionals


Objective
In this exercise, youll create code that branches based on a condition.

Things to Consider
When would you use if/else vs. switch? How might you exit a program quickly if you cannot continue?

Step-by-Step Instructions
1. Create a new console application. 2. Within Main, ask the user for his/her age. If the user is under 21, write a message and exit the program:
using System;

namespace Conditionals { class GuessIt { static void Main() {

Console.Write("Enter your age: ");

// read the user's age from the keyboard string strAge = Console.ReadLine();

// convert it to an integer int age = Convert.ToInt32(strAge);

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

3-31

Lab 3: Branching
// don't let them continue if they're not of age if (age < 21) { Console.WriteLine( "Oh, you are just too young to play this game!"); return; } else Console.WriteLine( "Step right up, and watch carefully....");

3. Prompt the user to guess a number between 1 and 10. Convert the value to an integer and switch on the value, printing appropriate messages based on how close they are to the value that the computer is thinking of (4).
Console.Write( "Enter a number between 1 and 10: ");

// read the user's guess from the keyboard string strGuess = Console.ReadLine();

// convert the guess to an integer int guess = Convert.ToInt32(strGuess);

// switch on the guess and choose // the appropriate action switch (guess) { case 1: case 2: Console.WriteLine("Nope, way too low..."); break; case 3: Console.WriteLine("Very close..."); break; case 4:

3-32

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Branching Your Program Using Conditionals


Console.WriteLine("You got it!!"); break; case 5: Console.WriteLine("You just missed it!"); break; case 6: case 7: Console.WriteLine("A bit high..."); break; case 8: case 9: case 10: Console.WriteLine("Way too high!"); break; default: Console.WriteLine( "I'm sorry, that is not a valid guess"); break; } } } } // end switch // end Main // end class GuessIt // end namespace

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

3-33

Lab 3: Branching

Repeating Work with Looping


Objective
In this exercise, youll add loops to the previous game to make it more interesting.

Things to Consider
What kind of loop will work best to allow the user to keep playing after each guess? What is the condition for ending the loop?

Step-by-Step Instructions
1. Reopen the previous exercise. 2. Modify the code to declare the integer variable guess before you prompt for the number, and initialize it to 0.
int guess = 0;

3. Create the while loop, testing the new value guess, and continuing the loop until guess is equal to 4.
int guess = 0; while (guess != 4) { Console.Write("Enter a number between 1 and 10: ");

// read the user's guess from the keyboard string strGuess = Console.ReadLine();

// note that you no longer declare guess here guess = Convert.ToInt32(strGuess);

3-34

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Repeating Work with Looping


4. The rest of the code is unchanged. Remember to close the while loop (with a closing brace).
default: Console.WriteLine( "I'm sorry, that is not a valid guess"); break; } } } } } // end while // end switch // end Main // end class GuessIt // end namespace

5. Compile and run your program as shown in Figure 14.

Figure 14. Running the looping application.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

3-35

Lab 3: Branching

3-36

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Operators

Operators
Objectives
Understand the various categories of operators. Explore how relational operators and logical operators work together. Use short circuit evaluation. Use parentheses to change the order of precedence.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

4-1

Operators

Operator Types
C# supports a number of operators, including: Relational operators (>, <, ==) Assignment (=) Mathematical (*,/,-,+) Logical (&&, ||, !)

When you looked at if statements (and many of the branching statements) you tested for certain conditions, such as whether two values were equal or one was greater than another. These are examples of relational operators. The assignment operator may be the operator used most in C#. You will often find yourself assigning the value of one object to another, or assigning to an object the value returned by a method. Mathematical operators are used less frequently, but many programs involve simple arithmetic, and nearly all C# programs use the increment operator (++) in loops. Logical operators are used when you are testing whether either or both of two (or more) conditions are true.

Relational Operators
The relational operators include: Equals (==) Not Equals (!=) Greater than (>) Greater than or equal to (>=) Less than (<) Less than or equal to (<=)
It is a common mistake to write the assignment operator, which is a single equal sign (=), when you mean to use the equals operator, which is a pair of equal signs with no space between them (==).

WARNING!

4-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Operator Types
Relational operators always evaluate to a Boolean (either true or false). Given two values and a relational operator, the entire expression is either true or it is not. For example:
7 < 5; 7 > 5; 5 > 5; 5 >= 5; // evaluates false // evaluates true // evaluates false // evaluates true

You will almost never compare two literal constants in this way; typically youll evaluate two variables or objects:
myAge < yourAge

And typically, youll do this evaluation within a test. Most often youll use an if statement or a while or for statement:
if ( myAge < yourAge ) // take some action while ( turnsLeft > 0 ) // take some action for ( i = 0; i < 10; i++ ) // take some action

In each of these cases, the evaluated expression will return true or false.

Mathematical Operators
The mathematical operators generally work the way youd expect. The addition operator (+) adds two values, the subtraction operator (-) returns the difference between two values. When you multiply two values (*) the expression evaluates to their product:
z = x * y; // assign to z the product of x and y

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

4-3

Operators

Division
There are no surprises with addition, subtraction, or multiplication. When you divide two int values, however, the result is what youd get in fourth grade: that is, the quotient is truncated! You will remember that back in fourth grade, the problem (13/5) had the answer 2, with a remainder of 3. That is, 5 went into 13 twice, with 3 left over. C# integer division works the same way. In the following expression, x has the value 2. The remainder is lost.
int x = 13 / 5;

When you divide doubles and floats, of course, you do get a decimal result, to the limits of the precision of the type.

Modulus
You can get at the remainder value in integer division, by using the modulus operator (%). Modulus returns only the remainder. The following expression evaluates to 3, because 13/5 is 2 with a remainder of 3, and % returns only the remainder:
int x = 13 % 5

It turns out that modulus is quite useful for finding every nth occurrence in a loop. See Operator.sln For example, to find every tenth occurrence in a loop, you might write something like the following:
for (int i = 11; i <= 100; i++) { Console.Write("{0} ",i); if (i % 10 == 0) Console.WriteLine("\t {0}", i); }

This code writes the values 11 through 100. After every ten values, it writes a tab and then writes the number followed by a new line. The result is shown in Figure 1.

4-4

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Operator Types

Figure 1. The modulus operator.

The first time through the loop, i has the value 11. The modulus operator returns the value 1 and the if statement fails:
if (i % 10 == 0)

11 % 10 is 1 because 11 / 10 is 1 with 1 left over. The second time through the loop i is 12, and 12 % 10 is 2, again the if fails, two is not equal to zero. This continues until i is 20. At that point the modulus operator returns 0 (20 % 10) which is equal to zero, and so the if statement is executed.

Concatenation
The addition operator (+) is also used to concatenate strings:
string s1 = hello; string s2 = world; string s3 = s1 + s2;

The result will be that s3 contains the string hello world. This has become such a common idiom in programming that few find it confusing. NOTE The space at the beginning of string s2 allows the concatenated string to be hello world rather than helloworld.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

4-5

Operators

Self-Applied Operators
It is common to want to increment a variable or multiply a variable by a value, and then assign that value back to the variable itself. In C# you can, as you do in other languages, write the resulting value back to the variable:
myVariable = myVariable + 5;

If myVariable had the value 7 before this statement, it would now contain the value 12. Similarly, you can write:
int a = 5, b = 12; a = a * b;

After these two statements, variable a will have the value 60. This is such a common idiom, that C# provides a shorthand. The statement,
a = a * b;

is exactly equivalent to the statement,


a *= b;

The latter is just shorthand for multiply myself times b and reassign the value back to me. This shorthand can be applied to any of the mathematical operators:
a += b; a -= b; a *= b; a /= b; a %= b; // add b // subtract b // multiply by b // divide by b // modulus b

4-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Operator Types

Increment and Decrement


Adding or subtracting a single value to a variable is such a common occurrence that C# supports operators that do exactly that: the increment operator adds one to a value, and the decrement operator subtracts one. In addition, each of these comes in two flavors: prefix and postfix.

Prefix vs. Postfix Operator


The semantics of the prefix increment operator (++x) are increment and then fetch. The semantics of the postfix increment operator (x++) are fetch and then increment. You can see the difference by examining the following lines of code:
int a = 5, b = 7, c = 9, d = 11; a = ++b; // a = 8, b = 8 c = d++; // c = 11, d = 12

The first line initializes four int variables. The second line assigns to a the result of the prefix increment operator on b. The prefix operator increments b and then returns the value, and the incremented value is assigned to a. Since b started out as 7, the incremented value is 8, and both a and b now have the value of 8. The third line assigns to c the result of calling the postfix operator on d. The semantics here are to return the current value of d (11) and then to increment d. Thus, c has 11 and d has 12.

Logical Operators
C# supports three logical operators: && (Logical AND) evaluates true if both operand expressions are true. || (Logical OR) evaluates true if either operand expression is true. ! (Logical NOT) evaluates true if the operand expression is false.

One way to understand these operators is to examine their impact given a test case. If you assume that you have two integer variables, x and y, and that x has the value 5 and y has the value 12 then the following code will evaluate false, because while x does equal 5, y does not equal 7 and for the logical AND to evaluate true, both operands must be true.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

4-7

Operators
(x == 5) && (y == 7)

On the other hand, the following expression does evaluate true, because even though y does not equal 7, x does equal 5 and logical OR evaluates true if either operand is true. You can read this as x is 5 or y is 7 and the answer to that is true.
(x==5) || (y == 7)

Finally, the following expression will evaluate true, precisely because x is not equal to 3. That is, the inner expression x == 3 evaluates false, and so the entire expression !(x==3) evaluates true. It is as if you said, It is false that x is equal to 3. That statement is true, it is false that x is 3.
!(x == 3)

Short Circuit Evaluation


The logical operators are typically used in tests, often in if statements, so you might see code like the following:
if ( (x == 3) && (y == 12) )

The body of the if statement will be executed only if the logical statement evaluates true, which in this case it will do only if x is equal to three and y is equal to 12. It turns out that the compiler may not need to evaluate the right side of a logical expression if the answer can be determined from the left side alone. For example, in a logical AND statement (&&) there is no need to evaluate the right side of the expression if the left side is false. Since they must both be true, if the left is false then the entire statement is false regardless of the value on the right. Similarly, in an OR statement, if the left statement is true, then it doesnt matter whether the right statement is true or not, the logical OR condition is satisfied if either side is true. You can test short circuit evaluation by creating a couple of methods that return Boolean values:

4-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Operator Types
static bool ReturnsTrue() { Console.WriteLine("In ReturnsTrue()."); return true; } static bool ReturnsFalse() { Console.WriteLine("In ReturnsFalse()."); return false; }

The first of these, returnsTrue always returns the value true, and the second, returnsFalse always returns false. You can now set up test conditions:
if (ReturnsTrue() || ReturnsFalse()) Console.WriteLine ("or statement worked!");

If you run this code, the call to ReturnsFalse will never be executed, as shown in Figure 2. The first half of the OR statement evaluates true, and so the OR condition is satisfied. There is no reason to call ReturnsFalse; it doesnt matter whether it returns true or false; the expression is already true, and the if statement executes.

Figure 2. Testing the OR with short circuit evaluation.

If you reverse the order of the test the second method must be tested:
//if (ReturnsTrue() || ReturnsFalse()) if (ReturnsFalse() || ReturnsTrue()) Console.WriteLine ("The || statement worked!");

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

4-9

Operators
The first method runs and returns false. Now the OR statement must execute the right side to see if it will return true. It does, so the OR statement is satisfied, as shown in Figure 3.

Figure 3. Reversing the test.

Similarly, you can set up an AND statement that will take advantage of shortcircuit evaluation:
if (ReturnsFalse() && ReturnsTrue()) Console.WriteLine ("The && statement worked!"); else Console.WriteLine("Nope, the && statement failed.");

In this case, once the first method call, ReturnsFalse returns a value of false, there is no point in checking the second method call. Whether or not it returns true, the entire if statement has already failed its test, because the AND statement must evaluate true for both operands. The result is that the else clause is invoked, as shown in Figure 4.

Figure 4. Short circuit evaluation of the AND statement.

4-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Operator Types

Try It Out!
See operators.sln To get a good handle on operators, youll create a simple console application.

1. Open a new console application, and name it Operators. 2. Set up a series of variables.
int intA, intB; float floatA, floatB; intA = 12; intB= 7; floatA = 12; floatB= 7;

3. You are now ready to explore the arithmetic operators for addition, subtraction, multiplication, and division.
Console.WriteLine("Arithmetic operators...");

Console.WriteLine("intA + intB: {0}", intA + intB); Console.WriteLine("intA - intB: {0}", intA-intB); Console.WriteLine("intA * intB: {0}", intA * intB); Console.WriteLine("intA / intB: {0}", intA / intB); Console.WriteLine("intA % intB: {0}", intA % intB);

Console.WriteLine( "\nfloatA + floatB: {0}", floatA + floatB); Console.WriteLine( "floatA - floatB: {0}", floatA-floatB); Console.WriteLine( "floatA * floatB: {0}", floatA * floatB); Console.WriteLine( "floatA / floatB: {0}", floatA / floatB); Console.WriteLine( "floatA % floatB: {0}", floatA % floatB);

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

4-11

Operators
4. Compile and run the program. The results are shown in Figure 5, but there are a few things to note. The console output for the first float variable begins with \n:
Console.WriteLine( "\nfloatA + floatB: {0}", floatA + floatB);

The \n character sequence is an escape sequence that represents a new line character. That is why there is an extra vertical space between the int examples and the float examples. Note also that the output is identical for addition, subtraction, and multiplication, but the int example uses integer division (with truncation), and the float shows the decimal value.

Figure 5. The arithmetic operators.

5. Add the following code to the program:

4-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Operator Types
int x = 5; int z = 0;

Console.WriteLine("\n\nx: {0}", x); z = ++x; Console.WriteLine("int z = ++x; x: {0}, z: {1}", x, z);

Console.WriteLine("\n\nx: {0}", x); z = x++; Console.WriteLine("z = x++; x: {0}, z: {1}", x, z);

Console.WriteLine("\n\nx: {0}", x); z = x--; Console.WriteLine("z = x--; x: {0}, z: {1}", x, z);

Here you create two integer variables, x and z, initializing x to 5 and z to 0. You then apply the prefix increment operator to x and assign the value to z, displaying the result. You then apply the postfix increment operator and finally the postfix decrement operator. 6. To simplify the output, comment out the earlier code, and run the program. The results are shown in Figure 6. You can see the differing impact of prefix and postfix. In the first example, x is incremented before its value is fetched, so both x and z have the incremented value of 6.

Figure 6. Increment and decrement.

In the second example, the value of x (6) is fetched and assigned to z, and then x is incremented to 7. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

4-13

Operators
In the third example, the value in x (7) is assigned to z, and then x is decremented to 6. 7. Return to the code and comment out what you have so far, adding the following:
int testValue = 7;

if (testValue == 6) Console.WriteLine("testValue is 6!");

if (testValue != 6) Console.WriteLine("testValue is not 6!");

if (testValue > 6) Console.WriteLine("testValue is greater than 6");

if (testValue >= 6) Console.WriteLine( "testValue is equal to or greater than 6");

8. Compile and run this test code to see the impact of the relational operators. The results are shown in Figure 7.

Figure 7. Relational operators.

The program begins by initializing x to the value 7. The first test fails because testValue is not equal to 6 and so nothing is printed:
if (testValue == 6) Console.WriteLine("testValue is 6!");

4-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Operator Types
The second test succeeds, and the message is printed:
if (testValue != 6) Console.WriteLine("testValue is not 6!");

The final two tests succeed as well; the value 7 is both greater than 6 and also greater than or equal to 6.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

4-15

Operators

Operator Precedence
Operators are evaluated in a very strict order as established by the rules of precedence. You may remember some of these rules from high school algebra. For example, multiplication has a higher precedence than addition:
nt x = 8 + 5 * 3;

After this statement executes, x will have the value 23 rather than 39, because the order of operations is 5*3 (15) plus 8 (23), rather than 8+5 (13) times 3 (39). Every operator has its own precedence level, and operators with higher precedence levels are invoked first.

Using Parentheses
You can change the order of precedence by using parentheses. For example:
int x = (8 + 5) * 3;

In this example, x evaluates to 39. The parentheses are evaluated first, and the operation is 8 + 5 (13) times 3 or 39. Using parentheses makes your program easier to read and maintain. You may want to use parentheses even when they dont change the order of operations, if only to document your intent.

4-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Preprocessor

Preprocessor
Preprocessor directives are not, strictly speaking, operators, but they act as special instructions to the C# compiler and precompiler. The preprocessor is an operation that runs before your program is compiled. Preprocessor directives allow you to provide special instructions to the compiler, which is useful when you want to control what portions of your program are compiled. Preprocessor directives begin with the character #. This must be the very first character on the line, and there must be no space between the # and the directive name. For example, the directive might be:
#define

The #define directive defines an identifier within the preprocessor. You can then test whether that identifier has been defined using #if and #endif. If the test succeeds, the code within the if/endif block will be compiled; otherwise it will not be compiled and will not be in the executable code.
#define Debug #if Debug // compile the code here // only if debug is defined #endif

You can also add an else clause to the if statement:


#define Debug #if Debug // compile the code here // only if debug is defined #else // compile this code only if // debug is NOT defined #endif

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

4-17

Operators
Finally, you can add elif clauses to create complex switch-like statements:
#define Debug #if Debug // compile the code here // only if debug is defined #elif Test // compile if debug is not defined // but test is #else // compile this code only if // debug and test are both NOT defined #endif

Typically, preprocessor directives are used when you want to mark a section of code to be compiled only if an identifer exists. Note that the identifier does not have to have a specific value, it just has to be defined. You might use preprocessor directives when you want to mark out an area of code to be compiled only when debugging. You might also use it for compiling different versions for different customers or platforms.

4-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Preprocessor

Summary
C# supports a wide variety of operators, including relational, mathematical, and logical operators. The mathematical operators act as you might expect, except that integers perform integer division, truncating the result. You can find the remainder in integer division by using the modulus (%) operator. You can concatenate two strings using the addition operator (+). C# provides both a prefix and a postfix increment (and decrement) operator. The semantics of prefix are increment and then fetch, while the semantics of post fix are fetch and then increment. The Logical operators && and || can be short circuited. All operators have precedence, but the order of evaluation can be overridden by using parentheses.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

4-19

Operators

(Review questions and answers on the following pages.)

4-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Preprocessor

Questions
1. What is a practical use for the modulus operator? 2. What is the difference between integer multiplication and multiplication with floats? 3. What is the difference between using the prefix increment operator vs. the postfix increment operator? 4. How can you change the order in which operators are evaluated? 5. What is the code to test if DEBUG has been defined for the preprocessor?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

4-21

Operators

Answers
1. What is a practical use for the modulus operator?
The modulus operator may be used during loops to find intervals. When you modulus a number with an exact multiple the result is 0. You can test for that condition to take action every nth iteration.

2. What is the difference between integer multiplication and multiplication with floats?
None, it was a trick question. Multiplication, addition, and subtraction are identical; integer division however, is quite different from division with floats.

3. What is the difference between using the prefix increment operator vs. the postfix increment operator?
The prefix operator increments the value before it is assigned, while the postfix operator assigns the original value and then increments.

4. How can you change the order in which operators are evaluated?
Use parentheses. Parentheses have the highest precedence and are evaluated first.

5. What is the code to test if DEBUG has been defined for the preprocessor?
#if Debug //code here #endif

4-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Preprocessor

Lab 4: Operators

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

4-23

Lab 4: Operators

Lab 4 Overview
In this lab youll learn how to use operators in your program To complete this lab, youll need to work through two exercises: Using Mathematical Operators Comparing Values with the Comparison Operators

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

4-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Using Mathematical Operators

Using Mathematical Operators


Objective
In this exercise, youll use mathematical operators to manipulate values.

Things to Consider
What is the difference between division with integers and floats? What does modulus do with integers and floats?

Step-by-Step Instructions
1. Open the project named Operators.sln in the Operators folder. This project has been prepared for you. 2. Fill in the values for the WriteLine statements. For example, the value you might fill in for the first WriteLine would be:
Console.WriteLine("firstInt + secondInt: {0}", firstInt + secondInt);

3. Fill in the operator in the if statement, replacing the question mark. Your goal is to have the if statement execute every fifth value. 4. Compile and run the program.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

4-25

Lab 4: Operators

Comparing Values with the Comparison Operators


Objective
In this exercise, youll work with the comparison operators.

Things to Consider
What is the difference between > and >=?

Step-by-Step Instructions
1. Open the program named Comparison Operators.sln in the Comparison Operators folder. 2. This project has been created for you, but you must fill in the missing operators. Replace the question marks with the appropriate operators. For example, you might replace the first question mark with the equals (==) operator:
if (comparisonValue ? 6) Console.WriteLine("comparisonValue is 6!");

3. Be sure to add the decrement operator where the comment shows.


comparisonValue?; // decrement the comparison value

4. Compile and run the program, as shown in Figure 8.

4-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Comparing Values with the Comparison Operators

Figure 8. Running the comparison program.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

4-27

Lab 4: Operators

4-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Classes and Objects

Classes and Objects


Objectives
Understand how to create new types. Discover the distinction between classes and objects. Use access modifiers. Add methods to classes. Add constructors to classes and overload constructors. Use private fields to enforce data hiding. Distinguish between reference and value types. Pass by reference and use out parameters. Understand non-deterministic finalization. Use properties to support data hiding.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-1

Classes and Objects

Creating New Types


Traditional procedural programming languages provide a set of data types, just as C# does. These include int, long, char, string, and so forth. The innovation of object-oriented languages like C#, however, is that you can add your own types to represent the things in your problem domain.

Problem Domain
The problem domain is the set of issues you are trying to model or solve in your program. For commercial programs the problem domain is often a business area (such as sales records or marketing issues). The problem domain can be just about any task youve set yourself in programming; if you are working on a simulation of warfare, then the battle characteristics, weaponry, and terrain will all be within your problem domain.

This ability to extend the language with new types is very powerful. You create new types in C# with the class keyword. Classes have behavior and they have state. You model the behavior using methods (sometimes called functions) and you model the state with member variables (sometimes called fields). You provide access to the class state through properties.

Class vs. Object


In C# you can draw a distinction between a class, which is the definition of a new type, and an object, which is an instance of that type. For example, Dog is a class (it describes the Dog type) but Fido, Milo, and Rover are all objects: they are instances of the class Dog. Similarly, Button is a class, but in Figure 1 the three buttons, Save, Cancel, and Delete are all objects; instances of class Button.

Figure 1. Three buttons.

5-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating New Types

Declaring a Class
You declare a class with the class keyword. Here is the formal definition syntax:
[attributes] [access modifiers] class identifier [:base class] [class body]

Attributes are discussed in the advanced portion of this course. Access modifiers are discussed later in this chapter. The keyword class appears followed by an identifier: the name of your class (e.g., Button or Dog). The base class is discussed in a future chapter when you learn about inheritance. The class body is the set of statements that define the members (methods, fields, and properties) of the class.

Access Modifiers
The possible access modifiers for the class (and for members of the class) are: public private protected internal protected internal

Public access means that the class can be seen by any method of any class. Private access means that the class (or method) can only be seen by methods of the class in which it is created. Classes can be nested, but private classes are an advanced topic taken up later in this course. Private methods and certainly private fields are common. Most fields and some methods are marked private to indicate that they are visible only to methods of the class in which they are created. The most common case is for methods and properties to be public and for fields to be private. Protected is just like private except that the protected member is visible to classes derived from this one. Youll learn about protected access in the chapter on inheritance. Internal access is like private except that the internal members are visible to any class in this assembly. The assembly is the basic unit of creating a C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-3

Classes and Objects


program. Assemblies are implemented as either executable programs or as dlls. When you mark a class or method as internal you are saying that it should be visible to the classes in this assembly, but not to classes in other assemblies. Protected internal is the union of both internal and protected: that is the class (or method) is private except to derived classes and also classes in the current assembly.

5-4

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Class Members

Class Members
Classes consist of methods, fields, and properties. Properties are discussed in a subsequent chapter. Methods are responsible for implementing the behavior of the class, and fields hold the state of the class.

Methods
A class methods implement the behavior of that class. All methods in C# belong to one or another class. A method can take parameters, but that is not required. A method may have any number of parameters, but each parameter must declare its type. It is possible for a method to return a value, in which case the type of the value returned must be declared.
// return int, take no parameters public int myMethod();

// return double, take two parameters // the first parameter is an int // the second parameter is a string public double SecondMethod(int p1, string p2);

If the method does not return a value, the return type is void.
// returns void (no return value) // takes one parameter: an integer public void ThirdMethod(int x);

Constructor
A constructor is a special method of a class, used to create a valid instance of the class. The constructor must have the same name as the class itself. Constructors have no return value, not even void. Their purpose is to create a valid instance, and they are called by the compiler; your code never explicitly calls a constructor. For example, if you create an Employee class, you might give it a constructor like the following: C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-5

Classes and Objects


public class Employee { private int empAge; private string empName; public Employee(int age, string name) { empAge = age; empName = name; } }

Here the constructor is passed two parameters, and it uses these parameters to set private members of the class.

Default Constructor
A constructor may have no parameters at all, in which case it is referred to as the default constructor. It turns out that if you create a class with no constructor at all, the compiler will provide a default constructor for you, that does nothing. It is as if you wrote the following:
public Employee() { }

Notice that you are able to create your own default constructor, in which case one will not be provided for you; if you create any constructors at all you will not get a default constructor from the compiler. The default constructor that you create may do work:
public Employee() { age = 35; // set a member }

It is easy to be confused about the meaning of default a constructor. To be clear, the default constructor is any constructor that takes no parameters, whether you create it explicitly or it is provided by the compiler.

5-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Class Members

The this Keyword


The this keyword refers to the current object. Within a constructor, the this keyword refers to the object being created. It is common to name private member variables with the same name as the parameters passed to the constructor; in such a case you can use the this keyword to differentiate the member variable from the parameter:
public class Employee { private int age; private string name; public Employee(int age, string name) { this.age = age; this.name = name; } }

In this example, this.age refers to the member variable, while age refers to the parameter. Similarly, this.name refers to the member variable, while name refers to the parameter. Every method has a hidden this parameter that is a reference to the current object. You can use the this keyword in any method, though most of the time it is not necessary. If you have a method that updates a member variable you do not need the this keyword unless there is ambiguity:
public void SomeClassMethod(string name) { age = 57; this.name = name; } // no ambiguity // resolve ambiguity

In this example, SomeClassMethod is a method of the employee class. It can access the age member variable without the this keyword, because there is no ambiguity. However, when it tries to access the name member variable, it must use the this keyword to differentiate the member from the parameter.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-7

Classes and Objects

Fields
Fields are often called member variables or even class variables. The purpose of a field is to hold the state of the object. You declare a field much as you would declare a local variable within a method:
private int age; private string name;

The difference is that you need to declare the access level (see access levels, above). Typically, member fields will be private. This allows for data hiding and supports encapsulation of your class. This is covered later in this chapter when Properties are discussed in detail.

Fields Can Be Initialized


It is possible to initialize a field within the definition of the class. For example, if you want to ensure that the field yearsOfService is initialized to 1, you can declare it as follows:
private int yearsOfService = 1;

If the yearsOfService is not otherwise set in the constructor, its value will be 1 when the object is created. This is an easy way to give objects default values for their fields.

Instantiating Objects
An objects constructor is called when the object is instantiatedthat is, when an object is created (an instance is made). You instantiate an object with the new keyword.
Employee emp1 = new Employee(); // instantiate an Employee

This is a common programming idiom in C#:


Type identifier = new Type();

You can create a Dog object with the following: 5-8 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Class Members
Dog milo = new Dog();

If your constructor takes parameters, you pass them inside the parentheses:
// instantiate an employee // pass in the age and name Employee emp1 = new Employee(25, Jim);

Once you have an instance, you may call methods on that object:
emp1.DisplayInfo(); // call a method

Try It Out!
See Employee.sln Lets look at an example that instantiates a class using the new keyword. 1. Open a new console application project in Visual Studio .NET and name it Employee. 2. Rename Class1 to Employee. 3. Instantiate an Employee object in the static method Main by adding the following code to Main:
int age = 46; string name = "John Doe"; Employee emp1 = new Employee(); emp1.SomeMethod(age, name);

4. Add the SomeMethod method to the Employee class. Copy the following method after the Main method, but still within the Employee class:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-9

Classes and Objects


// params act as local variables public void SomeMethod(int paramOne, string paramTwo) { Console.WriteLine("Received 2 params: {0} and {1}", paramOne, paramTwo);

Console.WriteLine("paramOne++ = }

{0}",paramOne);

5. Examine the program. The static method Main() instantiates an Employee object, and then calls an instance method on that object. The method invoked takes two parameters, and treats them as local variables, incrementing one and then printing them to the console. 6. Compile and run the program.

Overloading the Constructor


You can create more than one constructor. This is known as overloading the constructor and allows you to provide different options for instantiating the object. You can overload the constructor (or any method) by modifying the signature. The signature is the name and parameter list. You may vary the number or the types of the parameters (or both). You can have one constructor that takes a single string parameter, another that takes an int (modifying the type), and a third that takes two strings (modifying the number).

Try It Out!
See Employee2.sln Lets look at an example that instantiates a class using an overloaded constructor. 1. Open a new console application project in Visual Studio .NET and name it Employee2. 2. Rename Class1 to Employee. 3. Give the class the following member fields:

5-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Class Members
private int age; private string name; private int yearsOfService = 1;

4. Create two constructors: a default constructor and one that takes two parameters. In the default constructor, set the age and name to reasonable default values:
public Employee() { age = 21; this.name = "John Doe"; }

public Employee(int age, string name) { this.age = age; this.name = name; }

5. Add a method to display the employees name, age, and years of service:
public void DisplayEmployeeInfo() { Console.WriteLine( "{0} is {1} years old", name, age );

Console.WriteLine( "{0} has worked here {1} year(s)", name, yearsOfService); }

6. You are now ready to instantiate a couple of Employee objects, using both the default constructor and the overloaded second constructor that takes two parameters:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-11

Classes and Objects


static void Main(string[] args) { int age = 46; string name = "Jesse Liberty"; Employee emp1 = new Employee(age, name); emp1.DisplayEmployeeInfo(); Employee emp2 = new Employee(); emp2.DisplayEmployeeInfo(); }

7. Compile and run the program. The results should show that the first Employee object was created with the parameters you passed in, while the second Employee object used the default constructor, as shown in Figure 2.

Figure 2. Running the overloaded constructors.

Static and Non-Static Methods


Class methods and members come in two flavors: static and non-static. Nonstatic methods are often called instance methods. You invoke instance methods on an object (an instance of the class) and you invoke static methods on the class itself. Static methods are a compromise on the C concept of global methods. C# has no global methods, but it is convenient at times to be able to invoke a method without having a particular instance of a class. Youve seen many static methods at work already. When you call WriteLine() you invoke it not on an instance of Console, but on the Console class itself:
Console.WriteLine(hello world);

5-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Class Members
Notice that you do not have an instance of Console. You never wrote:
Console myConsole = new Console(); // not needed myConsole.WriteLine(); // nope!

The WriteLine method is conceived to be a method of the class, and is declared to be a static method. You can call the method on the class itself, rather than on an object (instance) of the class. Instance methods are methods of an object, and typically impact the object itself. Your Employee class might declare a method to increment the salary field:
public class Employee { private int salary; // field public BumpSalary(int increment) { salary += increment; } }

The salary method takes an int value and increases the private member salary by that amount. You invoke this method on an instance (not on the class). This allows you to change the salary for a particular employee.
// create an employee, pass in age, name, initial salary Employee joe = new Employee(50, Joe, 100); joe.BumpSalary(50); // increase Joes salary

Static Main()
Every program must have a static Main() method in one of the classes in the program. The Main() method is the entry point for the program; it is the method called by the operating system. Static methods can only call other static methods unless an instance is provided or created.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-13

Classes and Objects

Static Fields
Like static methods, static fields belong to the class, rather than to an instance of the class. You can use static fields to keep track of data that is instance independent. For example, it is common to use static member fields as a counter to track how many instances of the class have been created. You access static fields with static methods.

Try It Out!
See Static Members.sln The best way to see how to work with static fields is with an example.

1. Open a new console application project in Visual Studio .NET and name it StaticMembers. 2. Rename Class1 to Employee. 3. Give the class two private members, one static and one not.
private int age; static private int numEmployees = 0;

4. Create a constructor that initializes the instance variable. Have the constructor update the static member to indicate that another object has been created. Note that you must access the static member through the class, rather than through the instance:
public Employee(int age) { this.age = age; Employee.numEmployees++; }

5. You access the static member numEmployees with a static method. Add the following method to the class:

5-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Class Members
public static int GetNumEmployees() { return numEmployees; }

6. Add an instance method to display information about the employee, and within that method, access the number of employees through the static method:
public void DisplayEmployeeInfo() { Console.WriteLine( "This employee is {0} years old", age );

Console.WriteLine( "{0} employees have been created", Employee.GetNumEmployees()); }

7. Create a test method to instantiate Employees and display their characteristics and the number of Employees created.
static void Main(string[] args) { int age = 46; Employee emp1 = new Employee(age); emp1.DisplayEmployeeInfo(); Employee emp2 = new Employee(35); emp2.DisplayEmployeeInfo(); Employee emp3 = new Employee(21); emp3.DisplayEmployeeInfo(); }

8. Compile and run the program. Note that the static member is able to track the number of objects created, as shown in Figure 3.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-15

Classes and Objects

Figure 3. Using static fields.

5-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Value vs. Reference

Value vs. Reference


Like many other languages, C# differentiates between the intrinsic types (e.g., int, long, string) and the types you create (classes). C# also differentiates, however, between value types and reference types. When you instantiate an integer variable, its value is placed on the stack. The variable is a named location on the stack. The stack location contains the actual value of the integer.

Stack and Heap


The stack is an area of memory set aside for value types. The stack is of limited size, and values are added to the stack as needed. Values are popped off the stack as they are destroyed. The heap is an undifferentiated area of memory that is used for creating user-defined types (classes). When an object is created on the heap a reference is returned, and the built-in garbage collector cleans objects off the heap when there are no longer active references to the object.

When you instantiate a class, however, the object is instantiated on the heap, and what you actually have on the stack is a reference to that object.
House myHouse = new House();

The House object is created on the heap, and myHouse is actually a reference to that object, as shown in Figure 4. myVar is an integer variable (a value type) whose value is on the stack. myHouse is a reference to an instance of a class (a reference type) instantiated on the heap.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-17

Classes and Objects

Figure 4. Classes are instantiated on the heap.

All intrinsic types (int, long, char) are value types. All classes are reference types.

Passing Parameters
Parameters are passed to methods by value. This simple statement has profound implications. When an object is passed by value, a copy is made. When you pass an int, a copy of the int is made and used in the method. When you pass an object, however, what is made is a copy of the reference. The copy of the reference still refers to the same object on the heap. So, reference types are effectively passed by referencethat is, you still have access to the original object on the heap, because the copy of the reference refers to that same object. Suppose you have a method, MethodOne that creates two variables: an int (myInt) and a Cat object, (frisky) as shown in Figure 5.

5-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Value vs. Reference

Figure 5. Creating two objects.

In this example, frisky is a reference to the Cat object created on the heap. That cat object has two fields, secretVal whose value is 3 and Power whose value is 0. Suppose you now call a method, Method2 passing in the integer variable and the Cat object as shown in Figure 6.

Figure 6. Passing by reference and by value.

Both parameters are passed by value, and so a copy is made. The copy of magicLevel cannot affect the value of myInt back in the calling method. The Cat reference boots however, does refer to the same object as frisky and so changes made to that object will be seen back in MethodOne. You can change the cat frisky because you have a reference to the object to which frisky refers. You simplify this statement by saying that you have a reference to frisky, but that is just shorthand for having a reference to the object to which frisky refers. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-19

Classes and Objects


You cannot change the variable myInt in MethodOne however, because you do not have a reference to it; you have a local copy of its value.

Try It Out!
See Swap.sln You can prove to yourself that int values are passed by value by trying to write a method to swap two values. 1. Open a new console application project in Visual Studio .NET and name it Swap. 2. Rename Class1 to Swap. 3. Start by creating a method to swap two integers. This method takes two parameters, left and right. It then creates a temporary variable, and assigns the value in the parameter left to that temporary variable. It then assigns the value in right to left, and the value in the temporary variable to right, thus swapping the values. The method displays the value in left and right both before and after the swap.
static void DoSwap(int left, int right) { int temp; Console.WriteLine("Swap before. left: {0}, right: {1}", left, right); temp = left; left = right; right = temp; Console.WriteLine( "\nSwap after. left: {0}, right: {1}", left, right); }

4. Create a test of the DoSwap method in the Main() method. Pass in two values, printing their value both before and after the swap.

5-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Value vs. Reference


static void Main() { int x = 5; int y = 7; Console.WriteLine( "Main before. x: {0}, y: {1}", x, y); DoSwap(x, y); Console.WriteLine( "Main after. x: {0}, y: {1}", x, y); }

5. Compile and run the program. The results are shown in Figure 7. You can see that the swap method shows that the values are swapped, but the values are not swapped back in Main. This is consistent with the idea that the int variables are passed by value.

Figure 7. Running swap with value types.

Passing by Reference
In this case, youd like the int to be passed by reference. You can accomplish this in C# by adding the ref keyword both to the method and to the invocation of the method.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-21

Classes and Objects

Try It Out!
See Swap.sln To see the ref keyword at work, youll return to the previous example, but make a couple of simple changes: 1. Modify the DoSwap method, changing the parameters to add the ref keyword:
static void DoSwap(ref int left, ref int right)

2. Modify the invocation of DoSwap, adding the ref keyword:


DoSwap(ref x, ref y);

3. Compile and run this program. As you can see, this makes all the difference; the values are changed back in Main, as shown in Figure 8.

Figure 8. Using the ref keyword.

The keyword ref instructs the compiler to treat the parameter as passed by reference. A copy is not made; a reference to the original value is passed.

The out Keyword


At times, you will pass parameters to a method so as to retrieve multiple values out of that method. Normally, methods can return only one value (the return value), but by passing in parameters by reference and manipulating those values, you can extract multiple values out of a method. You can accomplish this by passing in values by ref, using the ref keyword. The problem is that you must initialize the values, because C# requires definite assignment. You cannot just declare a variable and then pass it by ref without 5-22 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Value vs. Reference


first initializing it. If, however, the entire reason you created the variable was to use it to retrieve values from the method, then the value you initialize it with will be meaningless, and it is annoying (and misleading) to initialize the variable with a meaningless value. If your code does not make sense then it is hard to maintain, and when you fill in dummy values, you make the code harder to understand. To solve this problem, C# offers the out keyword. When you are passing values to a method by reference only to extract a value set within the method, you can use the out keyword rather than the ref keyword. Out and ref are identical in every way except this: variables passed with the out keyword do not need to have an initial valid value. You can declare a variable and pass it using out without initializing the value. See PassByRef.sln Lets look at an example that illustrates how to use the out keyword. In this program you create a class Employee, and give that class three private member variables: baseLevel, bonusLevel, and profitSharingLevel.
public class Employee { private int baseLevel; private int bonusLevel; private int profitSharingLevel;

The constructor for this class takes values for the three private member variables, and sets them accordingly:
public Employee( int baseLevel, int bonusLevel, int profitSharingLevel) { this.baseLevel = baseLevel; this.bonusLevel = bonusLevel; this.profitSharingLevel = profitSharingLevel; }

You can imagine that this class would have methods that affect these values by interacting with other systems, databases, and perhaps data available over the Web. The example uses a very simple method named WorkMethod that stands in for that work.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-23

Classes and Objects


public void WorkMethod() { // get some data from the database // do some calculations baseLevel++; bonusLevel *= 4; profitSharingLevel = 7; }

The method that illustrates getting a value by reference is getLevels. Again, this stands in for a method that might set values based on database tables or other computation. In this case, youll compute the new values based on the member variables and constants hard wired in to keep the program simple:
public int GetLevels( out int extendedBase, out int extendedProfitSharingLevel) { extendedBase = baseLevel + bonusLevel; extendedProfitSharingLevel = baseLevel * profitSharingLevel + 327;

// show what you'll return! Console.WriteLine ( "returning these values: {0}, {1}, {2}", extendedBase, extendedProfitSharingLevel, baseLevel + bonusLevel + profitSharingLevel);

return this.baseLevel + this.bonusLevel + this.profitSharingLevel;

This method takes two parameters by reference, marked with the keyword out: extendedBase and extendedProfitSharingLevel. These parameters are not used in the method at all except as (effectively) return values. The incoming values are completely ignored; allowing you to use the out keyword. The extendedbase value is set based on two of the class fields: 5-24 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Value vs. Reference


extendedBase = baseLevel + bonusLevel;

The second parameter is set based on the member field baseLevel multiplied by the member field profitSharingLevel plus the addition of a constant 327.
extendedProfitSharingLevel = baseLevel * profitSharingLevel + 327;

Both of these parameters act as a mechanism to extract these new values from the method. Finally, the method returns a value based on summing the three member variables:
return this.baseLevel + this.bonusLevel + this.profitSharingLevel;

When the GetLevels method is invoked, you must pass in two values. Because of the out keyword, you need not initialize these variables:
static void Main(string[] args) { Employee fred = new Employee(10,20,30); fred.WorkMethod(); int newExtendedBase; int newExtendedProfitSharing;

int newTotal = fred.GetLevels( out newExtendedBase, out newExtendedProfitSharing );

Console.WriteLine ( "new total: {0}", newTotal); Console.WriteLine ( "new base: {0}, newProfit: {1}", newExtendedBase, newExtendedProfitSharing); }

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-25

Classes and Objects


You can see that newExtendedBase and newExtendedProfitSharing are never initialized, and no value is assigned to them before they are passed as parameters to GetLevels. Compile and run this program. The results show that the values created in GetLevels are available to the variables back in Main, as shown in Figure 9.

Figure 9. Using the out keyword.

If you change the out keyword in the method call and the method itself back to ref, you will get a compile error, as shown in Figure 10. Weve circled and highlighted the change to ref in the calling method as well as in the GetLevels method, and circled and highlighted the error received by the compiler, shown in the bottom (Task List) window.

5-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Value vs. Reference

Figure 10. A compile error using the ref keyword.

Properties
As discussed earlier, private member variables are normally kept private. This supports data hiding: the idea that the internal state of your class should not be visible to your clients. If you hide the details of class state, you can change how you manage that state without breaking your clients.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-27

Classes and Objects

TIP:

A client is any other class that interacts with your class. If a change to your program causes code in the client to stop working, you are said to have broken the client. It is a goal of object-oriented programming that you can change your class without breaking the clients of your class.

Heres why data hiding helps. Suppose you keep the employees age as a field in your Employee class. If you make that field public, other classes may access the Employees age directly. You might decide to get rid of the age field, and compute the Employees age based on the Employees birth date. If the age field was public, making this change will break those clients that are currently using this field. As the designer of the class, you want your clients to access the age field through a method. Today that method might just return the value of the member field, tomorrow it might compute the age. If your clients are calling the method, they will not break when you change the method; they expect to get back an age, and they will get one. The problem is that your clients really want to interact with the age as a field, not as a method. It is cleaner to write,
currentAge = myEmployee.age;

rather than
currentAge = myEmployee.GetAge();

It isnt a big deal, but writing cleaner code makes it easier to maintain and more reliable. This dilemmathat the designer of a class wants to present data through methods, and the client of the class wants to access data as a fieldis solved by properties.

Declaring a Property
A property looks to the designer of the class just like a method, but it looks to the client of the class just like a field. The designer is free to change the implementation without breaking the client (the implementing class would need to be recompiled, but the client class would not). Property statements have two parts: a get part for retrieving the value, and a set part for setting the value. The set part has an implicit parameter: value, which is the value being set. 5-28 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Value vs. Reference


For example, you can add an Age property to the Employee class, to provide public access to the age value:
public class Employee { private int age; // private member variable // property note capital A

public int Age { get { return age; }

// can change later to compute

set { age = value; } } } // use implicit parameter

To the designer, the property Age looks just like a method. The designer can change the get accessor to compute the age and then recompile without breaking any clients accessing this property:
get { ComputeAge(birthDate); } // change how you get age

To the client, however, this property looks just like a field:


Employee emp = new Employee(5); // initialize employee int currentAge = emp.Age; // emp.Age = 27; // set property // access property

The value assigned to the property (27) is passed into the set accessor as the value implicit parameter. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-29

Classes and Objects

Read-Only and Write-Only Properties


You can create a read-only parameter by not creating a set accessor. Similarly, you can create a write-only parameter by creating a set, but not a get accessor.

Try It Out!
See Properties.sln The best way to see how properties are used is to create a small test program.

1. Open a new console application project in Visual Studio .NET and name it Properties. 2. Rename Class1 to Tester. 3. Create an Employee class with two private member variables.
public class Employee { private int baseLevel; private string name;

4. Provide a constructor that sets these two fields:


public Employee(string name, int baseLevel) { this.name = name; this.baseLevel = baseLevel; }

5. Create properties to match the two private member variables.

5-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Value vs. Reference


public int BaseLevel { get { return baseLevel; } set { baseLevel = value; } }

public string Name { get { return name; } set { name = value; } }

6. You are now able to access the state of the Employee class through the properties youve created. Instantiate a couple of Employee objects in Main().
static void Main() { Employee fred = new Employee("Fred", 2); Employee joe = new Employee("Joe", 5);

7. You can access the state of the fred instance through the get properties.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-31

Classes and Objects


Console.WriteLine( "{0}'s base: {1}", fred.Name,fred.BaseLevel);

8. Similarly you can set a value using the set accessor.


joe.BaseLevel = 12;

// use get to see the new values Console.WriteLine( "{0}'s base: {1}", joe.Name,joe.BaseLevel);

9. To really see the accessors at work, set breakpoints in the get and set accessors for the two properties, and set a breakpoint in the first WriteLine in Main(). Run the application to the first breakpoint (WriteLine) and step into the get method, as shown in Figure 11. You can see that you have stepped into the get accessor for Name just by using the Name property in a place that would retrieve the value.

5-32

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Value vs. Reference

Figure 11. Stepping into the accessor.

10. Continue stepping through the code until you set the new value for baseLevel. Step into the accessor, and you can see that the set accessor is automatically called when you are assigning a new value.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-33

Classes and Objects

Destroying Objects
One of the most powerful innovations in C# and .NET is the availability of garbage collection. This means that, unlike with C and C++, in C# you do not need to destroy objects when you are done with them; the CLR will clean up after you.

Managing Resources
If your program works only with managed resources, you do not have to do anything at all to ensure that your objects are destroyed. The garbage collection is automatic. If you create an object, it will be destroyed as soon as there are no references to that object; that is, as soon as it is no longer needed. If you do manage unmanaged resources, you will need to ensure that they are destroyed. You do this by providing a finalize method. Only release the resources your object holds, and only implement this if you have unmanaged resources. Even then, you never invoke finalize directly; it is called for you by the garbage collector.

Non-Deterministic Finalization
Since you never call finalize yourself, you can never be certain exactly when your object will be destroyed. This is generally not a problem, but if you manage scarce resources (such as files or connections to a database) you may need to ask your client to help you close those resources. The idiom for doing so is not to call finalize directly, but rather to implement the IDispose interface. Interfaces are covered in a later chapter, but to implement IDispose you have only to implement the Dispose method. The interface simply dictates the return type, name, and parameters of the Dispose method.
void Dispose()

Your client classes call Dispose, and that is your opportunity to shut down and destroy the precious resources you manage. Some classes will not want a Dispose method; theyll want a close method. For example, if your class represents a file, your client will expect to close the file rather than dispose it. No problem, just create a public method close and have that call your private method Dispose.

5-34

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Destroying Objects

The Using Idiom


It is easy for your clients to forget to call Dispose on your class. To facilitate this, C# supports a using statement. Heres an example of its use:
using (Font theFont = new Font(Ariel, 10.0f)) { // use the font here } // the compiler calls Dispose on the font

You create the resource within the parentheses and use the object within the braces. When the closing brace is reached, the Dispose method is called on the resource.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-35

Classes and Objects

Summary
C# provides the ability to extend its intrinsic types with user-defined types. You define a type with the keyword class. Instances of classes are called objects. You control access to classes and their members with access modifiers such as public, private, protected, and internal. Classes consist of methods, member fields, and properties. The constructor is a special method responsible for creating a valid instance of the class. The this keyword refers to the current object. All methods may be overloaded by changing their signature. Static methods belong to the class rather than to an instance. Intrinsic types are value types; user-defined types are reference types. You can pass a value type by reference using either the ref or the out keywords. C# will provide garbage collection, but destruction of objects is nondeterministic.

5-36

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Destroying Objects

Questions
1. What is the difference between a class and an object? 2. What is the difference between private and public access? 3. What is the this keyword and how is it used? 4. What is a default constructor? 5. What does it mean to overload a constructor? 6. What is the difference between a field and a property? 7. What is the difference between a static method and an instance method? 8. What is the difference between a value type and a reference type? 9. What is the difference between pass by reference and pass by value? 10. What is the difference between the ref keyword and the out keyword?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-37

Classes and Objects

Answers
1. What is the difference between a class and an object?
A class defines a new type, an object is an instance of that type

2. What is the difference between private and public access?


Public members of a class are accessible from any method of any class. Private members are accessible only from methods of their own class.

3. What is the this keyword and how is it used?


The keyword this refers to the current object. It is provided as a hidden argument to all instance methods, and can be used at any time to refer to the current object. It is often used to distinguish between member fields and parameters.

4. What is a default constructor?


A default constructor is any constructor that takes no arguments.

5. What does it mean to overload a constructor?


When you overload a constructor (or any method) you provide more than one constructor with the same name, but with a different number (or type) of parameters.

6. What is the difference between a field and a property?


A field is a simple data value. A property appears to a client as a field, but to the implementor as a method, and may access the value either from a field, or from a database (or file, etc.) or it may compute the value.

7. What is the difference between a static method and an instance method?


A static method is a method of the class, and does not have a this pointer. An instance method is a method of an object, and refers to the instance data of that object.

8. What is the difference between a value type and a reference type?


Value types are created on the stack (all the intrinsic types are value types). Reference types are created on the heap and a reference to the object is held on the stack. All user-created types (classes) are reference types.

9. What is the difference between pass by reference and pass by value?


When you pass an argument to a method it is passed by value, and a copy is made. If you use the ref (or out) keyword, the argument is passed by reference, and no copy is made; the parameter acts as a reference to the original value.

10. What is the difference between the ref keyword and the out keyword?
These two keywords work identically, except that the variable used as an out parameter need not be initialized.

5-38

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Destroying Objects

Lab 5: Classes and Objects

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-39

Lab 5: Classes and Objects

Lab 5 Overview
In this lab youll learn to overload constructors and to pass values by reference. To complete this lab, youll need to work through two exercises: Overload a Constructor Pass a Value by Reference

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

5-40

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Overload a Constructor

Overload a Constructor
Objective
In this exercise, youll overload a constructor. You are provided with a default constructor and youll add a second constructor that takes two parameters.

Things to Consider
Why would you need two constructors for one object? How can overloaded constructors help create optional parameters?

Step-by-Step Instructions
1. Open OverloadedConstructor.sln in the OverloadedConstructor folder. 2. Add a constructor that takes a parameter for the age and a second parameter for the name.
public Cat(int age, string name) { this.age = age; this.name = name; }

3. Add test code in Main, to test this constructor.


int age = 4; string name = "Frisky"; Cat frisky = new Cat(age, name); frisky.DisplayCatInfo();

4. Build and run the program. It should look like Figure 12.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-41

Lab 5: Classes and Objects

Figure 12. Final exercise results.

5-42

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Pass a Value by Reference

Pass a Value by Reference


Objective
In this exercise, youll create a program to demonstrate that intrinsic types are passed by value by default and that classes are passed by reference. First, you will finish the Cat class already started for you. You will then create a Swap method that you will overload three times: once to take integers by value, once to take integers by reference, and once to take Cat objects.

Things to Consider
How do you demonstrate that a value has been passed by reference? How can you cause value types to be passed by reference? What does it mean to pass an object by reference? What can you do with that object?

Step-by-Step Instructions
1. Open PassByRef.sln in the PassByRef folder. 2. Fill in the constructor.
public Cat(int age) { this.age = age; }

3. Create an Age property for the Cat object.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-43

Lab 5: Classes and Objects


public int Age { get { return age; } set { age = value; } }

4. Fill in the parameters for the Swap method that takes two integers by value.
public void Swap(int first, int second { int temp = first; first = second; second = temp; }

5. Fill in the parameters for the Swap method that takes two integers by reference and fill in the method.
public void Swap(ref int first, ref int second) { int temp = first; first = second; second = temp; }

6. Create the method for the Swap that takes two Cat objects.
public void Swap(Cat c1, Cat c2) { int temp = c1.Age; c1.Age = c2.Age; c2.Age = temp; }

5-44

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Pass a Value by Reference


7. Invoke the three Swap methods in Run.
public void Run() { int valOne = 5; int valTwo = 10;

Console.WriteLine("Pass by value..."); Console.WriteLine("ValOne: {0}, ValTwo: {1}", valOne, valTwo); Swap(valOne, valTwo); Console.WriteLine("ValOne: {0}, ValTwo: {1}", valOne, valTwo);

Console.WriteLine("\nPass by reference..."); Console.WriteLine("ValOne: {0}, ValTwo: {1}", valOne, valTwo); Swap(ref valOne, ref valTwo); Console.WriteLine("ValOne: {0}, ValTwo: {1}", valOne, valTwo);

Console.WriteLine("\nPass a user defined type..."); Cat boots = new Cat(2); Cat frisky = new Cat(3); Console.WriteLine("boots is {0} while frisky is {1}", boots.Age, frisky.Age); Swap(boots,frisky); Console.WriteLine("boots is {0} while frisky is {1}", boots.Age, frisky.Age); }

8. Build and run the program. The output should look like Figure 13.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

5-45

Lab 5: Classes and Objects

Figure 13. The Pass by Reference exercise results.

5-46

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Inheritance

Inheritance
Objectives
Use inheritance to implement specialization. Chain up to base classes to implement reuse. Use virtual functions to implement polymorphism. Box and unbox intrinsic types to treat them as objects.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

6-1

Inheritance

Creating Hierarchies
It is a human characteristic to organize the things in the world into hierarchies. Give us a random collection of objects and well start categorizing them. In fact, this is a great party game: ask a person to empty his pockets. Then tell that person to create three piles. Give no further advice, and refuse all questions. Nearly everyone will, in fact, make three piles, organizing their stuff into categories of their own choosing. Categorization is the first step towards creating hierarchies. You see hierarchies everywhere. The most familiar, of course, is the taxonomy of life used by scientists: Kingdom, Phyla, Class, Order, Family, Genus, Species. This is just a formal hierarchical organization of all the types of life. Every hierarchy represents generalization and specialization. The taxonomy that classifies life notes that some types of life share certain characteristics (generalization) and that each sub-type specializes the type above it, as shown in Figure 1.

Figure 1. Animal hierarchy.

The Gerbil, Cow, Dog, Horse, and Cat types all share certain characteristics (they suckle their young, who are born live, and they have hair). These are characteristics generalized in the Mammal class, and you know that all Mammals share these characteristics.

6-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Hierarchies
As you move down the hierarchy, you see greater specialization. A dog is a special kind of mammal (one that whines, barks, begs for food) as is a cat (for example, a cat is a mammal that is totally indifferent to human approval). Continuing down the specialization hierarchy you find retrievers (dopey animals eager to chase tennis balls) and terriers (yappy animals eager to chase retrievers). You can see the same kind of generalization/specialization in any welldesigned hierarchy. For example, Windows, and many other windowing systems, treat all the controls (widgets) that you might use on a form or in a Graphical User Interface as though they are all types of windows, as shown in Figure 2.

Figure 2. Window hierarchy.

In this example, Text, Button, and List share characteristics that are generalized in Control. These characteristics might include the ability to be used on a form, or to raise certain kinds of events. Similarly, controls, dialog boxes, and documents share characteristics such as being drawable and having a location; these commonalities are generalized in Window. The Button type is specialized in command buttons and check boxes. These two types are both buttons, but they behave differently when clicked (the command button fires an event, the check box is checked or unchecked and has its state changed).

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

6-3

Inheritance
The key point is that as you move up the hierarchy you see greater generalization, and as you move down the hierarchy, you see greater specialization, as shown in Figure 3.

Figure 3. Specialization and generalization.

Implementing Specialization
In C#, specialization is implemented with inheritance. When you specialize a class you create a new class based on the first. The first class is called the base class (e.g., Control), while the specialized class (Button) is called the derived class. Button derives from Control; Control is the base class of Button. The syntax for inheritance is:
public class ListBox : Window

The colon separates the new class (on the left) from its base class (on the right). In this example, ListBox derives from (inherits from/specializes) Window, and Window is the base class for ListBox. A derived class inherits all the members of the base, and can have its own members (fields, properties, methods) as well. In addition, the derived class must have its own constructor. 6-4 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Hierarchies

TIP:

The purpose of a derived class is to specialize (extend) the base class.

For example, you might have a class Person, with a field age, as shown in the following code and illustrated in Figure 4.
public class Person { private int age; Person(int age) { this.age = age; } }

Figure 4. A base class.

Notice that the Person constructor sets the age member. You can derive a new class from Person, specializing the characteristics of a person as an Employee. An Employee is a Person, but it is a special kind of person, who works for a firm. The Employee will have an age, but will add new fields, such as baseLevel and bonusLevel. This is shown in the following code, as well as illustrated in Figure 5.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

6-5

Inheritance

Figure 5. Deriving Employee from Person.

Chaining Up to Base
It is the job of the Employee constructor to set its member variables, baseLevel and bonusLevel, but how will Persons age field be set? The answer is to initialize or chain up to the base class constructor, using the keyword base.
public Employee : Person { public Employee(age, baseLevel, bonusLevel) : base(age) { this.baseLevel = baseLevel; this.bonusLevel = bonusLevel; } }

You chain up to the base by placing a colon after the arguments, and then invoking the base constructor with the keyword base, followed by the arguments for the base constructor in parentheses.

6-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Hierarchies

Inheritance Implements is-a


In C#, inheritance is used to implement the is-a relationship; that is the relationship of specialization. A Dog is-a Mammal, a ListBox is a Window, and so forth. This relationship is distinguished from the has a relationship that is maintained by having one class have a reference to an object of another type, and the composed of relationship that is created by member fields. So, a List-Box is-a window. It might have a document that it is connected to, and it might be composed of an array of strings.

Polymorphism
You can treat a derived type as if it were an instance of its base type. This is known as polymorphism. Polymorphism comes from the root parts poly indicating many and morph indicating forms. A polymorphic object can have many forms. For example, you can treat list boxes, buttons, drop downs, and text fields all as if they were controls. You can place them on a form and tell each to draw itself. Each of these objects implements the Draw method differently (a list box draws its list, a button draws a rectangle with its text inside, and so forth). You are treating control (the base class) polymorphicallyin all its forms. In C#, polymorphism is implemented with the keyword virtual. You add this keyword to a method to indicate that you expect it to be overridden in the derived classthat is, you expect the derived class to have a method with the same name (e.g., Draw()) that acts somewhat differently than that method in the base class. You indicate your intention to override the method in the derived class by explicitly marking the method with the keyword override.

Implementing Polymorphism
To see how inheritance and polymorphism work, youll create a simple console application that demonstrates treating objects polymorphically. To get started, youll need a base class: Employee. The purpose of the employee class is to represent any employee in your corporation. For this demonstration youll strip down the employee class and provide it only with three fields:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

6-7

Inheritance
public class Employee { protected string name; protected int bonusLevel; protected int profit = 0;

The constructor will initialize the two fields that do not have an initialization value:
public Employee (string name, int bonusLevel) { this.name=name; this.bonusLevel = bonusLevel; }

Employee will create two virtual methods. By declaring these methods as virtual, you express your expectation that they will be overridden in derived classes. For example, the first method is TakeProfits. You expect that specialized kinds of Employees might use a different formula to take profits for the year, but this base method will provide a default way of computing an employees profits. In a real application this would probably involve working with a database and computing complex formulas; for this example youll just multiply the bonusLevel by a constant:
public virtual void TakeProfits() { profit += (bonusLevel * 500); Console.WriteLine("{0} profit this year: {1}", name, profit); }

The second virtual method simulates booking a trip on an airline. For this to work youll need a TravelAgent class. TravelAgent will have only a single method, BookFlight that will take a constant indicating the type of seating the Employee wants (coach, business class or first class). To indicate this, TravelAgent will have an enumeration:

6-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Hierarchies
class TravelAgent { // an enum scoped to TravelAgent public enum FlightType { coach, businessClass, firstClass }

The method BookFlight takes two parameters, a value of the enumerated FlightType and a string indicating the passengers name. Again, in a real application this method would actually book the flight; for now youll just display the choice to the console:
public void BookFlight(FlightType type, string name) { string output = " will be flying "; switch (type) { case FlightType.coach: output += "coach"; break; case FlightType.businessClass: output += "business class"; break; case FlightType.firstClass: output += "first class"; break; } Console.WriteLine(name + output); }

The employee method PlanBizTrip can now instantiate a TravelAgent and book a flight:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

6-9

Inheritance
public virtual void PlanBizTrip() { TravelAgent agent = new TravelAgent(); agent.BookFlight(TravelAgent.FlightType.coach, name); }

Deriving a New Class


The Manager class is a specialized form of Employee, one that represents managers within the company. Managers have a manager level:
public class Manager : Employee { int managerLevel = 1;

While the manager level is initialized to 1, it can be changed in the constructor. Note that the constructor for a manager must take all the parameters expected for an Employee so that it can initialize its base class:
public Manager( string name, int bonusLevel, int managerLevel): base(name,bonusLevel) { this.managerLevel = managerLevel; } // chain up

You can see here that the first two parameters are passed to the constructor for the base class (Employee), while the third parameter (managerLevel) is used to initialize the member field. The Manager class inherits both the virtual methods from Employee. As the author of the Manager class you are free to override the behavior of the Employee class, if you wish to specialize the behavior in your derived class. Begin by overriding the virtual method PlanBizTrip. To do so, you must match the signature (name and parameters) of Employee.PlanBizTrip, but rather than writing virtual you will write override. 6-10 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Hierarchies
The body of the method may be entirely different than the body in the base class. In this case, the Manager will fly first class:
public override void { TravelAgent agent = new TravelAgent(); agent.BookFlight( TravelAgent.FlightType.firstClass, name); } PlanBizTrip()

It is possible that you will want to invoke the base class method from within your override. You do this with the keyword base, as shown in the override of TakeProfits. The manager will increase his profit by multiplying the managerLevel value (not available in the Employee class) by a constant, and then invoking the base method to reuse the work done there:
public override void TakeProfits() { profit += managerLevel * 750; base.TakeProfits(); }

Treating Employees Polymorphically


You are now ready to treat references to Employee polymorphically, whether they actually refer to an instance of the base class Employee or to the derived class Manager. To begin, create an instance of Employee and an instance of Manager:
public class Tester { public static void Main() { Employee e1 = new Employee("Fred", 2); Manager m1 = new Manager("Joe", 3,5);

You can create an array of Employee objects, in which you store your two objects. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

6-11

Inheritance
NOTE Arrays are a standard ingredient in C# and in many programming languages. Arrays are discussed in detail in a future chapter. You can think of an array as a simple collection of objects that are all of the same type. You access an object in the array using the index operator. Indices start at 0 and count up, so the fourth item in an array named myArray is accessed at myArray[3].

Notice that you may safely store a Manager in an array of Employees, because a Manager is-an Employee. This is the essence of the inheritance relationship:
Employee[] EmpArray = new Employee[2]; EmpArray[0] = e1; EmpArray[1] = m1;

You can now iterate through the array using a foreach loop. The foreach loop extracts each employee in turn and assigns it to the reference e, which you can then use to access properties and methods of the employee. In this case you will invoke the two methods:
foreach (Employee e in EmpArray) { // treat all the members as employees e.PlanBizTrip(); e.TakeProfits(); }

The output shows that the objects are treated polymorphically, as shown in Figure 6. You can see that the Employee Fred uses the base methods, while the Manager Joe uses the Manager methods. The client (Main) does not know or care which is a Manager, as far as the client is concerned it has a collection of Employees. It calls the virtual method and the right thing happens.

6-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Hierarchies

Figure 6. Treating employees polymorphically.

Note that calling foreach in this way is exactly like calling the methods on each indexed value explicitly, as illustrated by adding this block of code:
Console.WriteLine( "\nAgain, using explicit array indexing..."); EmpArray[0].PlanBizTrip(); EmpArray[0].TakeProfits(); EmpArray[1].PlanBizTrip(); EmpArray[1].TakeProfits();

The result of adding this code is shown in Figure 7. The results are the same, this time, however, you are invoking the methods directly, rather than through the enumerator.

Figure 7. Accessing array members explicitly.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

6-13

Inheritance

The Object Class


In C# every class is considered to derive (ultimately) from Object. That is, Object is the root of all class hierarchies. The object class has a number of useful methods, some of the most important of which are shown in Table 1.

Method Equals ToString Finalize

Description Are two objects the same? Return a string representation of the object Clean up resources

Table 1. Important object class methods.

When a method expects an object to be passed as an argument, you are free to pass in any type at all. Since all types derive from object, they can be treated polymorphically. That is, if the method expects an object and you pass in an Employee, that will work fine because an Employee is an object. What happens, however, if you pass in an integer? It turns out that C# treats value types as if they derive from object as well. What actually happens, however, is that the integer is boxedthat is an object is created that holds the value of the integer. Boxing happens automatically. You do not have to take any action to create the boxed integer; it is done for you by the CLR. When you unbox an object back to its original type, you must explicitly cast the object to the original type. If you box an int, when you unbox it you must cast the object to an int. This is illustrated in Figure 8. In Figure 8 the integer variable i has the value 123. When you pass variable i to a method that expects an object, the integer is boxed. A new object is created on the heap that contains the value of the integer, and a reference (o) is created. Later, when you assign the value in o back to an integer you must explicitly cast back to an int and the value is unboxed back to the integer j.

6-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

The Object Class

Figure 8. Boxing and unboxing an integer.

The goal of boxing is to allow the intrinsic types to have the same behavior as user-defined types (classes); that is, everything in C# is assumed to derive from object.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

6-15

Inheritance

Summary
Inheritance implements specialization. Common features should be factored to base classes. Reuse can be accomplished by chaining up to base class methods. Virtual functions allow you to treat objects polymorphically. When you override a virtual method you must use the keyword override. All classes derive from object. Intrinsic types derive from object as well. When you pass an intrinsic type to a method expecting an object, the intrinsic type is implicitly boxed. You must explicitly cast to unbox a boxed object.

6-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

The Object Class

Questions
1. How do you implement specialization in C#? 2. What is the syntax for deriving class D from base class B? 3. What is the difference between private and protected access? 4. How do you mark a base class method to support polymorphism? 5. How do you mark a method in a derived class that overrides a virtual method in the base class? 6. Which needs an explicit cast: boxing or unboxing?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

6-17

Inheritance

Answers
1. How do you implement specialization in C#?
Specialization is implemented in C# using inheritance. To accomplish this you derive the more specialized class from the more general class.

2. What is the syntax for deriving class D from base class B?


To derive class D from base class B you write a colon to the right of the derived class identifier, followed by the base class identifier: public class Derived : Base

3. What is the difference between private and protected access?


Private members are available only to the methods of the class in which they are created, protected members are available to the creating class and any class derived from the creating class.

4. How do you mark a base class method to support polymorphism?


Methods that will be used to support polymorphism are marked virtual. This indicates that the method author expects the method to be overridden in derived classes.

5. How do you mark a method in a derived class that overrides a virtual method in the base class?
When you override a method in a derived class, you must use the keyword override.

6. Which needs an explicit cast: boxing or unboxing?


Boxing is implicit, but when you unbox the value you must explicitly cast it back to its original type.

6-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

The Object Class

Lab 6: Inheritance

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

6-19

Lab 6: Inheritance

Lab 6 Overview
In this lab youll learn how to use polymorphism and inheritance. To complete this lab, youll need to work through two exercises: Implementing Polymorphism Passing Objects as Parameters

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

6-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing Polymorphism

Implementing Polymorphism
Objective
In this exercise, youll create a Student class and derive from that a more specialized MathMajor class. You will create virtual methods in Student that youll override in MathMajor. Weve created a Registrar class for you, which you will want to review in the starter program. When you call BookClass, you pass in your major and your name. The BookClass method assigns you to the appropriate math class based on your major.

Things to Consider
Why derive one class from another? What is the semantic implication of marking a method virtual? Why would you override a virtual method?

Step-by-Step Instructions
1. Open the starter program named Polymorphism.sln in the Polymorphism folder. The Student class is started for you. Implement the constructor to assign the name, passed in as a parameter, to the member variable name.
public class Student { protected string name; private int numCredits = 0;

public Student (string name) { this.name=name; }

2. Implement the BookClasses method of Student. Delegate the work to the BookClass method of the Registrar.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

6-21

Lab 6: Inheritance
public virtual void BookClasses() { Registrar registrar = new Registrar(); registrar.BookClass( Registrar.Major.LiberalArts, name); }

3. Finish the AddCredits method. This method increments the numCredits member variable by the number of credits passed in, and displays an appropriate message. The second parameter describes the subject these credits are for, and is used in the message displayed.
public virtual void AddCredits( int numCredits, Registrar.Major subject) { this.numCredits = numCredits; Console.WriteLine( "{0} has added {1} credits in {2}", name, numCredits, subject); }

4. Implement a MathMajor class that derives from Student.


public class MathMajor : Student {

5. Provide a member variable in MathMajor to keep track of how many credits the Student is taking in mathematics. Call the member variable numMathCredits.
int numMathCredits = 0;

6. Implement the constructor to take two parameters. The first parameter will be the students name. The second will be the number of math credits the student currently has on record.

6-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing Polymorphism
public MathMajor( string name, int numMathCredits): base(name) { this.numMathCredits = numMathCredits; }

7. Override the BookClasses method to pass Mathematics as the major.


public override void { Registrar r = new Registrar(); r.BookClass(Registrar.Major.Mathematics, name); } BookClasses()

8. Override AddCredits to check if the subject is Mathematics, and if so, increment this students numMathCredits value.
public override void AddCredits( int credits, Registrar.Major subject) { base.AddCredits(credits, subject); if ( subject == Registrar.Major.Mathematics ) { numMathCredits += credits; Console.WriteLine( "Now registered for {0} math credits", numMathCredits); } }

9. In the Run method of Tester create an instance of Student and an instance of MathMajor and put them both in an array of Students.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

6-23

Lab 6: Inheritance
Student s1 = new Student("Fred"); MathMajor m1 = new MathMajor("Joe",20);

Student[] StudentArray = new Student[2]; StudentArray[0] = s1; StudentArray[1] = m1;

10. Iterate over the array, and call the two methods of Student.
foreach (Student e in StudentArray) { // treat all the members as employees e.BookClasses(); e.AddCredits(3,Registrar.Major.Mathematics); }

11. Compile, build, and run the program. Your output should look like Figure 9.

Figure 9. Running the program.

6-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Passing Objects as Parameters

Passing Objects as Parameters


Objective
In this exercise, youll create a method that takes an object as a parameter. Youll then pass in two objects: one a user-defined type and the other an intrinsic type.

Things to Consider
What does it mean that everything in C# derives from Object? How can intrinsic types derive from Object?

Step-by-Step Instructions
1. Create a new console application. 2. Create a class, Cat. Do not explicitly derive this class from any other class.
class Cat {

3. Give the Cat class two member variables: age and name.
private int age; private string name;

4. Create a constructor to initialize the age and name.


public Cat(int age, string name) { this.age = age; this.name = name; }

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

6-25

Lab 6: Inheritance
5. Override the ToString method to return a string that includes the Cat objects name and age.
public override string ToString() { return "I am " + name + ", and I am " + age + " years old."; }

6. Create a Tester class.


class Tester {

7. Give Tester a DisplayObject method that takes an object as its parameter. In this method, invoke ToString on the object passed in as a parameter.
public void DisplayObject(object o) { Console.WriteLine("Displaying object {0}", o.ToString()); }

8. Create a Run method for Tester. In Run, create a Cat instance and pass it to DisplayObject.
public void Run() { Cat frisky = new Cat(3,"Frisky"); DisplayObject(frisky);

9. In Run create an integer variable, initialize it and pass it to DisplayObject.


int x = 25; DisplayObject(x);

6-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Passing Objects as Parameters


10. Put a breakpoint on the first call to DisplayObject and step into the call. Watch as your user-defined class (which was not explicitly derived from Object) is passed to DisplayObject, as shown in Figure 10.

Figure 10. Stepping into the DisplayObject method.

11. Note the type of this is System.Object, but the true type of the object is Cat. 12. Step through in the debugger and see the int passed to DisplayObject. 13. Your output should look like Figure 11.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

6-27

Lab 6: Inheritance

Figure 11. Output from displaying both objects.

6-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Operator Overloading

Operator Overloading
Objectives
Understand why you overload operators in user-defined classes. Establish rules about when and how to overload operators. Examine implicit and explicit type conversion. Walk through a detailed example using operator overloading.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

7-1

Operator Overloading

Why Overload Operators


A principal goal in C# and object-oriented languages in general, is to allow user-defined types (classes) to have all the functionality of intrinsic types. There should be little difference in interacting with an Employee than in interacting with an int. One of the key features of the intrinsic types (int, double, char, etc.) is that they support the use of operators. So, you know that you can write:
int a, b = 5, c = 10; a = b + c;

After these two lines of code run, the variable a will hold the value 15 (the sum of b and c). If you look closely at these lines of code, you will realize that two operators are being invoked. First the addition (+) operator is invoked. The expression b + c evaluates to 15. Second, the assignment operator (=) is invoked, assigning the value 15 to a. For user-defined types to act like intrinsic types, they must be able to support these operators when appropriate. For many types, operator overloading is not appropriate (what does it mean to add two Employees?) but for other classes, operator overloading can greatly simplify your code. Imagine, for example, that youve created a class to represent Roman numerals:
class Roman { // }

You would like to be able to add variables of type Roman just as you might add integers.
Roman a, b = 15, c = 10; a = b + c; Console.WriteLine(a: {0}, a);

7-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Why Overload Operators


You would like the third statement to display:
a: XXV

To accomplish this, you must implement the addition and assignment operators. When you implement an operator you are said to overload that operator. Without operator overloading you would have to create an Add method for Roman, and then you would write:
a = b.Add(c);

In fact, without operator overloading you could not use the assignment operator, and so youd have to write:
a.Assign(b.Add(c));

This statement is a lot less intuitive than:


a = b + c;

Operator Overloading and the CLS


The Common Language Specification does not mandate operator overloading and some languages, like Visual Basic .NET do not support it. If you do provide an overloaded operator, you should supply a method that does the same work as well. That way, if someone derives from your class or interacts with it from another language they will still obtain the necessary functionality. For example, if you overload the equality operator (==) be sure to overload Object.Equals().

Creating Overloaded Operators


Implement operator overloading in C# using static methods. Binary operators (such as addition, subtraction, etc.) take two parameters. You would implement the addition operator for your Roman class like this:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

7-3

Operator Overloading
public static Roman operator+ (Roman lhs, Roman rhs) { // }

The method begins with an accessor declaration (public), followed by the keyword static. The return type is Roman (adding two Roman objects returns a new Roman object). The two parameters are both instances of Roman. It is a convention to name the two arguments lhs (left-hand side) and rhs (right-hand side). When you write:
b + c;

the operator+ is invoked and the Roman on the left-hand side of the + sign is assigned to lhs, while the Roman on the right-hand side of the + sign is assigned to rhs.

Danger, Will Robinson!


Operator overloading can clarify your code, as shown earlier. It can also be a recipe for writing miserable, confusing, obscure, and difficult to maintain code. You can, if you are particularly malicious, write the addition operator (+) and have it subtract two values! There are no rules that determine what you must do inside the implementation of an operator, except common sense. Long experience with operator overloading dictates the following guidelines: Use operator overloading sparingly. Mimic only the natural use of an operator (subtraction operator should subtract). The one exception to the natural use of operator is to implement the convention that the + operator may be used for concatenation (as is done with Strings). Each operator should do one simple thing.

Matched Pairs
C# dictates that some operators must be implemented in matched pairs. For example, if you implement the greater than operator (>) you must implement the less than operator (<). Similarly, if you implement the greater than or equals operator (>=) you must implement the less than or equals operator (<=).

7-4

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Why Overload Operators


One interesting matched pair is that if you implement the equals (==) operator, you must implement the not equals operator (!=).

Type Conversion
In C#, conversion from one type to another (casting) may be implicit or it may be explicit. A type may be converted implicitly when there is no chance that data will be lost:
int intValue = 5; long longValue = intValue; // implicit conversion longValue++; intValue = (int) longValue; // explicit

In the code shown here the conversion from int to long is implicit. The long can accommodate any possible int value, i.e., no value can be lost. When you convert back to an int, however, it is possible for a value to be lost (the long holds values up to 9,223,372,036,854,775,808, while the int can hold values only up through 2,147,483,647). Notice that the implicit conversion requires no work, you just assign the int value to the long variable. The explicit conversion is accomplished by putting the type you want to convert to inside parentheses. What actually happens is that longValue is cast to an int, and that int value is then assigned to intValue. You can provide type conversions for your classes. Once again this assists with allowing your users to interact with your classes just as if they were intrinsic types. You provide conversion with the keywords implicit and explicit. For example, you might create an explicit conversion from a float to a Roman as follows:
public static explicit Roman(float theFloatValue) { create new Roman value here }

The conversion from float to Roman must be explicit, because a float may have a fractional part that will be lost in the Roman. In some ways the explicit and implicit operators are like constructors. The syntax is to declare the access level (public) followed by the keyword static and then the keyword explicit. You then declare the type you are converting to C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

7-5

Operator Overloading
(Roman). The parameter is an object of the type you want to convert from, in this case a float. Within the body of the operator you make the conversion, returning a new Roman object based on the argument (the float). Implement the implicit conversion in exactly the same way:
public static implicit float(Roman r) { create new float value here }

In this case you are converting from a Roman to a float. Here the conversion can be implicit because a float can hold any value that can be held in a Roman.

A Detailed Example
In this next example, youll create a Roman class to explore operator overloading in detail. To understand how this program works, you need to examine the relationship between integer values (1,2,3) and Roman numerals (I, II, III). The conversion can be tricky at points. Table 1 indicates the key values to watch for.

7-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Why Overload Operators


Integer Value 1 2 3 4 5 6 7 8 9 10 40 50 90 100 101 400 500 900 1000 I II III IV V VI VII VIII IX X XL L XC C CI CD D CM M Table 1. Roman numeral to Arabic numeral conversions. Roman Numeral

You can see the pattern. You add an I for each one, a V for each 5, an X for each 10, an L for each 50, a C for each 100, a D for each 500, and an M for each 1,000. This would be straightforward except that the fours and nines (40, 90, 400, 900) present a challenge. For example, 4 is not IIII, it is IV. That is, one less than 5. Similarly, 9 is IX. Forty carries on with ten less than 50 (XL) and 90 is ten less than 100 (XC). The value 900 is written as one hundred less than one thousand (CM). Much of the work in dealing with Roman numerals is in converting integers to Roman and back. To simplify this work and to simplify the class, youll store the value of the Roman as an integer, and convert it only when required.
public class Roman { private int val;

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

7-7

Operator Overloading
The private member val holds the integer value of the Roman numeral. When you add two Romans or do other math with Romans you will just manipulate the integer values. When you display the Roman, however, you will convert it to a string based on the rules for writing Roman numerals. The constructor for a Roman numeral takes an int as a parameter:
public Roman(int val) { Console.WriteLine("In Roman Constructor(int)"); this.val = val; }

This allows you to create a Roman numeral with the value IX by writing:
Roman r1 = new Roman(9);

You can imagine overloading the constructor to allow you to pass in a Roman numeral as a string:
Roman r2 = new Roman(IX);

This is left as an exercise for the ambitious student!

Overriding ToString
Virtually all the remaining methods are operator overloads, except for the method ToString. ToString is a method of Object, and Roman overrides the base methods implementation to return a Roman numeral as a string. If the value of the Roman is 49, ToString will return the string XLIX. Creating the string is done in two steps: 1. Create temporary values. 2. Use the temporary values to create the string.

For example, if the number you were representing was 1950, the Roman numeral would be DMDL. The first D represents 1,000, the MD represents 900, and the L represents 50.

7-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Why Overload Operators


To build this number you need to know how many thousands you have (1), and how many nine hundreds you have. The number of 900s can only be 1 or 0. No number is thought of as having more than one 900 in it. For example, 1800 has no 900s, it has one thousand, one five hundred and three hundreds. This is not a mathematical rule, it is the syntax of Roman numerals. Certain values (900, 500, 90, 50, 9, 5) can have only the values 1 or 0. There can be only one of these or none of these in any given number. You also need to know the total number of thousands, hundreds, tens, and ones. You compute all of this using the division and modulus operator:
// comments show how 3899 would be converted // 3 // 899 // 0 // 899 // 1 // 399 // 3 // 99 // 1 // 9 // 0 // 9 // 0 // 9 // 1 // 0 // 0 // 0

int numThousands = val / 1000; int remainder = val % 1000; int numNineHundreds = remainder / 900; remainder %= 900; int numFiveHundreds = remainder / 500; remainder %= 500; int numHundreds = remainder / 100; remainder %= 100; int numNineties = remainder / 90; remainder %= 90; int numFifties = remainder / 50; remainder %= 50; int numTens = remainder / 10; remainder %= 10; int numNines = remainder / 9; remainder %= 9; int numFives = remainder / 5; int numOnes = remainder % 5;

The comments indicate how this works. Given the value 3,899 the numThousands will be 3. 3,899 / 1000 returns 3, because this is integer division and the remainder is simply truncated away. To get the remainder and compute the rest of the value, you use the modulus operator: 3,899 % 1000. The result is the remainder after the division, or 899. You can now compute the number of 900s by dividing the 899. The result is 0 (899 / 900 is 0, remainder 899).

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

7-9

Operator Overloading
You compute the number of 500s in the same way (1 with a remainder of 399). The number of hundreds is 3. Continuing on, assemble the values for 3,899 shown in Table 2. Value 1000s 900s 500s 100s 90s 50s 10s 9s 5s 1s
Table 2. The special values for 3,899.

Number in 3,899 3 0 1 3 1 0 0 1 0 0

You are now ready for Step 2, assembling the string:


string output = "[" + val + "]: "; StringBuilder s = new StringBuilder(output); for (int i = 0; i < numThousands; i++) s.Append( 'M' ); if (numNineHundreds == 1) s.Append("CM"); if (numFiveHundreds == 1) s.Append( 'D' ); if (numHundreds == 4) s.Append( "CD" ); else for(int i = 0; i < numHundreds; i++) s.Append( 'C' ); if (numNineties == 1)

7-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Why Overload Operators


s.Append("XC"); if (numFifties == 1) s.Append( 'L' ); if (numTens == 4) s.Append( "XL" ); else for(int i = 0; i < numTens; i++) s.Append( 'X' ); if (numNines == 1) s.Append("IX"); if (numFives == 1) s.Append( 'V' ); if (numOnes == 4) s.Append( "IV" ); else for (int i = 0; i < numOnes; i++) s.Append( 'I' );

Notice that there is special processing for the 4s. That is, if the value is 400, the value is represented as CD rather than as CCCC:
if (numHundreds == 4) s.Append( "CD" ); else for(int i = 0; i < numHundreds; i++) s.Append( 'C' );

If the number of hundreds is not 4 it must be less than 4, and so you write a C for each hundred. The same logic is applied to 40 and 4:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

7-11

Operator Overloading
if (numTens == 4) s.Append( "XL" ); else for(int i = 0; i < numTens; i++)

if (numOnes == 4) s.Append( "IV" ); else for (int i = 0; i < numOnes; i++) s.Append( 'I' );

Overloading Operators
With ToString in place, you are ready to overload various operators to make your Roman numeral behave like an int. The first is the addition operator. You want to be able to add two Roman values to one another:
public static Roman operator+(Roman lhs, Roman rhs) { Console.WriteLine("In operator+"); return (lhs.val + rhs.val);

The logic here is that given two Roman numerals, you can extract their val fields (with the underlying values), add these, and return a new Roman. The result is an int, but the return value is marked as a Roman. For this to work, you need an implicit conversion from int to Roman:
public static implicit operator Roman(int val) { Console.WriteLine("In implicit conversion from int"); return new Roman(val); }

The implicit conversion operator works by explicitly creating a new Roman, passing in the integer value. The constructor takes an int, so this works fine. Semantically this makes sense; given an int, it is safe to implicitly cast to a Roman, as a Roman can represent any value held by an int. 7-12 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Why Overload Operators


While looking at the conversion operators, you might as well go ahead and create the conversion from a Roman to an int. Again, this can safely be implicit:
public static implicit operator int(Roman r) { Console.WriteLine("In implicit conversion to int"); return } r.val;

When converting to and from a float, a little more thought is required. While it is safe to implicitly convert from a Roman numeral to a float, the reverse is not true:
// convert roman to float public static implicit operator float(Roman r) { Console.WriteLine("In implicit conversion to float"); return (float) r.val; }

// explicit conversion float -> roman public static explicit operator Roman(float val) { Console.WriteLine("In explict conversion from float"); return new Roman((int) val); }

You need an equality operator to test whether two Romans are equal. If you implement this, however, you must implement the not-equals operator and you should override the Equals method:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

7-13

Operator Overloading
// overload equality operator. // strategy: compare the integer values public static bool operator==(Roman lhs, Roman rhs) { Console.WriteLine("In operator =="); return (lhs.val == rhs.val); }

// overload the not equals operator // strategy - test operator == public static bool operator !=(Roman lhs, Roman rhs) { Console.WriteLine("In operator !="); return !(lhs==rhs); }

// provided for language compatibility // check to make sure it is a Roman object // if so, delegate to operator == public override bool Equals(object o) { Console.WriteLine("In method Equals"); if (! (o is Roman) ) { return false; } return this == (Roman) o; }

The equality operator tests the underlying integer values of the two Roman numerals. If these are equal then the objects are equal. The strategy with the not-equal operator is to return the logical inverse of the equality operator, using operator ! on the Boolean value returned from the equality operator.
return !(lhs==rhs);

7-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Why Overload Operators


This works by evaluating lhs==rhs, which returns a Boolean value and then applying operator ! which returns the inverse. So, if lhs==rhs evaluates true, then !(lhs==rhs) returns false. The logic of the Equals method is to first test that the object passed to the method is of type Roman. If so, then it delegates responsibility to the overloaded equality operator. This is a very common idiom. If you are overriding Equals and ToString, you might as well go whole hog and override GetHashCode, the third significant method of Object:
public override int GetHashCode() { return val; }

GetHashCode is used by hashtable collections (explained in a subsequent chapter), and simply requires an integer value. Here you provide the underlying value of the Roman numeral.

Testing The Program


To test the program, first provide a number of values to translate and display. Then add code to test the overloaded operators:
public void Run() { Console.WriteLine("\n creating new Roman(9)"); Roman r1 = new Roman(9); Console.WriteLine("r1: {0}", r1.ToString());

Console.WriteLine("\n creating new Roman(999)"); r1 = new Roman(999); Console.WriteLine("r1: {0}", r1.ToString());

Console.WriteLine("\n creating new Roman(567)"); r1 = new Roman(567); Console.WriteLine("r1: {0}", r1.ToString());

Console.WriteLine("\n creating new Roman(1423)"); Roman r2 = new Roman(1423);

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

7-15

Operator Overloading
Console.WriteLine("r2: {0}", r2.ToString());

// addition will call implicit int converter // and then Roman constructor Console.WriteLine("\n r3 = r1 + r2..."); Roman r3 = r1 + r2; Console.WriteLine("r1 + r2 = r3: {0}", r3.ToString());

// convert 5 to Roman // add the two int vals (call int to roman) // construct a Roman answer Console.WriteLine("\n r4 = r3 + 5..."); Roman r4 = r3 + 5; Console.WriteLine("r3 + 5 = r4: {0}", r4.ToString());

Console.WriteLine("\n r4 = r3 + 7.5f..."); Roman r5 = r3 + (Roman) 7.5f; Console.WriteLine("r3 + 7.5f = r5: {0}", r5.ToString()); }

The output from this is shown in Figure 1. First create the value 9, and display its value (IX). You can see that you have stepped into the constructor to do this. You then create 999 to test its value (CMXCIX). The value 567 is translated as DLXVII and 1,423 is MCDXXIII. All of these are correct. Then add the value in r1 to the value in r2 and print the resulting value of 1,990 (MCMXC). To accomplish this, step into operator + as you might expect, but then you see that you are in the implicit conversion from int to Roman, and then you are in the Roman constructor.

7-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Why Overload Operators

Figure 1. Testing the Roman class.

In the next text, adding a Roman (r3) to an integer (5), you step not only into the implicit conversion and the constructor, but then into operator +, back into the implicit conversion and then into the constructor. To understand why this is happening, youll need to set some breakpoints and step through the code. Start by setting a breakpoint on line 185 as shown in Figure 2. Run to the breakpoint.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

7-17

Operator Overloading

Figure 2. Setting a breakpoint.

You are in the middle of the program. All of the simple tests have run, as you can see by switching to the output window, shown in Figure 3. The line youve stopped on will display the next action, r3 = r1 + r2. Step over the WriteLine with F10. The line is displayed to the console and you are ready to step into the assignment and addition:
Roman r3 = r1 + r2;

7-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Why Overload Operators

Figure 3. Running to the breakpoint.

Press F11 to step into the operator+ method. This is what youd expect; the order of operations should be add, then assign. After the line that displays that you are in operator+ you should see the return statement. This returns the sum of the two underlying values. Whoa! You are taken to the implicit conversion operator that takes an int and returns a Roman. Why? You are here because the sum of lhs.val + rhs.val is an integer, but the operator + method returns a Roman. To do so, the compiler tries to convert the int to a Roman. Since you have defined such a conversion operator, it is invoked, as shown in Figure 4.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

7-19

Operator Overloading

Figure 4. Stepping into the conversion operator.

Pay particular attention to the call stack (circled and highlighted in Figure 4). You see that you have come to the implicit conversion operator from the Addition operator. Look at the locals window, the value of val is 1990; the sum of the values of the two roman numerals lhs and rhs. You can prove this to yourself by double-clicking on the second line in the call stack. This takes you back to operator+. In the Locals window you find lhs and rhs, and you can click on the + sign next to them to find their values, as shown in Figure 5. The values are highlighted in Figure 5. You see that the value of lhs was 567 and the value of rhs was 1423. The sum of 567 + 1423 is 1990.

7-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Why Overload Operators

Figure 5. Examining the values in the calling method.

Returning to the Implicit conversion operator, you can now step into the constructor that takes an int. Youll see that the value 1990 is passed in. You will then pop out of the operators and return to the test method, where you will display the value of r3. This invokes ToString. Step into this method. The value of val is 1990. You can watch the conversion, as you step through. The Locals window displays all you need to know about this value, as shown in Figure 6. You can see that there is 1 one thousand, 1 nine hundred, one ninety and no other values. That is the information needed to display this value as a Roman numeral.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

7-21

Operator Overloading

Figure 6. Evaluating 1990.

As you continue to step through the method, you can watch the string being built. If you want to see the value of s as you are appending to it, click on the + sign to the left of s in the Locals window. The m_Stringvalue property will display the current value of the string as you modify it, as shown in Figure 7. Each value is added in turn, building the Roman numeral as it goes.

7-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Why Overload Operators

Figure 7. Building the output string.

Converting an int
In the next part of the example, add an int to a Roman numeral. Step through this as well. Youll see that the very first method invoked is the conversion from int to Roman. You cant call operator+ unless you have two Romans, so the compiler attempts to convert the integer 5 to a Roman. Because you have an implicit conversion operator, it is able to do so. The conversion operator invokes the constructor. This explains the first two lines of the output for this part of the example. The rest of this test is identical to the one you just walked through: the addition of two Romans. The final part of the test is to add a Roman to a float:
Roman r5 = r3 + (Roman) 7.5f;

Notice that you must explicitly cast the float to a Roman, because the decimal part (.5) will be truncated. This invokes the explicit cast operator, which C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

7-23

Operator Overloading
simply casts the float value to an int, and then invokes the constructor, as shown in Figure 8.

Figure 8. The explicit float conversion.

From here, the rest of this example is just like the previous example, adding two Romans.

7-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Why Overload Operators

Summary
Operator overloading facilitates creating user-defined types that behave like intrinsic types. The CLS does not require operator overloading, so be cautious when implementing classes that will be used with languages that do not support this language feature. Operator overloading can be used to create very confusing code; be careful and parsimonious in your use of operator overloading. Some operators are matched pairs (e.g., > and <). If you overload one, you must overload the other. If you overload the equality operator (==) be sure to override object.Equals. Type conversion can be implicit (if no value may be lost) or explicit.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

7-25

Operator Overloading

(Review questions and answers on the following pages.)

7-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Why Overload Operators

Questions
1. Do all .NET languages support operator overloading? 2. When should you avoid using operator overloading? 3. What is the matched pair for operator ==? 4. What is the difference between implicit and explicit casting? 5. Should overloaded operators be static or instance members of the class?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

7-27

Operator Overloading

Answers
1. Do all .NET languages support operator overloading?
No. It is not part of the Common Language Specification, and many .NET languages, such as Visual Basic .NET do not support it.

2. When should you avoid using operator overloading?


Do not use operator overloading when the semantics of the class do not match the expected semantics of the operator. It is safe to use the addition operator for adding the key underlying values, but much more suspect to add two obscure values, and far less acceptable to have the addition operator do work that is unrelated to addition.

3. What is the matched pair for operator ==?


The matched pair for the equality operator (==) is the not-equals operator (!=).

4. What is the difference between implicit and explicit casting?


In implicit conversion no cast must be shown; the compiler will invoke the implicit conversion with no other help from the developer. Explicit conversion requires that the developer write the type explicitly.

5. Should overloaded operators be static or instance members of the class?


Overloaded operators must be static members of the class.

7-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Why Overload Operators

Lab 7: Operator Overloading

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

7-29

Lab 7: Operator Overloading

Lab 7 Overview
In this lab youll learn how to overload operators, including implicit and explicit type conversions. To complete this lab, youll need to work through two exercises: Implementing the Equality Operator Overloading Conversion Operators

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

7-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing the Equality Operator

Implementing the Equality Operator


Objective
In this exercise, youll reproduce the Roman numeral class used as an example within the lesson. It will be your job to overload the equality operator and the not-equals operator.

Things to Consider
Remember that if you overload the equality operator you will want to overload the Equals method. Can you use operator== to simplify operator !=? Why would the implementation of operator== be different from the implementation of the Equals method?

Step-by-Step Instructions
1. Open the project named EqualityOperatorStarter.sln in the EqualityOperatorStarter folder. Notice that the Roman class was provided for you, and the ToString and HashCode methods have been created. The implementation of these methods is identical to that shown in the exercises. 2. Start by creating the constructor. You need only assign the parameter value to the private member variable val.
Console.WriteLine("In Roman Constructor(int)"); this.val = val;

3. Create an equality operator. Think through what the parameters should be. Add a line of code that calls the console to announce that you are in the equality operator.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

7-31

Lab 7: Operator Overloading


// overload equality operator. // strategy: compare the integer values public static bool operator==(Roman lhs, Roman rhs) { Console.WriteLine("In operator =="); return (lhs.val == rhs.val); }

4. Override the Equals method from Object. Think through what parameter this method takes and how the method differs from the equality operator.
// takes an object (required by virtual method signature) // check to make sure it is a Roman object // if so, delegate to operator == public override bool Equals(object o) { Console.WriteLine("In method Equals"); if (! (o is Roman) ) { return false; } return this == (Roman) o; }

5. Create a non-equality operator. Can you delegate some of the work to the equality operator?
// overload the not equals operator // strategy - test operator == public static bool operator !=(Roman lhs, Roman rhs) { Console.WriteLine("In operator !="); return !(lhs==rhs); }

6. In the Run method of the Tester class, instantiate a Roman numeral with the value 678. Display the value as a Roman numeral. 7-32 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing the Equality Operator


Console.WriteLine("\n creating new Roman(567)"); Roman r1 = new Roman(678); Console.WriteLine("r1: {0}", r1.ToString());

7. Instantiate a second Roman numeral with the value 345. Display its value:
Console.WriteLine("\n creating new Roman(1423)"); Roman r2 = new Roman(345); Console.WriteLine("r2: {0}", r2.ToString());

8. Fill in the if statement to test for equality between the first and second Roman object.
if ( r1 == r2 ) Console.WriteLine("r1 == r2");

9. Fill in the if statement to test for inequality between the first and second Roman object.
if ( r1 != r2 ) Console.WriteLine("r1 != r2");

10. Examine the output from running this program as shown in Figure 9. Notice that the output shows two calls to operator == and one to operator !=. Why?

Figure 9. Running the program.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

7-33

Lab 7: Operator Overloading


11. Put a breakpoint in the program, as shown in Figure 10. Run the program to the breakpoint and step into the overloaded operators.

Figure 10. Setting a breakpoint.

7-34

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Overloading Conversion Operators

Overloading Conversion Operators


Objective
In this exercise, youll extend the previous example to add implicit and explicit conversion operators and youll add an addition operator as well.

Things to Consider
Why would you choose implicit vs. explicit conversion? Will data be lost converting a Roman to an int or vice versa? Will data be lost converting a Roman to a float or vice versa? What should be the type for the return value of the addition operator?

Step-by-Step Instructions
1. Open ConversionOperatorStarter.sln in the ConversionOperatorStarter folder. 2. Scroll down past the existing operators and methods for the Roman class, and add a new operator to support Addition. Think about which parameters this method will need.
public static Roman operator+(Roman lhs, Roman rhs) { Console.WriteLine("In operator+"); return (lhs.val + rhs.val);

3. Implement the conversion operator from int to Roman. Should this be implicit or explicit?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

7-35

Lab 7: Operator Overloading


public static implicit operator Roman(int val) { Console.WriteLine("In implicit conversion from int"); return new Roman(val); }

4. Implement the conversion operator from Roman to int. Should this be implicit or explicit?
public static implicit operator int(Roman r) { Console.WriteLine("In implicit conversion to int"); return } r.val;

5. Implement the conversion from Roman to float. Should this be implicit or explicit?
public static implicit operator float(Roman r) { Console.WriteLine("In implicit conversion to float"); return (float) r.val; }

6. Implement the conversion from float to Roman. Should this be implicit or explicit?
public static explicit operator Roman(float val) { Console.WriteLine("In explict conversion from float"); return new Roman((int) val); }

7. Add to the Run method. Instantiate a new Roman and initialize it with the sum of the first two Roman objects. Display the results to the console.

7-36

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Overloading Conversion Operators


Console.WriteLine("\n r3 = r1 + r2..."); Roman r3 = r1 + r2; Console.WriteLine("r1 + r2 = r3: {0}", r3.ToString());

8. Create a new instance of Roman and initialize it by adding the integer value 5 to the Roman object created in Step 7.
Console.WriteLine("\n r4 = r3 + 5..."); Roman r4 = r3 + 5; Console.WriteLine("r3 + 5 = r4: {0}", r4.ToString());

9. Create a new Roman object and initialize it by adding the float value 7.5 to the Roman object created in Step 8.
Console.WriteLine("\n r4 = r3 + 7.5f..."); Roman r5 = r3 + (Roman) 7.5f; Console.WriteLine("r3 + 7.5f = r5: {0}", r5.ToString());

10. Compile and run the program. It should look like Figure 11.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

7-37

Lab 7: Operator Overloading

Figure 11. The final exercise results.

7-38

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Structs and Interfaces

Structs and Interfaces


Objectives
Create structs as lightweight value-type objects. Create interfaces as contracts for classes to fulfill. Instantiate interfaces. Pass interfaces to methods. Extend interfaces to provide specialization. Use the keywords is and as to test the implementation of interfaces.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-1

Structs and Interfaces

Alternatives to Classes
The principal method for defining new types in C# is to create a class. Classes offer the full array of support for creating types, and much of your work as a C# developer will be in designing and implementing classes. There are times, however, when you will want to create special types to meet extraordinary requirements. This chapter focuses on two such occasions. You may, from time to time, need to create a lightweight value type (rather than a reference type). For this, you may choose to create a struct, rather than a class. There are limitations with structs (discussed in the next section) but if what you need is a small value type, structs may be the answer. More important, you will often want to define a contract that other classes must fulfill. For example, you may want to define what methods an object must have if it is to be stored in your database. To create such a contract, define an interface. This chapter provides examples of how to use each of these alternative types.

8-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Structs

Structs
A struct is a lightweight user-defined value type. They are similar to classes and may have properties, methods, constructors, fields, operators, nested types, and indexers. They are different from classes in some very important ways, including:
TIP:

Structs do not support inheritance. Structs are value types, not reference types. Structs do not support user-defined default constructors. There is no initialization.
Structs are sealed, meaning that you may not inherit from them, nor may a struct inherit from any class except object.

Defining Structs
You define a struct much as you do a class. The syntax is as follows:
[attributes] [access-modifiers] struct identifier [ :interface-list] { struct members }

Attributes are discussed in the advanced portion of this course. Access modifiers include public and private. Typically, structs are public. The key word struct is followed by an identifierthe struct name is like a class name. The interface list shows which interfaces the struct implements. Interfaces are described later in this chapter. The struct members define the struct, much like the class members define the class.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-3

Structs and Interfaces

Try It Out!
See structs.sln To see how to work with structs, youll create a simple application that defines a struct and manipulates instances of the struct. 1. Create a new console application named structs. 2. Create a struct named Book and give it three member fields: bookName and isbn, both of which are strings. Also give it a public member salesRank of type int. It is not uncommon to have public fields in a struct, though some might argue that it is not a good programming practice.
namespace structs { public struct Book { private string bookName; private string isbn; public int salesRank;

3. Provide a constructor that takes a name and an isbn and assigns them to the member fields. Note that your constructor must specifically set a value for the salesRank. There can be no initializer in a struct, so you must set this value in the constructor.
public Book(string name, string isbn) { bookName = name; this.isbn = isbn; salesRank = -1; }

4. Create properties for the two private fields.

8-4

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Structs
public string BookName { get { return bookName; } set { bookName = value; } }

public string ISBN { get { return isbn; }

5. Override the ToString() method. Note that while structs do not support inheritance (they are sealed) they do still derive from object, and may override methods of object.
public override string ToString() { return (String.Format("{0} ISBN: {1} Rank: {2}", bookName, isbn, salesRank)); }

6. Create a test class to test your new struct. In the Run() method, instantiate two struct objects.
Book progASP = new Book("Programming ASP.NET","0596001711"); Book cSharp = new Book("Programming C#", "0596001177");

7. Assign the following to the public field.


progASP.salesRank = 12; cSharp.salesRank = 2;

8. Use the public field to access information about the book.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-5

Structs and Interfaces


Console.WriteLine ("{0} has a sales rank of {1}", cSharp.BookName, cSharp.salesRank);

9. Use the overridden ToString method.


Console.WriteLine(progASP);

The output of this program is shown in Figure 1.

Figure 1. Testing the struct.

Structs Are Value Types


The Book struct youve created creates value objects. If you pass a Book to a method, a copy is made. You can prove this to yourself by creating a new method, PassByValue that takes a Book object as a parameter:
public void PassByValue(Book theBook) { theBook.salesRank = 20; Console.WriteLine("In the book: {0}", theBook); }

This method modifies the Book object passed in; it sets the salesRank to 20 and then displays the information about the book. Call this method from your test program, displaying the book both before and after the call:
Console.WriteLine(progASP); Console.WriteLine("\nPassing progASP to PassByValue..."); PassByValue(progASP); Console.WriteLine("Back from PassByValue: {0}", progASP);

8-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Structs
The results are shown in Figure 2. You can see that the Rank was set within the method, but when you return to Main the value is not changed. This is consistent with pass by value semantics.

Figure 2. Passing by value.

If you do need to pass a struct by reference, you do so as you would with an intrinsic type: using the ref keyword. Add another method, PassByRef that takes a Book as a reference:
public void PassByRef(ref Book theBook) { theBook.salesRank = 20; Console.WriteLine("In the book: {0}", theBook); }

This method is identical to PassByValue except that the Book type is preceded by the keyword ref. Remember to use the ref keyword when you invoke the method as well:
Console.WriteLine("\nPassing progASP to PassByRef..."); PassByRef(ref progASP); Console.WriteLine("Back from PassByRef: {0}", progASP);

The output is shown in Figure 3. You can see that using the keyword ref avoided the copy and the original object is now modified.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-7

Structs and Interfaces

Figure 3. Passing with the ref keyword.

Be Careful with Structs


Because structs are value objects they can degrade performance when you store them in collections. Most .NET collections expect to store reference objects. When you pass in a struct the struct must be boxed, and there is overhead in boxing an object. This can degrade performance when you are storing many structs. This lightweight object can actually hurt your programs performance rather than helping it!

8-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Interfaces

Interfaces
An interface is a contract. When a class implements the interface it must support the methods, properties, and events of the interface. The interface describes the contract, and the class implements that contract. The designer of a class may choose not to implement any interfaces, or the designer may choose to implement one or more interfaces. That is entirely a design decision, but if an interface is implemented, then it must be implemented in full. The formal definition of an interface is as follows:
[attributes] [access-modifier] interface identifier [:base-list] {interface body}

Attributes are an advanced topic, covered in future chapters. The access modifier is typically public. An interface can be declared with any visibility, but interface members must all be public. The keyword interface is followed by the interface name. It is a convention to have that name begin with the letter I, as in IStorable, ITransmittable, and so forth. The base list includes the list of interfaces that this interface extends. Youll see more about extending interfaces later in this chapter. The interface body defines the methods, properties, and events that must be implemented by the class fulfilling the contract.

Try It Out!
See Interfaces.sln To get started, youll create a simple interface: IInvestment. 1. Open a new console application and call it Interfaces. 2. Create a public interface named IInvestment and give it two methods as shown here.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-9

Structs and Interfaces


public interface Iinvestment { void Invest(float amount); void DisplayCurrentValue(); }

3. Notice that the Interface methods do not include implementation. The return type and parameters are listed, but there is no body; the definition is followed by a semicolon. 4. Also notice that the methods have no access modifiers. All interface methods must be public. 5. You have now created a contract. To fulfill that contract, you need a class that implements the interface. Create a Stock class and indicate that it implements the interface using the colon operator:
public class Stock : IInvestment {

6. The colon followed by the name of the interface indicates that the class implements the interface. This looks very similar to derivation, and it is up to the compiler to determine that Stock does not inherit from IInterface, but rather fulfills the interface. 7. You are free to give the implementing class any fields and methods you like, so long as you also include all the members required by the interface definition. Start by adding three private members:
private string name; private float price; private float numShares;

8. Initialize these values in the constructor.

8-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Interfaces
public Stock( string name, float price, float numShares) { this.name = name; this.price = price; this.numShares = numShares; }

9. Provide public read-only properties for the name and number of shares.
public string Name { get { return name; } } public float NumShares { get { return numShares; } }

10. Finally, it is time to implement the two methods required by the interface. The first is Invest, which must return void and take a float indicating the amount to invest. In a real program you might implement this by actually purchasing the stock and updating database records. For this simplified example, youll just set a member variable:
public void Invest (float amt) { this.numShares += (amt/price); }

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-11

Structs and Interfaces


11. Similarly, you must implement DisplayCurrentValue; a method taking no parameters and returning void.
public void DisplayCurrentValue() { Console.WriteLine("{0}: totalValue: {1}", name, price*numShares); }

12. Test this program by creating an instance of Stock and setting its values. You can then call methods defined in the interface to interact with the stock as an investment.
public class Tester { public void Run() { Stock myStock = new Stock("Liberty", 5.7f, 100f); myStock.Invest(5200f); myStock.DisplayCurrentValue(); Console.WriteLine("{0} has {1} shares", myStock.Name, myStock.NumShares);

The results are shown in Figure 4.

Figure 4. Stock class implements Iinvest.

13. To see the flexibility of the interface, create a second class, OilWell that will also implement this interface. An OilWell has different private member variables, and it can have different methods than Stock, but will also implement the required methods. Of course, the details of how an OilWell implements the Invest method may differ from how it is implemented by Stock.

8-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Interfaces
public class OilWell : Iinvestment { private string wellID; private float investment = 0;

public OilWell(string wellID) { this.wellID = wellID; }

public string WellID { get { return wellID; } }

public void Invest(float amount) { this.investment = amount; }

public void DisplayCurrentValue() { Console.WriteLine("{0}: has a current value of {1}", wellID, investment); } }

14. Notice that in this case, the OilWell invests by setting its investment field to the total value of the amount invested, while a stock implements Invest by incrementing the number of shares, computed by dividing the amount invested by the current price. 15. Add test code to your test method to instantiate an OilWell and invest in it.
OilWell myWell = new OilWell("J3752"); myWell.Invest(10000f); myWell.DisplayCurrentValue();

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-13

Structs and Interfaces


The results are shown in Figure 5.

Figure 5. A second class implements Iinvest.

Instantiating the Interface


Once you have classes that have implemented the interface, you can create instances of the interface itself. This allows you to interact with the implementing classes polymorphically. That is, you can interact through the interface and access all of the members of the interface, without regard to which class has implemented it. This ability to work with an interface generically allows you to create methods that expect an interface object, and pass in an instance of any object that implements that interface. You can see this by adding code to the test method in the example just reviewed:
IInvestment myInvestment = myStock; Console.WriteLine( "\nCalling myInvestment.DisplayCurrentValue()"); myInvestment.DisplayCurrentValue();

You have instantiated myInvestment, an object of type IInvestment, which you have initialized to refer to the object myStock. Since myStock implements IInvestment, this is a reasonable thing to do. You are now free to call any IInvestment method such as DisplayCurrentValue, and you will call that method on the myStock instance, as shown in Figure 6.

8-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Interfaces

Figure 6. Instantiating the interface.

You can reassign that same reference to IInvestment to point to the OilWell object:
myInvestment = myWell; Console.WriteLine( "\nCalling myInvestment.DisplayCurrentValue()"); myInvestment.DisplayCurrentValue();

The result of adding these lines is shown in Figure 7. You can see here that myInvestment is first assigned to myStock, and then reassigned to myWell.

Figure 7. Reassigning the reference to Iinvestment.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-15

Structs and Interfaces

Implementing More than One Interface


While a class can only inherit from (at most) one other class, it can implement any number of interfaces. This allows you to define small sets of functionality as interfaces, and then implement classes that mix and match that functionality. To see this, extend the previous example to add a second interface.

Try It Out!
See Interfaces2.sln In this next example you will add a second interface, ITaxable, and define classes that implement more than one interface. 1. Reopen the previous example. 2. Add a second interface, ITaxable, to support investments on which you will pay tax.
interface ITaxable { float ComputeTaxes(); }

3. Modify Stock to implement both interfaces.


public class Stock : IInvestment, Itaxable

4. Add a method to Stock to implement Itaxable.


public float ComputeTaxes() { return (price * numShares) * 0.3f; }

5. Do not modify OilWell. Just because stocks are both Investments and Taxable does not mean that all investments are taxable.
// no change public class OilWell : IInvestment

8-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Interfaces
6. Modify the test program to create a stock and test its implementation of the interface.
public void Run() { Stock myStock = new Stock("Liberty", 5.7f, 100f); myStock.Invest(5200f); myStock.DisplayCurrentValue(); Console.WriteLine("{0} has {1} shares", myStock.Name, myStock.NumShares); Console.WriteLine("Taxes on my stock investment: {0}", myStock.ComputeTaxes());

The results are shown in Figure 8.

Figure 8. Implementing two interfaces.

7. Try calling ComputeTaxes on the OilWell.


OilWell myWell = new OilWell("J3752"); myWell.Invest(10000f); myWell.DisplayCurrentValue(); Console.WriteLine("Taxes on my stock investment: {0}", myWell.ComputeTaxes());

When you try to compile this the compile fails with the following message:
Interfaces2.OilWell does not contain a definition for ComputeTaxes

Oops; OilWell did not implement the ITaxable interface. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-17

Structs and Interfaces

Extending Interfaces
Just as you can derive a new class from an existing class, you can extend an existing interface. This allows you to specialize the requirements of an interface, and allow classes to choose the level of specialization they will implement.

Try It Out!
See Interfaces3.sln To see how this works, reopen the previous example. This time youll add an extension to one of the interfaces you created in the previous example. 1. Reopen the previous example. 2. Create a new interface, IShelterable that extends ITaxable. This supports investments that are not only taxable, but eligible to be used as a tax shelter.
interface IShelterable : ITaxable { float TaxableAmount { get; } void ReduceTaxImpact(); }

Notice that extending the interface uses the same syntax as deriving from a class. That is, you write the name of the new interface, followed by a colon, followed by the name of the base interface. This new interface, IShelterable, adds a property and a new method. Notice also that the property is not implemented it is just defined. 3. Modify OilWell to implement Ishelterable.
public class OilWell : IInvestment, IShelterable

4. You will have to implement not only the methods of IShelterable, but also the methods of ITaxable, the interface IShelterable extends. To accomplish this, start by adding a member field taxableAmount to the OilWell:

8-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Interfaces
private string wellID; private float investment = 0; private float taxableAmount;

5. Implement the method of Itaxable.


public float ComputeTaxes() { return taxableAmount * 0.3f; }

6. Implement the method of Ishelterable.


public void ReduceTaxImpact() { taxableAmount -= taxableAmount * .97f; }

7. Modify the test program to test the Shelterable interface.


public class Tester { public void Run() { Stock myStock = new Stock("Liberty",5,100); myStock.Invest(5200); myStock.DisplayCurrentValue(); Console.WriteLine( "Taxes on my stock investment: {0}", myStock.ComputeTaxes());

OilWell myWell = new OilWell("Well 15"); myWell.Invest(20000); Console.WriteLine( "Taxes on my well: {0}",myWell.ComputeTaxes()); myWell.ReduceTaxImpact();

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-19

Structs and Interfaces


Console.WriteLine( "Taxes on my well reduced to: {0}", myWell.ComputeTaxes()); }

public static void Main() { Tester t = new Tester(); t.Run(); } }

The result of running this code is shown in Figure 9. You can see that the OilWell is able to reduce its taxes by implementing IShelterable!

Figure 9. Extending an interface.

Testing an Implementation
It is possible to create a method that takes a Taxable object (that is, an object that implements ITaxable) and pass to that method both objects that implement ITaxable and also objects that implement IShelterable. That is, wherever you can use an interface, you can use an extension to that interface. Within such a method you may need to test whether the object you have actually implements one or another extension. You can test an interface with the is keyword. The keyword is will return true if the object implements the interface you are testing and false if it does not.

8-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Interfaces

Try It Out!
See Interfaces3.sln Modify the previous example by adding a new method, Shelter. 1. Reopen the previous example. 2. Add a new method, Shelter that takes an ITaxable object. This method takes a second parameter, identifier as a string.
public void Shelter( ITaxable investment, string identifier) {

Console.WriteLine("Taxes on {0}: {1}", identifier, investment.ComputeTaxes());

3. Test the investment, and if it is shelterable then apply the shelter, otherwise indicate that by writing to the console.
if (investment is IShelterable) { IShelterable shelterable = (IShelterable) investment; shelterable.ReduceTaxImpact(); Console.WriteLine( "Taxes reduced to: {0}", shelterable.ComputeTaxes()); } else Console.WriteLine ("{0} is not shelterable!", identifier);

4. Add test code to test this method.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-21

Structs and Interfaces


OilWell myWell = new

Shelter(myWell,myWell.WellID);

Stock myStock = new Stock("Liberty",5,100); myStock.Invest(5200); myStock.DisplayCurrentValue(); Shelter(myStock,myStock.Name);

When you run the program, the results are as shown in Figure 10. The stock is not shelterable, but the OilWell is.

Figure 10. Passing interfaces to methods.

Using the as Keyword


While the is keyword works, it is not efficient to test the interface and then cast it. You can combine these two steps by using the as keyword. The keyword as casts the interface, and if the cast fails it returns null. See Interfaces5.sln You can see the difference with a small change to the example just completed.

1. Reopen the project you just completed. 2. Modify the Shelterable method to use as rather than is.

8-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Interfaces
public void Shelter( ITaxable investment, string identifier) {

Console.WriteLine("Taxes on {0}: {1}", identifier, investment.ComputeTaxes());

IShelterable shelterable = investment as IShelterable;

if (shelterable != null) { shelterable.ReduceTaxImpact(); Console.WriteLine("Taxes reduced to: {0}", shelterable.ComputeTaxes()); } else Console.WriteLine ("{0} is not shelterable!", identifier); }

3. Notice that in this example you cast the investment to a Shelterable using the keyword as, and then test to see if the cast interface is null.
IShelterable shelterable = investment as IShelterable;

if (shelterable != null) {

This is more efficient than first testing with is and then casting. The results are identical, as shown in Figure 11.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-23

Structs and Interfaces

Figure 11. Using as rather than is.

8-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Interfaces

Summary
Structs are lightweight user-defined value types. Structs may have properties, methods, constructors, fields, operators, nested types, and indices. Structs are sealed and do not support user-defined default constructors or member initialization. Interfaces create a contract. You may instantiate an interface, and you may make an interface be a parameter to a method. A class may implement zero, one, or more interfaces. An interface may be extended much as a class is derived from. You may test whether an object implements a particular interface using either the is or the as keyword.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-25

Structs and Interfaces

(Review questions and answers on the following pages.)

8-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Interfaces

Questions
1. What is the difference between a struct and a class? 2. What is an interface? 3. Can a class implement more than one interface? 4. How do you extend an interface? 5. How do you test if an instance of a class implements an interface? 6. What is the difference between the keywords is and as?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-27

Structs and Interfaces

Answers
1. What is the difference between a struct and a class?
A struct is a value type. Structs are sealed and they do not support user-defined constructors. Structs do not support initialization.

2. What is an interface?
An interface defines a contract that will be fulfilled (implemented) by a class. The interface defines the members (methods, properties, events, etc.) that the implementing class must provide.

3. Can a class implement more than one interface?


Yes, a class may implement no interfaces, a single interface, or any other number of interfaces.

4. How do you extend an interface?


You extend an interface much like you derive from a base class, using the colon operator (:). Classes implementing the extended interface must fulfill the contract of the base interface as well.

5. How do you test if an instance of a class implements an interface?


You can use the is keyword that returns a Boolean value, or you can cast using the as keyword, which returns null if the cast fails.

6. What is the difference between the keywords is and as?


The keyword is provides a test that returns a Boolean, but does not actually make the cast. The keyword as performs the cast, but if the cast fails, it returns null.

8-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Interfaces

Lab 8: Structs and Interfaces

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-29

Lab 8: Structs and Interfaces

Lab 8 Overview
In this lab youll learn how to create structs and interfaces. To complete this lab, youll need to work through two exercises: Creating Structs Implementing Interfaces

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

8-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Structs

Creating Structs
Objective
In this exercise, youll create a struct to represent a simple CD object that might be used to keep track of popular music CDs in your inventory. You will explore whether CDs are value or reference objects, and see that CDs provide much the same kind of interface as classes do.

Things to Consider
Is a struct a reference or a value type? Why would you use structs rather than classes? What are the limitations of structs?

Step-by-Step Instructions
1. Open the project named Structs_Starter.sln in the Structs_Starter folder. 2. Create a structure for a CD that might contain information about the CDs name, artist, ISBN, and sales rank.
public struct CD { private string cdName; private string artist; private string isbn; private int salesRank;

3. Create a constructor to set all the valuesset the sales rank to 1.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-31

Lab 8: Structs and Interfaces


public CD(string name, string isbn, string artist) { cdName = name; this.artist = artist; this.isbn = isbn; salesRank = -1; }

4. Create properties for the four members.


public string CDName { get { return cdName; } set { cdName = value; } }

public string ISBN { get { return isbn; } set { isbn = value; } }

public string Artist { get { return artist; } set { artist = value; } }

public int SalesRank { get { return salesRank; } set { salesRank = value; } }

5. Override ToString to display the name, artist, ISBN, and rank.

8-32

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Structs
public override string ToString() { return (String.Format( "{0} by {1}. ISBN: {2} Rank: {3}", cdName, artist, isbn, salesRank)); } }

6. Create a public method within Tester that takes a CD object by value and sets the sales rank and artist. Display the value to the console:
public void PassByValue(CD theCD) { theCD.SalesRank = 20; theCD.Artist = "D. Brubeck"; Console.WriteLine("In PassByValue with : {0}", theCD); }

7. Create a public method that takes a CD object by reference and sets the sales rank and the artist. Display the values to the console.
public void PassByRef(ref CD theCD) { theCD.SalesRank = 20; theCD.Artist = "D. Brubeck"; Console.WriteLine("In PassByRef with: {0}", theCD); }

8. Create a public method, Run that instantiates two CD objects.


public void Run() { CD TakeFive = new CD("Take Five", "B00005J96U", "Dave Brubeck"); CD Rhapsody = new CD("Rhapsody in Blue", "B0000026FG", "Gershwin");

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-33

Lab 8: Structs and Interfaces


9. Set the sales ranks for the two CDs and display one of them in detail.
TakeFive.SalesRank = 266; Rhapsody.SalesRank = 712; Console.WriteLine ("{0} has a sales rank of {1}", TakeFive.CDName, TakeFive.SalesRank); Console.WriteLine(TakeFive);

10. Pass one of the CDs to the method that takes a CD struct by value. Display its contents when you return:
Console.WriteLine( "\nPassing TakeFive to PassByValue(TakeFive); Console.WriteLine("Back from PassByValue: {0}", TakeFive); PassByValue...");

11. Pass one of the CDs to the method that takes a CD struct by reference. Display its contents when you return:
Console.WriteLine("\nPassing TakeFive to PassByRef..."); PassByRef(ref TakeFive); Console.WriteLine("Back from PassByRef: {0}", TakeFive);

12. Compile and run the program. It should look like Figure 12.

Figure 12. Final exercise results.

8-34

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing Interfaces

Implementing Interfaces
Objective
In this exercise, youll create and extend interfaces, and youll create classes that implement the interfaces.

Things to Consider
What is the purpose of an interface? Why might you extend an interface? What must a class do to implement the interface?

Step-by-Step Instructions
1. Open the project named Interfaces_Starter.sln in the Interfaces_Starter folder. 2. Create an interface named IShelfable. This will represent items that might be on a shelf in your store. The interface requires that implementing classes provide a method PutOnShelf that returns void and takes no parameters, and a read-only property that provides the classification for the shelfable object:
public interface IShelfable { void PutOnShelf(); string Classification { get; } }

3. Create an interface ISellable for any item that might be sold. Provide a property for the selling price:
public interface ISellable { float Price { get; set; } }

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-35

Lab 8: Structs and Interfaces


4. Create a new interface that extends ISellable, named IDiscountable. Add a read-only property for the discount percentage. Also add a method that takes no parameters and returns void, named ReduceSellingPrice.
public interface IDiscountable : ISellable { float DiscountPctg { get; } void ReduceSellingPrice(); }

5. Create a Book class that implements both IShelfable and ISellable.


public class Book : IShelfable, ISellable {

6. Add three members to the Book class: name, price, and classification. Use a float for the price and a string for the others.
private string name; private float price; private string classification;

7. Write the constructor for the Book class.


public Book( string name, float price, string classification) { this.name = name; this.price = price; this.classification = classification; }

8. Implement the Name property (read-only):

8-36

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing Interfaces
public string Name { get { return name; } }

9. Implement the method PutOnShelf. For the purposes of this lab, just display the name of the book, its selling price, and its classification. NOTE To display the selling price as a currency, use the C formatter. To display the second parameter as a price rather than using the substitution parameter {1}, you may instead use {1:C} (note the colon between the 1 and the C).

public void PutOnShelf () { Console.WriteLine( "Displaying {0} selling for {1:C} Classify as: {2}", name, price,classification); }

10. Implement the Price property.


public float Price { get { return price; } set { price = value; } }

11. Implement the read-only Classification property.


public string Classification { get { return classification; } }

12. Implement a class CD to represent a music CD that implements both IShelfable and Idiscountable. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-37

Lab 8: Structs and Interfaces


public class CD : IShelfable, IDiscountable {

13. Create the four private fields for the CD class: cdName, price, discountPctg, and classification. The members price and discountPctg will be floats, the other two will be strings. Initialize the classification to Popular Music.
private string cdName; private float price; private float discountPctg; private string classification = "Popular Music";

14. Implement the read-only property DiscountPctg.


public float DiscountPctg { get { return discountPctg; } }

15. Implement the Price property.


public float Price { get { return price; } set { price = value; } }

16. Implement the read-only property Classification.


public string Classification { get { return classification; } }

17. Implement the constructor.

8-38

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing Interfaces
public CD(string name, float price, float discount) { this.cdName = name; this.price = price; | } this.discountPctg = discount;

18. Implement the method PutOnShelf. For the purposes of this lab, just display the name of the book, its selling price, and its classification. NOTE To display the selling price as a currency, use the C formatter. To display the second parameter as a price rather than using the substitution parameter {1} you may instead use {1:C} (note the colon between the 1 and the C).

public void PutOnShelf () { Console.WriteLine("Displaying {0} selling for {1:C}", cdName,price); }

19. Implement ReduceSellingPrice by applying the available discount to the current price:
public void ReduceSellingPrice() { price -= price * discountPctg; }

20. In the Run method, create a CD object and put it on the shelf.
CD takeFive = new CD("Take Five", 15.75f, .10f); takeFive.PutOnShelf();

21. Reduce the selling price of the CD and put it back on the shelf.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

8-39

Lab 8: Structs and Interfaces


takeFive.ReduceSellingPrice(); takeFive.PutOnShelf();

22. Create a new book object and put it on the shelf.


Book progCS = new Book( "Programming C#",39.95f,"Programming"); progCS.PutOnShelf();

23. Set its price and reshelf it.


progCS.Price = 45.50f; progCS.PutOnShelf();

24. Compile and run the program. It should look like Figure 13.

Figure 13. Final exercise results.

8-40

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Arrays

Arrays
Objectives
Create and use arrays to store same-type items in a simple, safe, ordered collection. Iterate over arrays using the foreach loop. Build rectangular multidimensional arrays, as well as jagged arrays of arrays. Pass in variable numbers of parameters using the params keyword. Create indexers for your own collection classes.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-1

Arrays

Defining Arrays
An array is a collection that is built into the C# language. An array is a simple, sized collection composed entirely of the same type of objects. That is, if you create an integer array you can hold a fixed number of integers and no objects of other types. When you create the array you must tell the compiler how many items are in the array. Arrays are indexed, starting at 0. That means that you access each item in the array using a specific integer index. If you create an integer array named myIntArray, the first item in the array is myIntArray[0] and the second item in the array is myIntArray[1]. C# considers the array to be part of the language, maintaining compatibility with C and C++. That said, when you create an array in C++, what you actually create is an instance of System.Array. This gives you the backward compatibility of making the array part of the language, but the object-oriented flexibility of interacting with an actual class. Here is the formal syntax for creating an array:
type[] identifier;

The identifier is the name of the array. For example, you might create an array of integers named myIntArray with the following declaration:
int[] myIntArray;

Instantiating an Array
You instantiate an array with the keyword new. At the time you instantiate the array you must assign it as size:
int[] myIntArray; myIntArray = new int[5]; // size to hold 5 integers

In the example, the five integers can be accessed as follows:

9-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Defining Arrays
int first = int second = int third = int fourth = int fifth = myIntArray[0]; myIntArray[1]; myIntArray[2]; myIntArray[3]; myIntArray[4];

Notice that the first element in the array is indexed at 0 and the fifth at index 4. Programmers say that an array of n elements is indexed 0 to n-1.

Reference and Value Types


Each element in the array is an object of the type defined for the array. An array may hold objects of either type: intrinsic or user-defined. The elements of the array are created based on their type, but the array itself is a reference type.

Initialized Values
When you add a value type to an array it is initialized to its default value: Numeric values are initialized to 0 Boolean values are initialized to false Character values are initialized to null (\0) Enumerated constants are initialized to 0

Reference types are not initialized to their default values; they are initialized to null. If you attempt to access an uninitialized reference type within an array, an exception will be thrown (exceptions are explained elsewhere in this course).

foreach Loops
You can iterate over an array (and many other collections) with the foreach statement. Here is the formal declaration of foreach:
foreach(type-identifier in expression) statement;

The type-identifier identifies the type of elements in the array. The expression, in this case, will be the name of the array. If you create an array of Employee

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-3

Arrays
objects named empArray, you can print the ID value from each with the following foreach loop:
foreach(Employee emp in empArray) Console.WriteLine(ID: {0}, emp.ID);

In this simple example, the local variable emp will be a reference to each Employee object in the array empArray. Each time through the loop, emp refers to the next Employee object in the array.

Try It Out!
See arrays.sln The best way to see how to work with arrays is to create a simple array and then to iterate over it. 1. Create a new console application and name it Arrays. 2. Create an Employee class to store in the array. Give the Employee two member fields and a constructor.
public class Employee { private int age; private string name;

public Employee(int age, string name) { this.age = age; this.name = name; }

3. In Employee, override ToString to return the Employees name.


public override string ToString() { return name; }

9-4

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Defining Arrays
4. In the test method Run, create an array of four Employee objects.
public void Run() { Employee[] empArray = new Employee[4];

5. Instantiate an Employee object, passing in the value 20 as the first parameter (age) and John Galt as the second parameter (name). You get back a reference to the Employee; rather than assigning this to a local variable, assign it directly to the first array element.
empArray[0] = new Employee(20, "John Galt");

6. Assign three more Employee objects to the subsequent array elements.


empArray[1] = new Employee(30, "Dagney Taggert"); empArray[2] = new Employee(40, "Hank Reardon"); empArray[3] = new Employee(50, "Ayn Rand");

7. Iterate through the array and display the contents. For the first iteration use a simple for loop. To test the counter variable (i) use the length of the array. The length property returns the number of elements in the array (4), so you want to iterate from 0 through 3 (n-1).
for (int i = 0; i < empArray.Length; i++) Console.WriteLine( "empArray[" + i.ToString() + "]: {0}", empArray[i].ToString());

8. Iterate through the loop again, this time using a foreach loop.
foreach (Employee instance in empArray) Console.WriteLine("{0}", instance.ToString());

The results are shown in Figure 1. The advantage of the simple for loop is that you have a counter that can be used to identify the members. The advantage of the foreach loop is that it is simple and clean.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-5

Arrays

Figure 1. Iterating over the array.

Initializing the Array


It is possible to initialize the contents of the array at the time it is instantiated:
int myFirstIntArray = new int[5] { 2, 3, 4, 5, 6 }

If you are initializing all the members of the array, you are free to leave out the operator new, the type, and the size; the compiler will create the appropriately sized array for you. So, the initialization above is identical to the following initialization:
int myFirstIntArray = { 2, 3, 4, 5, 6 }

9-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Multidimensional Arrays

Multidimensional Arrays
Until now, youve worked only with arrays that have a single index. It is possible for arrays to have more than one index. This is known as adding dimensions to the array. Multidimensional arrays come in two flavors: Rectangular arrays Jagged arrays (or arrays of arrays)

Rectangular Arrays
A simple array can be thought of as a series of boxes all in a row, as shown in Figure 2. Here, myInt is a simple (one-dimensional) array with six elements.

Figure 2. A simple array.

The simplest rectangular array is an array of two dimensions. An array of two dimensions can be thought of as an array with rows and columns, where each row is like a simple one-dimensional array, as illustrated in Figure 3. Here, myRectArray is an array of two dimensions. The first dimension describes the number of rows (3) and the second describes the number of columns (6).

Figure 3. A rectangular array.

In a rectangular array, the number of columns is the same in each row. Rectangular arrays are not limited to two dimensions. A three-dimensional C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-7

Arrays
array can be thought of as a cube. In C# you are not limited to three dimensions; you may have four, five, or any number of dimensions that is useful (though ones mind reels at creating nine-dimensional objects!)

Try It Out!
See Array2.sln The best way to see how to work with a rectangular array is to create one. 1. Create a new console application named Arrays2. 2. Create constants for the two dimensions: number of values in a row and number of values in a column.
const int rows = 4; const int columns = 5;

3. Instantiate the array.


int[,] rectArray = new int[rows,columns];

Notice that the declaration of the array does not provide the size of the dimensions; the comma simply indicates that there are two dimensions (for three dimensions there would be two commas). When the array is instantiated using the keyword new, however, the size of both dimensions (rows, columns) must be supplied. 4. Fill the array by using a for loop.
for(int i = 0; i < rows; i++) for(int j = 0; j < columns; j++) rectArray[i,j] = i*j+j;

The logic here is to iterate through each row with the outer for loop. Within each iteration of the row, you iterate through all the columns. The values placed into the array are somewhat arbitrary (multiplying the current row times the column and adding the column). 5. Iterate through the array again, this time displaying the contents of the array.

9-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Multidimensional Arrays
for(int i = 0; i < rows; i++) for(int j = 0; j < columns; j++) Console.WriteLine("rectArray[{0},{1}] = {2}", i,j, rectArray[i,j]);

The results of iterating through the array are shown in Figure 4. Notice that for any given row/column you can verify that the value is correct. For example, for row = 2 column = 3 (rectArray[2,3]), the result is the row (2) times the column (2*3 = 6) plus the column (6+3 = 9).

Figure 4. Iterating through an integer array.

6. Add a second array. This time, rather than dimensioning it explicitly, initialize the array on creation and allow the compiler to size the array implicitly.
int[,] secondRectArray = { {2,4,6,8,10}, {8,10,12,14,16}, {14,16,18,20,22}, {20,22,24,26,28} };

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-9

Arrays
7. This is a 4x5 array, just as the previous one was. You can iterate through this array using the same construct.
for(int i = 0; i < rows; i++) for(int j = 0; j < columns; j++) Console.WriteLine("secondRectArray[{0},{1}] = {2}", i,j, secondRectArray[i,j]);

Note that this array must be treated as a 4x5 array. In other C-family languages (C and C++) a 4x5 array is really a 20-element array that can just as easily be viewed as a 5x4 array. That is not true in C#, and if you try to treat your array as a 5x4 array you will receive a compile error. For example, if you were to write the following code, the program would compile but when you run it, it would throw an exception:
for(int i = 0; i < columns; i++) for(int j = 0; j < rows; j++) Console.WriteLine("secondRectArray[{0},{1}] = {2}", i,j, secondRectArray[i,j]);

Jagged Arrays
Think of jagged arrays as arrays of arrays. Each sub-array in the jagged array need not be the same size as the other arrays in the jagged array, as shown in Figure 5. Here jagged is a jagged array. The definition calls for the first dimension to be three; so there will be three arrays within jagged. The size of the internal arrays is not specified. You can see that one has three elements, one has six, and one has four.

Figure 5. A jagged array.

9-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Multidimensional Arrays

Try It Out!
See Arrays3.sln To see how to create a jagged array, youll build a simple array of arrays of integers. 1. Create a new console application called Array3. 2. In this case youll need only a single constant to indicate the number of rows.
int[][] jaggedArray = new int[rows][];

3. Instantiate four arrays, each of a different size, for the four rows within jaggedArray.
jaggedArray[0] = new int[3]; jaggedArray[1] = new int[2]; jaggedArray[2] = new int[4]; jaggedArray[3] = new int[2];

This is the essence of the jagged array. Each new array is assigned to a member of the array of arrays: jaggedArray and the various arrays need not be of the same length. 4. Assign values to some of the members of the jagged array.
jaggedArray[0][0] = 2; jaggedArray[0][1] = 3; jaggedArray[0][2] = 4; jaggedArray[1][0] = 5; jaggedArray[1][1] = 3; jaggedArray[2][1] = 6; jaggedArray[3][0] = 30; jaggedArray[3][1] = 40;

Notice that you do not need to fill all the values. Values that are not set have a default value based on the type of the contents of the array (in this case, an integer array, the values are set to zero). 5. Iterate through the various arrays, displaying their values. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-11

Arrays

for (int i = 0; i < jaggedArray[0].Length; i++) Console.WriteLine("JaggedArray[0][{0}] = {1}", i,jaggedArray[0][i]);

for (int i = 0; i < jaggedArray[1].Length; i++) Console.WriteLine("JaggedArray[1][{0}] = {1}", i,jaggedArray[1][i]);

for (int i = 0; i < jaggedArray[2].Length; i++) Console.WriteLine("JaggedArray[2][{0}] = {1}", i,jaggedArray[2][i]);

for (int i = 0; i < jaggedArray[3].Length; i++) Console.WriteLine("JaggedArray[3][{0}] = {1}", i,jaggedArray[3][i]);

The results are shown in Figure 6. Notice that the values that were not set are displayed as 0.

Figure 6. Iterating through the jagged array.

Using the Index Operator


When you access members of an array, you do so through the index operator ([]). With a rectangular array, you access members by using two values within a single index operator:

9-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Multidimensional Arrays
int a = myRectArray[3,5];

When you access members of a jagged array, however, you put each index into its own index operator. This is consistent with the idea of an array of arrays:
int a = myJaggedArray[3][5];

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-13

Arrays

Params
Until now, all of the parameters to a method have been explicitly declared in the method signature. There are times, however, when you do not know how many objects youll want to pass to a method. You can use method overloading, but C# offers an additional powerful option: the params keyword. Params declares that your method will take an unknown (at compile time) number of parameters. Within the method, the parameters are treated as if they were submitted in an array. When you actually call the method, however, you pass in any number of objects of the declared type, and the compiler assembles them into the array. See Params.sln The easiest way to see how this works is to write a simple test program. 1. Create a new console application named params. 2. Create a method that takes an unknown number of parameters. To do this, name a single parameter values. You may use any name you want; values is just an arbitrary name. Declare the type of the parameter to be an int array, with the keyword params.
public void DisplayValues(params int[] values)

3. Within the body of the method, treat the values parameter as if it were an array of ints.
public void DisplayValues(params int[] values) { foreach (int i in values) Console.WriteLine(i); }

4. Create a test class. In the Run method, create a number of int variables and pass them, in various combinations, to DisplayValues.

9-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Params
public void Run() { int x = 5, y = 7, z = 9, a = 11, b = 13; Console.WriteLine("Pass in all 5 integers..."); DisplayValues(x,y,z,a,b); Console.WriteLine("\nPass in 3 integers..."); DisplayValues(a,b,x); }

5. Notice that the first time DisplayValues is called, five integers are passed in, but the second time, only three are passed in. The results are shown in Figure 7.

Figure 7. Using params with variable arguments.

6. You can modify the program to take user-defined types rather than integers. To do so, create a simple Employee class:
class Employee {

7. Give the class a single private variable of type string, and a constructor to initialize that variable.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-15

Arrays
private string name;

public Employee(string name) { this.name = name; }

8. Override the ToString() method to return the Employees name.


public override string { return name; } ToString()

9. Modify the DisplayValues method to take an array of Employee objects.


public void DisplayValues(params Employee[] values) { foreach (Employee e in values) Console.WriteLine(e); }

10. Modify the test program. Comment out the integer test and create four Employee objects. Pass them into DisplayValues and note that the same logic applies to treating the parameter now as an array of Employee objects.
public void Run() { /* int x = 5, y = 7, z = 9, a = 11, b = 13; Console.WriteLine("Pass in all 5 integers..."); DisplayValues(x,y,z,a,b); Console.WriteLine("\nPass in 3 integers..."); DisplayValues(a,b,x); */

Employee john = new Employee("John");

9-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Params
Employee paul = new Employee("Paul"); Employee george = new Employee("George"); Employee ringo = new Employee("Ringo");

Console.WriteLine("\nNow pass in Employees..."); DisplayValues(john, paul, george, ringo); }

The results are shown in Figure 8. The params keyword can just as easily be used with user-defined types as it can with intrinsic types.

Figure 8. Params with user-defined types.

Mixing Types
You can create a method that takes a variable number of parameters of different types. To do so, you take advantage of the fact that everything in C# derives from object. Declare the params type to be object and it will be safe to pass in objects of any type. Reopen the project you just completed, and modify DisplayValues to take an object rather than an Employee:
public void DisplayValues(params object[] values) { foreach (object o in values) Console.WriteLine(o); }

Uncomment the int test and test both int and Employee:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-17

Arrays
public void Run() {

int x = 5, y = 7, z = 9, a = 11, b = 13; Console.WriteLine("Pass in all 5 integers..."); DisplayValues(x,y,z,a,b); Console.WriteLine("\nPass in 3 integers..."); DisplayValues(a,b,x);

Employee john = new Employee("John"); Employee paul = new Employee("Paul"); Employee george = new Employee("George"); Employee ringo = new Employee("Ringo");

Console.WriteLine("\nNow pass in Employees..."); DisplayValues(john, paul, george, ringo); }

The result is shown in Figure 9. This works because you are calling only methods common to the ToString()object (which is implicitly called by Console.WriteLine()).

Figure 9. Mixing types with params.

9-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Params
If you want to take advantage of a method or property specific to Employee, you will have to cast the parameter. Once again, you can use the keyword is to test whether the object is of the expected type. To see this, reopen the example and add a property to Employee:
public string Name { get { return name; } }

You can now modify DisplayValues to test whether the object you are working with is an Employee. If so, you can get the property:
if (o is Employee) { Employee e = (Employee)o; Console.WriteLine("Got Employee: {0}", e.Name); }

If the object is an Employee, you can cast the object and then invoke the property. The result is shown in Figure 10. Notice that for the employees the cast is skipped (an int is not an Employee) but for the employees the test returns true and the value is printed using the property.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-19

Arrays

Figure 10. Casting the object.

The params keyword gives you a great deal of flexibility when defining methods; you can decide at run time how many objects to pass in to the method and treat the result as an array.

9-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Indexers

Indexers
Arrays are built into C#, but it would be nice if you could add the ability to access data within your own collection classes using indexing. For example, assume you have a user-defined type office that acts as a collection of employees. It would be convenient if you could index into the office to find the fifth employee with the following code:
Employee employeeOfTheWeek = myOffice[4];

The square brackets operator is called the index operator (sometimes called the indexer). You can overload this operator much as you overload other operators in C#. NOTE Index operators only really make sense with classes that act like a collection.

Overloading the Index Operator


Indexers are implemented in a way very similar to properties. You use the this keyword along with the index type:
public string this[int offset] { get{} set {} }

The code above defines an index. Offset is an integer value that will be used to index into your collection. By providing both a get and a set, you allow the client to read from and write to your collection using the indexer:
// read from the collection Employee employeeOfTheWeek = myOffice[4];

// create a new Employee Employee joe = new Employee(25,Joe); // write to the collection myOffice[12] = joe;

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-21

Arrays
You can index on values other than integers! For example, it might make sense to allow the client to index on strings:
public string this[string name] { get{} set {} }

This allows you to write code like this:


Employee employeeOfTheWeek = myOffice[Joe];

The code shown above will index into the Office instance myOffice and find and retrieve the Employee whose name is Joe.

Try It Out!
See Indexers.sln The best way to see how to work with indexers is to write a small test program. 1. Create a new console application named Indexer. 2. Create an Employee class to store within your collection. The Employee class will have two fields, both strings. The field name will hold the Employees name and the field empID will hold the Employees employee ID. 3. Create a constructor to initialize the fields, and create properties to access the two fields.
public class Employee { string name; string empID;

public Employee(string name, string ID) { this.name = name; empID = ID; }

public string EmpID {

9-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Indexers
get { return empID; } }

public string Name { get { return name; } } }

4. Create a collection class named office to hold Employees. You could store the employees in any number of collection types, or you could store the employees in a database. To keep the example simple, youll store the Employee objects in a simple array. In addition, youll keep an integer field to keep track of the number of Employee objects already in the array.
public class Office { private Employee[] employees; private int ctr = 0;

5. Initialize the array of Employee objects in the constructor. Allow enough room for all the Employees your office will hold. The constructor will take a variable number of Employee objects, by using the params keyword. Initialize the array with the Employees passed in to the constructor.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-23

Arrays
public Office(params Employee[] employeeList) { // allocate space for the employees this.employees = new Employee[256];

// copy the employees passed in to the constructor foreach (Employee e in employeeList) { this.employees[ctr++] = e; } }

6. Add an indexer for the office, based on an integer value. The indexer will take an int, and test to make sure the int is within the legal range. If not, it will handle the error (possibly throwing an exception); otherwise it will set or retrieve the appropriate value.

9-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Indexers
public Employee this[int index] { get { if (index < 0 || index >= employees.Length) { // handle bad index } return employees[index]; } set { if (index >= employees.Length) { // handle error } else { employees[index] = value; if (ctr < index) ctr = index; } } }

Notice that the underlying array is now acting as a sparse array. That is, it is legal to set an Employee at offset 25 even if you only have four Employee objects in the array. If you prefer not to allow this, you handle that problem in the set part of the indexer. If you were storing your Employee objects in a database, then the get and set part of the indexer would manage retrieving and storing the data in the database. 7. Add a method, DisplayEmployees that iterates over the array of Employee objects and displays them to the console.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-25

Arrays
public void DisplayEmployees() { foreach (Employee e in employees) { if (e != null) { Console.WriteLine("{0} Employee ID: {1}", e.Name, e.EmpID); } } }

8. Write a test class and method to create Employee objects and add them to the office.
public void Run() { Employee[] empArray = new Employee[5]; empArray[0] = new Employee("John Galt","001"); empArray[1] = new Employee("Figaro Barbara", "002"); empArray[2] = new Employee("Wotan Warrior", "003"); empArray[3] = new Employee("George Washington", "004"); empArray[4] = new Employee("John Adams", "005"); Office office = new Office(empArray);

In the code shown above, a new office is created by passing an array of Employee objects to the office constructor. It is legal to pass an array to a params parameter, though you could have passed the five Employee objects individually. 9. Add another Employee through the indexer.
office[10] = new Employee("Thomas Jefferson", "007");

10. Call DisplayEmployees to see the Employees youve added.


office.DisplayEmployees();

9-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Indexers
11. Access an Employee using the indexer, and assign that Employee to a local object.
Employee empOfTheMonth = office[3];

12. Access the local Employee objects Name property, and display it.
Console.WriteLine("Employee of the month: {0}", empOfTheMonth.Name);

The results of this code are shown in Figure 11.

Figure 11. Adding an indexer to a class.

The syntax of working with indexers in a user-defined collection is comfortable and, once again, furthers the aim of making classes act just as intrinsic types do.

Indexing on Strings
See Indexer2.sln With this application working, you are ready to add an index based on the Employees name 1. Add a new indexer to Office based on string.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-27

Arrays
public Employee this[string index] { get { if (index.Length == 0) { // handle bad index }

return this[FindEmployee(index)]; } set { employees[FindEmployee(index)] = value; } }

2. Both the get and the set index functions require that you access the intbased indexer, passing in the int value of the Employee record corresponding to the string value index that was passed in as a parameter. If the client passes in Joe as a string, that value will be passed to the method FindEmployee (described in the next step). FindEmployee will return an int representing the index for the record corresponding to the Employee whose name is Joe. You then pass that int to the index that takes an int and you get back the Employee record. 3. Write the findEmployee method. It takes a string representing the Employees name, and iterates through its records until it finds the correct record.

9-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Indexers
private int FindEmployee(string employeeName) { for (int i = 0;i<employees.Length;i++) { if (employees[i] != null && employees[i].Name == employeeName) { return i; } } return -1; }

Because Office supports a sparse array it is possible that the record at a given offset might be null. If so, accessing the Name property would throw an exception. The first half of the if statement tests that employees[i] is not null. If it is null, then the second half of the and statement within the if is never tested. This is called short-circuit evaluation, and is covered elsewhere in this course. The net effect is that the Name property is only accessed on non-null entries in the underlying array. If the name in the record matches the parameter, then the value of the offset is returned, and it is that value (i) that is used as an offset into the int-based index. 4. Add to the test code to retrieve the record using the string:
public void Run() { Employee[] empArray = new Employee[5]; empArray[0] = new Employee("John Galt","001"); empArray[1] = new Employee("Figaro Barbara", "002"); empArray[2] = new Employee("Wotan Warrior", "003"); empArray[3] = new Employee("George Washington", "004"); empArray[4] = new Employee("John Adams", "005"); Office office = new Office(empArray); office[10] = new Employee("Thomas Jefferson", "007");

office[20] = new Employee("Sam Adams", "008");

Employee empOfTheMonth = office[3];

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-29

Arrays
Employee bestBrewer = office["Sam Adams"];

Console.WriteLine("Employee of the month: {0}", empOfTheMonth.Name); Console.WriteLine("Best brewer in the office: {0}", bestBrewer.Name); Console.WriteLine("Office[10] = {0}", office[10].Name);

The results of running this test program are shown in Figure 12.

Figure 12. Using the string-based indexer.

5. The use of the string-based indexer can be a bit confusing. To make it explicit, put a breakpoint on line 150 where bestBrewer is initialized using the string offset operator, as shown in Figure 13.

9-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Indexers

Figure 13. Setting a breakpoint on the string-based indexer.

6. Press F5 to run to the breakpoint. 7. Press F11 to step into the indexer. Press F11 again to get to the return statement.
return this[findEmployee(index)];

The return statement calls FindEmployee, passing in the index, as shown in Figure 14. You can see that the index passed in to this method is the string SamAdams (see the circled, highlighted value in the Locals window).

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-31

Arrays

Figure 14. Invoking FindEmployee.

8. Press F11 to step into FindEmployees. As you step through, for each entry in employees that is not null you will step into the Name property. The value returned is then compared to employeeName (Sam Adams). 9. Set a breakpoint return i on line 106. This breakpoint will fire when the Name property matches the employeeName parameter. Run to the breakpoint. 10. When the breakpoint fires, check the Locals window. The value of i is the index at which the matching record is found, as shown in Figure 15.

9-32

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Indexers

Figure 15. Returning the value of the offset for the target record.

You can see that i is set to the value 20. That is the value returned by FindEmployee. 11. Press F11 to exit from FindEmployee, returning back to the string indexer. Notice that the return statement invokes the integer-based indexer. The value returned from FindEmployee is used as the index value. 12. Press F11 again to step into the int-based indexer. Here the value (20) is used as an index into the underlying array, and the value within that array (the Employee object at offset 20) is returned, as shown in Figure 16.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-33

Arrays

Figure 16. Retrieving the Employee object.

What is returned is an Employee object, which is assigned to the reference bestBrewer. You may then use that reference to refer to the object held in the office at offset Sam Adams.

9-34

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Indexers

Summary
Arrays offer a simple sized collection of objects in which all objects are of the same type. Arrays are indexed, starting at 0. When you instantiate an array you must size it, or you must provide the initialization list from which the compiler can deduce the size. Foreach loops can be used to iterate over the array, extracting each value in turn. Arrays may have more than one dimension. Multidimensional arrays come in two flavors: rectangular and jagged. Jagged arrays are best described as arrays of arrays. You can add an index operator to your own classes. Index operators only makes sense in classes that act as collections. Index operators can index on any type, such as string, int, etc.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-35

Arrays

(Review questions and answers on the following pages.)

9-36

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Indexers

Questions
1. How do you refer to the second item in an array? 2. How do you refer to the fifth item in the third row of a two-dimensional rectangular array? 3. How do you refer to the fifth item in the third row of a two-dimensional jagged array? 4. How do you declare a method that returns void and takes one or more integer variables as parameters? 5. How do you declare an indexer for a collection class Jar that indexes on an integer and returns a Marble object? 6. How do you declare an indexer for a collection class Jar that indexes on a string and returns a Marble object?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-37

Arrays

Answers
1. How do you refer to the second item in an array?
myArray[1]; // remember offsets start at 0

2. How do you refer to the fifth item in the third row of a two-dimensional rectangular array?
myArray[2,4];

3. How do you refer to the fifth item in the third row of a two-dimensional jagged array?
myArray[2][4];

4. How do you declare a method that returns void and takes one or more integer variables as parameters?
public void myMethod(params int[] myParameter)

5. How do you declare an indexer for a collection class Jar that indexes on an integer and returns a Marble object?
public Marble this[int index]{get{ // } set {//} };

6. How do you declare an indexer for a collection class Jar that indexes on a string and returns a Marble object?
public Marble this[string index]{get{ // } set {//} };

9-38

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Indexers

Lab 9: Arrays

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-39

Lab 9: Arrays

Lab 9 Overview
In this lab youll learn to work with Arrays and the Params keyword, and youll work with Indexers. To complete this lab, youll need to work through two exercises: Using the Keyword Params Implementing an Indexer

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

9-40

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Using the Keyword Params

Using the Keyword Params


Objective
In this exercise, youll explore passing a variable number of parameters to a method using the params keyword.

Things to Consider
The params keyword signals that the parameters passed should be treated like an array. You can pass in values one by one, or you can pass in an array. You can iterate over an array using the foreach loop.

Step-by-Step Instructions
1. Open the project named ParamsStarter.sln in the ParamsStarter folder. 2. In the Cat class, create the constructor.
public Cat(string name) { this.name = name; }

3. Override ToString to return the Cats name.


public override string { return name; } ToString()

4. Provide a public, read-only property for the name.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-41

Lab 9: Arrays
public string Name { get { return name; } }

5. Within the Params class, create a method DisplayValues. This method takes a variable number of parameters.
public void DisplayValues(params object[] values) {

6. Within DisplayValues iterate over the objects provided. If the iterator has a Cat object, display the Cats name.
foreach (object o in values) { Console.WriteLine(o);

if (o is Cat) { Cat c = (Cat)o; Console.WriteLine("Got Cat: {0}", c.Name); } }

7. Within the Run method, create five integers and pass them to DisplayValues.
int x = 5, y = 7, z = 9, a = 11, b = 13; Console.WriteLine("Pass in all 5 integers..."); DisplayValues(x,y,z,a,b);

8. Call DisplayValues again, and pass in only three integers.


Console.WriteLine("\nPass in 3 integers..."); DisplayValues(a,b,x);

9-42

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Using the Keyword Params


9. Create four cat objects and pass them to DisplayValues.
Cat frisky = new Cat("Frisky"); Cat boots = new Cat("Boots"); Cat puff = new Cat("Puff"); Cat poco = new Cat("Poco");

Console.WriteLine("\nNow pass in Cats..."); DisplayValues(frisky, boots, puff, poco);

10. Create an array of four Cat objects. Fill the Array with the four Cat objects you created in Step 9 and pass that array to DisplayValues.
Cat[] catArray = new Cat[4]; catArray[0] = frisky; catArray[1] = boots; catArray[2] = puff; catArray[3] = poco;

Console.WriteLine("\nNow pass in an array of Cats..."); DisplayValues(catArray);

11. Compile and run the program, as shown in Figure 17.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-43

Lab 9: Arrays

Figure 17. Final exercise results.

9-44

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing an Indexer

Implementing an Indexer
Objective
In this exercise, youll create a Cat class and a Kennel class. The Kennel will hold a collection of Cat objects and will provide array-like access using an indexer.

Things to Consider
The Kennel class must implement its collection, either using an underlying private member variable or interacting with a back-end data store such as a database. The indexer should check for out of bounds requests.

Step-by-Step Instructions
1. Open IndexerStarter.sln in the Indexer Starter folder. 2. Create a Cat class. Youll use this as an object to hold in your Kennel class.
public class Cat {

3. Cat gets two member variables: a name and an age.


string name; int age;

4. Implement a constructor.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-45

Lab 9: Arrays
public Cat(string name, int age) { this.name = name; this.age = age; }

5. Implement read-only properties for the age and the name.


public int Age { get { return age; } }

public string Name { get { return name; } }

6. Create a Kennel class to act as a collection for the Cat objects.


public class Kennel {

7. Give Kennel a private array of Cat objects. This will act as the backingstore for the underlying data.
private Cat[] cats;

8. Create the constructor. Pass in a variable number of Cat objects (using Params).

9-46

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing an Indexer
public Kennel(params Cat[] catList) { // allocate space for the cats this.cats = new Cat[256]; int ctr = 0;

// copy the cats passed in to the constructor foreach (Cat c in catList) { this.cats[ctr++] = c; } }

9. Implement the indexer, allowing the user to index into the Kennel by providing an integer (just as you would do for an array).
public Cat this[int index] { get { if (index < 0 || index >= cats.Length) { // handle bad index } return cats[index]; } set { if (index >= cats.Length) { // handle error } else { cats[index] = value; } } }

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-47

Lab 9: Arrays
10. Implement a method DisplayCats that iterates over the collection and displays the name and age.
public void DisplayCats() { foreach (Cat c in cats) { if (c != null) { Console.WriteLine("{0} is {1} years old", c.Name, c.Age); } } }

11. Within the Run method, create an array of five Cat objects and populate the array.
Cat[] catArray = new Cat[5]; catArray[0] = new Cat("Frisky",5); catArray[1] = new Cat("Figaro", 2); catArray[2] = new Cat("Wotan", 3); catArray[3] = new Cat("Washington",4); catArray[4] = new Cat("Adam", 5);

12. Create an instance of Kennel and pass in the array.


Kennel kennel = new Kennel(catArray);

13. Assign a new Cat object to the eleventh offset into the array:
kennel[10] = new Cat("Jefferson", 3);

14. Display the array.


kennel.Displaycats();

9-48

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing an Indexer
15. Get the fourth member and display it.
Cat prettyKitty = kennel[3]; Console.WriteLine("PrettyKitty: {0}", prettyKitty.Name);

16. Compile and run the program, as shown in Figure 18.

Figure 18. Final exercise results.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

9-49

Lab 9: Arrays

9-50

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Collection Interfaces

Collection Interfaces
Objectives
Understand the role and uses of the various collection interfaces. Implement IEnumerable to enable custom classes to support the foreach loops enumeration. Use ArrayList to provide dynamically sized arrays. Implement IComparer to enable sorting of user-defined classes. Create a custom IComparer instance to enable dynamic sorting of user-defined types.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-1

Collection Interfaces

Standard Collection Interfaces


The .NET Framework defines a number of standard interfaces to help you build collection classes that provide the core functionality of the .NET collection classes. If your collection classes implement these interfaces, they will look and feel like fully functional collections to your clients. The standard interfaces define what your classes must do to support comparing, enumerating, and creating collections. This chapter examines a couple of these interfaces in some detail.

IEnumerable
You must implement the IEnumerable interface if you want your application to support foreach loops. When the compiler sees a foreach loop it invokes the method described in the IEnumerable interface. The IEnumerable interface requires only one method: GetEnumerator. The job of GetEnumerator is to return an object of a class that implements IEnumerator. This allows your class to return an IEnumerator specialized to enumerate over your collection.

Implementing IEnumerable
Lets consider an example. Assume you have a class Employee that represents an Employee of a firm, and you have a class Office that represents an office location. You would like to have your Office class function as a collection, and you might provide it with an indexer as shown in a previous chapter. Because your Office class acts as a collection, youd like to allow clients of your class to enumerate over the Office, retrieving each Employee object in turn. To do so, you will designate your Office class to implement IEnumerable.
public class Office : IEnumerable

When your code says that your Office class implements IEnumerable, you must provide a GetEnumerator method:
public IEnumerator GetEnumerator() { return (IEnumerator) new OfficeEnumerator(this); }

10-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Standard Collection Interfaces


In this case, your GetEnumerator method returns an OfficeEnumerator object; that is an object that specializes the implementation of IEnumerator for Office objects. OfficeEnumerator must implement IEnumerator, providing the required methods and properties, which are listed in Table 1.
Method or Property MoveNext() Reset() Current Descriptions Advance the enumerator Set the enumerator to initial position Get the current element

Table 1. The methods and properties of IEnumerator.

Implementing IEnumerator
The specialized IEnumerator may be implemented by the Office class itself, or by a separate class, e.g., OfficeEnumerator. This allows Office to delegate responsibility for enumeration to OfficeEnumerator, which better encapsulates the details of enumerating over the collection. In the common design pattern, OfficeEnumerator is implemented as a nested class; that is, as a class defined within the definition of Office. Nesting the private implementing class hides it from all external classes. Since OfficeEnumerator is of interest only to Office, nesting OfficeEnumerator within Office simplifies your larger program. OfficeEnumerator must know which Office it is enumerating. You will pass in a reference to the current Office object as a parameter to the OfficeEnumerator constructor. The relationship between Office and the OfficeEnumerator class is shown in Figure 1. OfficeEnumerator derives from IEnumerator, and so implements the methods MoveNext and Reset as well as the property Current. It also has two private member fields: Officea reference to the current Office object, and indexused for tracking the current Employee during enumeration.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-3

Collection Interfaces

Figure 1. The Office and OfficeEnumerator classes.

The outer Office class has an array of Employees (though it could just as easily store its Employees in another collection or in a database. It also has a method GetEnumerator that returns an instance of the nested OfficeEnumerator class.

Try It Out!
See enumerator.sln The relationship between the outer Office class and the nested OfficeEnumerator class is best understood from an example. 1. Create a new console application named Enumerator. 2. Create a simple Employee class. Provide two fields, name and empID, and initialize them in the constructor. Provide public properties for these fields.
public class Employee { string name; string empID; // employees name // employees ID

// constructor public Employee(string name, string ID) { this.name = name;

10-4

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Standard Collection Interfaces


empID = ID; }

// public properties public string EmpID { get {return empID; } }

public string Name { get { return name; } } }

3. Add the Office class, and have it implement IEnumerable. Provide two member fields: employees, an array of Employee objects, and ctr, which keeps track of the number of Employees in the array.
public class Office : IEnumerable { private Employee[] employees; private int ctr = 0;

4. Add a nested class OfficeEnumerator that implements IEnumerator. Give that class two fields: office, a reference to the Office instance and index, to keep track of which Employee is current.
public class Office : IEnumerable { private Employee[] employees; private int ctr = 0;

private class OfficeEnumerator : IEnumerator { private Office office; private int index = -1;

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-5

Collection Interfaces
Notice that the definition of OfficeEnumerator is nested within the definition of Office. Note also that index is initialized to -1, to designate that no record is current. 5. Add a constructor for OfficeEnumerator. Pass in the current instance of Office and initialize the member field.
public OfficeEnumerator (Office office) { this.office = office; }

The constructor is public within the private nested class and is private to other classes. 6. Implement the first of the two required methods, MoveNext. Increment the index to point to the next object. Test whether index is greater than or equal to the length of the array held in Office. If so, the index is past the end of the array and the code must return false, otherwise return true.
public bool MoveNext() { index++; if (index >= office.employees.Length) return false; else return true; }

Notice that OfficeEnumerator has intimate knowledge of the private members of Office. This is a privilege of a nested class, and the semantics are that the OfficeEnumerator knows the details of Office, but Office need not know the details of OfficeEnumerator. 7. Implement the second required method: Reset. To implement this method, just set index back to its initial value of -1.

10-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Standard Collection Interfaces


public void Reset() { index = -1; }

8. Add the required property: Current. The Current property returns the current object. In this case, it uses the index field as an offset into Office, allowing Offices index operator to return the correct Employee object:
public object Current { get { return (office[index]); } }

9. Close off the definition of the nested class and implement the methods of Office. Start with the required GetEnumerator method. Note that GetEnumerator must return an object of type IEnumerator. The code returns an OfficeEnumerator.
public IEnumerator GetEnumerator() { return new OfficeEnumerator(this); }

Notice that there is no need to explicitly cast the OfficeEnumerator to an IEnumerator. Because OfficeEnumerator implements IEnumerator, the cast is implicit. The value returned will be an object of type IEnumerator, because that is declared in the definition of GetEnumerator. 10. Implement the Office constructor.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-7

Collection Interfaces
public Office(params Employee[] employeeList){ this.employees = new Employee[256];

foreach (Employee e in employeeList) { this.employees[ctr++] = e; } }

11. Implement the Office indexer.


public Employee this[int index] { get { if (index < 0 || index >= employees.Length) { // handle bad index } return employees[index]; } set { if (index >= employees.Length) { // handle herror } else { employees[index] = value; if (ctr < index) ctr = index; } } }

10-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Standard Collection Interfaces


12. You are ready to test the office. Create an instance of the office, and populate it with a series of Employee objects.
Employee jg = new Employee("John Galt","001"); Employee fb = new Employee("Figaro Barbara", "002"); Employee ww = new Employee("Wotan Warrior", "003"); Employee gw = new Employee("George Washington", "004"); Employee ja = new Employee("John Adams", "005"); Office office = new Office(jg, fb, ww, gw, ja);

The Office constructor uses the params keyword to allow you to add as many Employee objects as youd like. 13. Add a couple more Employees to the office, using the indexer.
office[10] = new Employee("Thomas Jefferson", "007"); office[20] = new Employee("Sam Adams", "008");

14. Use a foreach loop to iterate over the office.


foreach (Employee e in office) { if (e != null) { Console.WriteLine("{0} Employee ID: {1}", e.Name, e.EmpID); } } // end if // end for each

Notice that you must test each member of the array to ensure it is not null. The design of the office is that space is allocated for 256 employees, but those spaces are initialized to null. 15. To prove that the foreach loop requires the implementation of IEnumerator, put a breakpoint on line 135 at the start of the foreach loop, as shown in Figure 2. Run to the breakpoint.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-9

Collection Interfaces

Figure 2. A breakpoint at the foreach loop.

16. Step into the foreach with F8. Immediately move into MoveNext, as shown in Figure 3. Notice that the index is set to -1, its initial value. It is about to be incremented to 0 to point to the first element in the collection.

10-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Standard Collection Interfaces

Figure 3. Move Next called by foreach.

17. Continue stepping; you return to the foreach header, and then, where in the header it says Employee it is time to retrieve the Employee object. Step into the Current property of the OfficeEnumerator, where the current Employee object is retrieved from Office, using the current value of index (0), as shown in Figure 4.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-11

Collection Interfaces

Figure 4. Retrieving the current Employee.

18. Continue stepping through the program; youll see that you step into the indexer to retrieve the current Employee object. 19. As you continue stepping through the program you will enter MoveNext and then Current, for each iteration of the foreach loop.

ArrayList
For the next interface examples youll want to work with an ArrayList. ArrayLists exist to overcome a significant limitation in normal arrays: arrays are of a fixed size. When you declare an array you must tell the compiler how many elements it will have. This is a problem, as youve seen with office. If you underestimate the number of elements youll need, you may run out while the program is running with no easy way to add more elements to the array. If you allow more than you need, then you waste a great deal of memory.

10-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Standard Collection Interfaces


The ArrayList is much like an Array, except that it grows as you need it to, dynamically, while the program is running. You add to an arrayList with the Add method. The most important ArrayList methods are shown in Table 2.
Method/ Property Count Add() Clear() Reverse() Sort() Table 2. ArrayList methods. Description Get number of elements Add an object Remove all objects Reverse order of elements Sort the elements

IComparable
Notice that the ArrayList, like many .NET collections, offers a Sort() method. To sort the ArrayList, the objects in the array must implement IComparable. IComparable has only one method: CompareTo(). For Employees to be sortable within an ArrayList or other collection, the code must implement IComparable. To do so, it must implement the CompareTo method, which compares the current employee with a second employee and returns a value indicating which is greater and which is lesser. How you define greater and lesser is entirely up to the designer of the Employee class. You might do so by comparing their employee ID, their years of service, their rank in the corporation; or you might sort them alphabetically by last name. All of the intrinsic types support IComparable, and offer a CompareTo method. Strings return values for alphabetical sorts, ints, and other numeric values return values for a value sort.

Try It Out!
See comparable.sln To see how to implement IComparable, youll revise the Employee class from the previous example to support sorting. 1. Reopen the previous example. 2. Mark Employee to implement Icomparable.
public class Employee : IComparable

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-13

Collection Interfaces
3. Implement the required method CompareTo. This method takes an object as a parameter. In this case, the object will be another Employee object. One convention is to name the parameter rhs, as it is on the right-hand side of the comparison operator. Within CompareTo, create a local instance of Employee, and assign to it the object passed as a parameter, cast to an Employee. Call the CompareTo method on the name field, passing in the name field of the rhs parameter:
public int CompareTo(Object rhs) { Employee right = (Employee) rhs; return this.name.CompareTo(right.name); }

The net effect is that the value returned by CompareTo is the value returned by calling CompareTo on the name fields. That is, the Employee objects are sorted alphabetically. 4. Modify the Office to use an ArrayList rather than an array. You no longer need the ctr field.
public class Office : IEnumerable { private ArrayList employees;

5. Modify OfficeEnumerator.MoveNext to use the Count field of the ArrayList.


public bool MoveNext() { index++; // ** change to count if (index >= office.employees.Count) return false; else return true; }

6. Modify the Office constructor to initialize and use the array list. 10-14 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Standard Collection Interfaces

public Office(params Employee[] employeeList) { // *** change this.employees = new ArrayList();

foreach (Employee e in employeeList) { // *** change this.employees.Add(e); } }

7. Add a new Add method to the office. This method takes an Employee object and adds it to the Array list.
public void Add(Employee e) { employees.Add(e); }

8. Add a Sort method to the Office and delegate the sort to the ArrayList.
public void Sort() { employees.Sort(); }

9. Modify the indexer. Notice that you must now cast the result of indexing into the ArrayList, because the ArrayList indexer returns an object, not an Employee. Since you know that only Employee objects have been stored in the ArrayList, the cast is safe. Notice also that ctr is now gone, and you must use employees.Count.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-15

Collection Interfaces
public Employee this[int index] { get { // *** change *** if (index < 0 || index >= employees.Count) { // handle bad index } // *** CAST *** return (Employee)employees[index]; } set { // ** change to count if (index >= employees.Count) { // handle herror } else { employees[index] = value; // ** change no ctr *** } } }

10. Modify the test code. Add a call to office.Sort and then redisplay the contents of the ArrayList.
public void Run() { Employee jg = new Employee("John Galt","001"); Employee fb = new Employee("Figaro Barbara", "002"); Employee ww = new Employee("Wotan Warrior", "003"); Employee gw = new Employee("George Washington", "004"); Employee ja = new Employee("John Adams", "005"); Office office = new Office(jg, fb, ww, gw, ja); office.Add(new Employee("Thomas Jefferson", "007"));

10-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Standard Collection Interfaces


office.Add(new Employee("Sam Adams", "008"));

foreach (Employee e in office) { Console.WriteLine("{0}",e.ToString()); }

// *** Sort the array and display the results *** office.Sort(); Console.WriteLine ("\n\nAfter sort: \n"); foreach (Employee e in office) { Console.WriteLine("{0}",e.ToString()); } }

The results of these changes are shown in Figure 5. Note that the names are sorted alphabetically by first name.

Figure 5. Sorting Employees by name.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-17

Collection Interfaces

Custom IComparer
The Sort method uses the default IComparer. You may choose to implement your own IComparer. Doing so allows you to customize the comparison. For example, right now Employees is sorted by name. It may be that under some conditions, to be determined at run time, you will want to sort by ID rather than by name. You can choose which sort to use by creating a custom IComparer. To create a custom IComparer you will create an EmployeeComparer class that will implement IComparer. EmployeeComparer will be a private nested class within Employee, as illustrated in Figure 6. The EmployeeComparer will be responsible for using the correct comparison.

Figure 6. The EmployeeComparer class nested within Employee.

Within EmployeeComparer you will declare an enumeration EmployeeComparison. If the client wishes to sort by name, the whichComparision field of EmployeeComparer will be set to Employee.EmployeeComparer.EmployeeComparison.name. In fact, the definition for the whichComparision field is:
private Employee.EmployeeComparer.EmployeeComparison whichComparison;

10-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Standard Collection Interfaces


Note that the type of the whichComparison field is Employee.EmployeeComparer.EmployeeComparison. That is, the EmployeeComparison enumeration is scoped within the EmployeeComparer class, which in turn is scoped within Employee. If you refer to this enumeration from a method of another class, you must use the full name.

Try It Out!
See Comparable2.sln To see how to write your own customized Icomparer, youll modify your previous example to customize Employee comparisons. 1. Reopen the previous project. 2. Create a new nested class within Employee, EmployeeComparer that implements IComparer.
public class Employee : IComparable { string name; string empID;

public class EmployeeComparer : IComparer {

3. Within EmployeeComparer, create an enumeration EmployeeComparison to hold constant values for the two types of comparisons youll support.
public enum EmployeeComparison { name, ID }

In this case you did not set values for name and ID, so their values will be 0 and 1, respectively. You dont care about the actual constant value, however, because youll be using these enumerations as flags to signal the type of comparison to be done, as youll see shortly. 4. Add a member variable to hold the current comparison type. The client will set this variable, and the comparison that is executed will be determined by the current value of this variable. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-19

Collection Interfaces
private Employee.EmployeeComparer.EmployeeComparison whichComparison;

5. Create a property to make this value accessible to clients. You want this to be read/write because your clients will need to set this value when they want to change the type of sort.
public Employee.EmployeeComparer.EmployeeComparison WhichComparison { get { return whichComparison; } set { whichComparison = value; } }

6. Implement the required Compare method. Compare must take two objects and compare them. You know these objects are Employee objects, so you can create local Employee references and cast the parameters accordingly.
public int Compare(object lhs, object rhs) { Employee left = (Employee) lhs; Employee right = (Employee) rhs; return left.CompareTo(right,WhichComparison); }

Notice that the work of comparison is delegated to the Employee class. You call the CompareTo method of Employee, passing in the second Employee object and the value retrieved by the WhichComparison property. 7. You must now overload the CompareTo method of Employee to support the call you just wrote within the nested class. The original CompareTo method looks like this:

10-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Standard Collection Interfaces


public int CompareTo(Object rhs) { Employee right = (Employee) rhs; return this.empID.CompareTo(right.name); }

You can leave this intact, though it wont be used in this exercise. Your new CompareTo method takes two parameters: an Employee object to compare to, and an enumerated constant indicating which comparison is requested:
public int CompareTo( Employee rhs, Employee.EmployeeComparer.EmployeeComparison whichComparison { switch (whichComparison) { case Employee.EmployeeComparer.EmployeeComparison.ID: return this.empID.CompareTo(rhs.empID); case Employee.EmployeeComparer.EmployeeComparison.name: return this.name.CompareTo(rhs.name); } return 0; } )

In this method you switch on the value of the whichComparison parameter. This parameter will match one of the two enumerated constants:
Employee.EmployeeComparer.EmployeeComparison.ID

or
Employee.EmployeeComparer.EmployeeComparison.name

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-21

Collection Interfaces
When the match is made, the responsibility for comparison is delegated to the appropriate field of Employee. If you are comparing by ID, the value is returned by calling CompareTo on the empID, passing in the second Employee objects empID field:
return this.empID.CompareTo(rhs.empID);

Similarly, if you are comparing by name, you return the result of calling CompareTo on the name field, passing in the name field of the other Employee:
return this.name.CompareTo(rhs.name);

8. Overload the Employee Sort method. The original Sort method delegated to the ArrayList.
public void Sort() { employees.Sort(); }

You can leave this intact and add a second Sort method that takes as a parameter an instance of the specialized EmployeeComparer object. This method will also delegate responsibility to the ArrayList, passing in the comparer. The ArrayList Sort() method is overloaded, and the version youll call expects an object of type IComparer.
public void Sort( Employee.EmployeeComparer comparer) { employees.Sort(comparer); }

9. Modify the Run() method in your test program. Start by creating the Office and iterating over the members.

10-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Standard Collection Interfaces


Employee jg = new Employee("John Galt","001"); Employee fb = new Employee("Figaro Barbara", "002"); Employee ww = new Employee("Wotan Warrior", "003"); Employee gw = new Employee("George Washington", "004"); Employee ja = new Employee("John Adams", "005"); Office office = new Office(jg, fb, ww, gw, ja); office.Add(new Employee("Thomas Jefferson", "007")); office.Add(new Employee("Sam Adams", "008"));

foreach (Employee e in office) { Console.WriteLine("{0}",e.ToString()); }

Add code to create an instance of the EmployeeComparer object.


Employee.EmployeeComparer comparer = Employee.GetComparer();

10. Set the value of the WhichComparison property within the EmployeeComparer instance to sort by name.
comparer.WhichComparison = Employee.EmployeeComparer.EmployeeComparison.name;

11. Call the Sort method of office, passing in the EmployeeComparer object and then iterate over Office.
office.Sort(comparer);

Console.WriteLine ("\n\nAfter sort by Name: \n"); foreach (Employee e in office) { Console.WriteLine("{0}",e.ToString()); }

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-23

Collection Interfaces
12. Change the value of the WhichComparison property to sort by ID and then iterate over Office again.
comparer.WhichComparison = Employee.EmployeeComparer.EmployeeComparison.ID; office.Sort(comparer);

Console.WriteLine ("\n\nAfter sort by ID: \n"); foreach (Employee e in office) { Console.WriteLine("{0}",e.ToString()); }

The results are shown in Figure 7. You can see that the first time through, the values display in the order they were added to Office. The second time through they are sorted by name, and the third time through they are sorted by ID.

10-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Standard Collection Interfaces

Figure 7. Using a custom IComparer.

Encapsulation
It is important to focus on which class has what responsibility. In the example you just wrote: The Employee class has responsibility for knowing about Employee information, such as Employee ID and name. The nested EmployeeComparer class has responsibility for knowing how Employees are sorted. As such it belongs to Employee, but relieves the Employee class of responsibility for the details of sorting. The EmployeeComparison enumeration is nested within EmployeeComparer, because it exists only to support the EmployeeComparer. The Office class is responsible for knowing about offices. In this example Office focuses only on the list of Employees, but an Office class might also know about addresses, and so forth. 10-25

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Collection Interfaces
The nested OfficeEnumerator class is responsible for enumerating over an office. As such it belongs to the Office, but relieves the Office class of responsibility for the details of enumeration. The Tester class is responsible for testing the Employee and Office classes and as such is protected from the details of how these classes work.

Each class is responsible for one discrete area of interest, and knows as little as possible about the internal state and mechanisms of any other class. This is the essence of encapsulation and data hiding, and it is this approach that helps you manage large complex programs.

10-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Standard Collection Interfaces

Summary
The .NET framework defines a number of standard collection interfaces. The IEnumerable interface supports enumeration and the use of the foreach loop. IEnumerable requires only one method: GetEnumerator. GetEnumerator returns an instance of Ienumerator. IEnumerator requires two methods: MoveNext and Reset and one property: Current. Typically an Enumerator is implemented as a nested class within the collection class. An ArrayList can act like a dynamically sizeable array. To support sorting you implement Icomparable. IComparable has only one method: CompareTo. You can implement a custom Icomparer. Typically custom IComparer objects are created as nested classes within the class youll compare. IComparer requires you implement Compare().

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-27

Collection Interfaces
(Review questions and answers on the following pages.)

10-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Standard Collection Interfaces

Questions
1. What is the interface you must implement to support foreach loops? 2. What are the methods and properties of the IEnumerator class? 3. What is the difference between an ArrayList and an Array? 4. What are the methods and properties of IComparable? 5. How do you refer to the ID enumerated constant within the EmployeeComparison enumeration defined within the EmployeeComparer class that is a nested class within Employee? 6. What are the advantages of nesting the EmployeeComparer within the Employee class?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-29

Collection Interfaces

Answers
1. What is the interface you must implement to support foreach loops?
The interface you must implement for your class to be iterated with a foreach loop is IEnumerable. The IEnumerable interface has only one method: GetEnumerator().

2. What are the methods and properties of the IEnumerator class?


The IEnumerator class has two methods, MoveNext and Reset as well as one property, Current.

3. What is the difference between an ArrayList and an Array?


The principal difference is that an Array is of a fixed size determined at compile time, while the ArrayList grows as needed.

4. What are the methods and properties of IComparable?


IComparable has only one method and no properties. The one method is CompareTo(), but that method may be overloaded.

5. How do you refer to the ID enumerated constant within the EmployeeComparison enumeration defined within the EmployeeComparer class that is a nested class within Employee?
You refer to the ID constant as Employee.EmployeeComparer.EmployeeComparison.ID.

6. What are the advantages of nesting the EmployeeComparer within the Employee class?
Nesting within the Employee class establishes that the EmployeeComparer is owned by the Employee and has special knowledge of the Employee class (it has access to the private members of Employee). It also scopes the EmployeeeComparer to the Employee class so that external methods must refer to it through the nesting class. This supports encapsulation.

10-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Standard Collection Interfaces

Lab 10: Collection Interfaces

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-31

Lab 10: Collection Interfaces

Lab 10 Overview
In this lab youll learn to implement the enumeration, comparison, and related standard interfaces for collections. To complete this lab, youll need to work through two exercises: Creating Enumerable Classes Sorting Objects by Implementing IComparable

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

10-32

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Enumerable Classes

Creating Enumerable Classes


Objective
In this exercise, youll implement a collection and the standard IEnumeration interface.

Things to Consider
Enumerable collections can be iterated with a foreach loop. You can specialize your enumerator to know about your specific collection. Typically the class implementing the enumeration is created as a nested class within the collection itself.

Step-by-Step Instructions
1. Open Enumeration Starter.sln in the Enumeration Starter folder. 2. Create a class named Cat that you will hold in your collection.
public class Cat {

3. Give the Cat class two member variables: name (a string) and age (an int).
string name; int age;

4. Implement the constructor.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-33

Lab 10: Collection Interfaces


public Cat(string name, int age) { this.name = name; this.age = age; }

5. Implement read-only properties for the age and name member variables.
public int Age { get { return age; } }

public string Name { get { return name; } }

6. End the Cat class and start a new class Kennel that implements Ienumerable.
public class Kennel : IEnumerable {

7. Declare a private member variable named cats, which is an array of Cat objects.
private Cat[] cats;

8. Create a nested class called KennelEnumerator to implement the IEnumerator interface. 10-34 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Enumerable Classes


private class KennelEnumerator : IEnumerator {

9. Give the nested class a reference to the outer class and an integer to represent an index into the array of Cat objects. Initialize the index to 1.
private Kennel kennel; private int index = -1;

10. Create the KennelEnumerator constructor. It takes one parameter: a reference to the Kennel class.
public KennelEnumerator (Kennel kennel) { this.kennel = kennel; }

11. Implement the MoveNext method to increment the index. If youve gone past the end, return false, otherwise return true.
public bool MoveNext() { index++; if (index >= kennel.cats.Length) return false; else return true; }

12. Implement the Reset method.


public void Reset() { index = -1; }

13. Implement the Current property. C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-35

Lab 10: Collection Interfaces


public object Current { get { return (kennel[index]); } }

14. End the definition of the nested KennelEnumerator class and continue on with the outer class.
} // end KennelEnumerator

15. Implement the GetEnumerator method, returning an IEnumerator.


public IEnumerator GetEnumerator() { return new KennelEnumerator(this); }

16. Implement the Kennel constructor to take a variable number of arguments.


public Kennel(params Cat[] catList) { this.cats = new Cat[256]; int ctr = 0;

foreach (Cat c in catList) { this.cats[ctr++] = c; } }

17. Implement the indexer to take an int and return the indexed Cat object.

10-36

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Enumerable Classes


public Cat this[int index] { get { if (index < 0 || index >= cats.Length) { // handle bad index } return cats[index]; } set { if (index >= cats.Length) { // handle error } else { cats[index] = value; } } }

18. Close the Kennel class.


} // Close Kennel class

19. Create an array of five Cat objects and populate the array.
Cat[] catArray = new Cat[5]; catArray[0] = new Cat("Frisky",5); catArray[1] = new Cat("Figaro", 2); catArray[2] = new Cat("Wotan", 3); catArray[3] = new Cat("Washington",4); catArray[4] = new Cat("Adam", 5);

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-37

Lab 10: Collection Interfaces


20. Create an instance of the collection class and pass the array to the constructor.
Kennel kennel = new Kennel(catArray);

21. Assign a new Cat to the eleventh member of the array.


kennel[10] = new Cat("Jefferson", 3);

22. Iterate over the Kennel, displaying each Cat in turn.


foreach (Cat c in kennel) { if (c != null) { Console.WriteLine("{0} Cat ID: {1}", c.Name, c.Age); } } // end if // end foreach

23. Compile and run the program as shown in Figure 8.

Figure 8. Final exercise results.

10-38

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Sorting Objects by Implementing IComparable

Sorting Objects by Implementing IComparable


Objective
In this exercise, youll see how to create sortable objects.

Things to Consider
For a class to be sortable you must implement Icomparable. Typically the class implementing IComparer is created as a nested class within the class being compared. You can tightly control the comparison by overloading the CompareTo method.

Step-by-Step Instructions
1. Create a Cat class that implements the IComparable interface. Give the Cat two members: name (a string) and age (an int).
public class Cat : IComparable { string name; int age;

2. Create a nested class CatComparer that implements the IComparer interface.


public class CatComparer : IComparer {

3. Within CatComparer create an enumeration, CatComparison, with two values: name and age.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-39

Lab 10: Collection Interfaces


public enum CatComparison { name, age }

4. Create a private enumerated constant of type CatComparison named whichComparison.


private Cat.CatComparer.CatComparison whichComparison;

5. Create a property for the comparison named WhichComparison.


public Cat.CatComparer.CatComparison WhichComparison { get { return whichComparison; } set { whichComparison = value; } }

6. Implement the Compare method. Compare two Cat objects and invoke the CompareTo method of the first one, passing in the second and the current value of whichComparison.
public int Compare(object lhs, object rhs) { Cat left = (Cat) lhs; Cat right = (Cat) rhs; return left.CompareTo(right,WhichComparison); }

7. End the nested class CatComparer.


} // End CatComparer

8. Within the outer class Cat, implement the GetComparer method. Return a CatComparer object.

10-40

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Sorting Objects by Implementing IComparable


public static CatComparer GetComparer() { return new Cat.CatComparer(); }

9. Implement the Cat constructor.


public Cat(string name, int age) { this.name = name; this.age = age; }

10. Override ToString to return the Cats name and age.


public override string ToString() { return name + " [Cat age: " + age + "]"; }

11. Implement the CompareTo method and compare the current Cat to the Cat passed in as a parameter. Compare them based on the name (alphabetic).
public int CompareTo(Object rhs) { Cat right = (Cat) rhs; return this.age.CompareTo(right.name); }

12. Overload the CompareTo method. Compare the current Cat to the Cat passed in as a parameter.
public int CompareTo( Cat rhs, Cat.CatComparer.CatComparison whichComparison ) {

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-41

Lab 10: Collection Interfaces


13. Base the comparison on the CatComparison constant passed in as a parameter.
switch (whichComparison) { case Cat.CatComparer.CatComparison.age: return this.age.CompareTo(rhs.age); case Cat.CatComparer.CatComparison.name: return this.name.CompareTo(rhs.name); } return 0;

14. Implement properties for Age and Name.


public int Age { get { return age; } }

public string Name { get { return name; } }

15. Implement the collection (Kennel) that implements IEnumerable.


public class Kennel : IEnumerable {

10-42

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Sorting Objects by Implementing IComparable


16. Provide a private ArrayList to hold the collection.
private ArrayList Cats;

17. Implement a nested class KennelEnumerator that implements IEnumerator.


private class KennelEnumerator : IEnumerator {

18. Provide a member variable that is a reference to the outer Kennel object and a second member that is an index, initialized to -1.
private Kennel kennel; private int index = -1;

19. Provide the constructor. It takes one parameter, which is a Kennel reference.
public KennelEnumerator (Kennel kennel) { this.kennel = kennel; }

20. Implement the MoveNext and Reset methods and the Current property.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-43

Lab 10: Collection Interfaces


public bool MoveNext() { index++; if (index >= kennel.Cats.Count) return false; else return true; }

public void Reset() { index = -1; }

public object Current { get { return (kennel[index]); } }

21. Implement GetEnumerator to return an IEnumerator object.


public IEnumerator GetEnumerator() { return (IEnumerator) new KennelEnumerator(this); }

22. Implement a constructor for Kennel that takes a variable number of parameters.
public Kennel(params Cat[] CatList) { this.Cats = new ArrayList();

10-44

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Sorting Objects by Implementing IComparable


foreach (Cat c in CatList) { this.Cats.Add(c); } }

23. Create an Add method that takes a Cat object and delegates adding the Cat object to the ArrayList.
public void Add(Cat c) { Cats.Add(c); }

24. Implement a Sort object that delegates sorting to the ArrayList.


public void Sort() { Cats.Sort(); }

25. Overload Sort to take an instance of CatComparer. Delegate sorting to the ArrayList, but pass in the CatComparer object.
public void Sort( Cat.CatComparer comparer) { Cats.Sort(comparer); }

26. Implement an integer based index for Cat.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-45

Lab 10: Collection Interfaces


public Cat this[int index] { get { if (index < 0 || index >= Cats.Count) { // handle bad index } return (Cat)Cats[index]; } set { if (index >= Cats.Count) { // handle error } else { Cats[index] = value; } } } // end else // end set // end indexer

27. Create an array of five Cat objects and populate the array.
Cat[] catArray = new Cat[5]; catArray[0] = new Cat("Frisky",6); catArray[1] = new Cat("Figaro", 2); catArray[2] = new Cat("Wotan", 3); catArray[3] = new Cat("Washington",4); catArray[4] = new Cat("Adam", 5);

28. Create an instance of the collection class and pass the array to the constructor.
Kennel kennel = new Kennel(catArray);

10-46

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Sorting Objects by Implementing IComparable


29. Assign a new Cat to the eleventh member of the array.
kennel[10] = new Cat("Jefferson", 3);

30. Iterate over the Kennel. Display each Cat in turn.


foreach (Cat c in kennel) { if (c != null) { Console.WriteLine("{0} Cat ID: {1}", c.Name, c.Age); } } // end if // end for each

31. Instantiate a CatComparer object.


Cat.CatComparer comparer = Cat.GetComparer();

32. Set the WhichComparison property of the CatComparer object to name and invoke Sort on Kennel, passing in the CatComparer object you created and set.
comparer.WhichComparison = Cat.CatComparer.CatComparison.name; kennel.Sort(comparer);

33. Iterate over the kennel, displaying the names and ages.
foreach (Cat c in kennel) { Console.WriteLine("{0} Cat ID: {1}", c.Name, c.Age); } // end foreach

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

10-47

Lab 10: Collection Interfaces


34. Set the WhichComparison property of the CatComparer object to age and invoke Sort on Kennel, passing in the CatComparer object.
comparer.WhichComparison = Cat.CatComparer.CatComparison.age; kennel.Sort(comparer);

35. Iterate over the kennel, displaying the names and ages.
foreach (Cat c in kennel) { Console.WriteLine("{0} Cat ID: {1}", c.Name, c.Age); }

36. Compile and run the program as shown in Figure 9.

Figure 9. Implementing Icomparable.

10-48

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Collections and Strings

Collections and Strings


Objectives
Create queues for first in, first out retrieval. Create stacks for last in, first out retrieval. Create dictionaries for matching keys with values. Create and manipulate strings. Compare strings and concatenate strings. Parse strings. Use regular expressions for advanced parsing.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-1

Collections and Strings

Collections
The .NET Framework provides a number of collection classes that are fully debugged and ready for use. The .NET collections hold objects, and are not type-safe. You are free to put any intrinsic or user-defined type into a collection; since all derive from object, the collection is prepared to hold them and treat them polymorphically. When you extract from the collection you will need to cast them back to their original type. In addition to Array and ArrayList, the principal .NET collections include: Queues Stacks Dictionary

Queues
A queue offers first in, first out semantics. That is, the first item you add to the queue is the first item extracted. The second item added is the second item to come out, and so forth. The classic analogy for a queue is a line of people at a movie theater (in the UK you dont line up, you queue up). You use queues when you are managing access to a limited resource. As requests arrive for your resource, you add them to the queue, and then you remove them in their order of arrival. Imagine that you have 20 operators sitting at consoles in a large office. Phone calls arrive (perhaps asking for customer support). The calls are routed to the operators, but if there are more calls than there are operators, someone must wait. You add the waiting calls to the queue and you inform your callers that Your call is important to us, and will be handled in the order received. Queues offer a number of methods and properties, the most important of which are shown in Table 1.

11-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Collections
Method/Property Count Clear() Dequeue() Enqueue() GetEnumerator() Peek() Description Get the number of elements in the queue. Remove all members of the queue. Remove and return an object from the queue. Add an object to the end of the queue. Return an enumerator for iterating over the queue. Return an object at the top of the queue without removing it.

Table 1. Methods and properties of queues.

The Count property tells you how many objects are in your queue. Notice that this is the same property you use with ArrayLists, and youll see that this is common to all collections. You can remove all the members of the queue by calling Clear(). Once again, this idiom is common to all collections. Another idiom common to many collections is the idea that you can either look at (get a reference to) the next item but leave it in the collection (Peek) or you can take the next item out of the collection, again getting a reference to it (Dequeue). There is also a method to add to the collection (Enqueue), and finally a method for iterating over the collection (GetEnumerator).

Try It Out!
See Queue.sln The best way to see how to work with a queue is to write a simple example program. 1. Create a new console application and name it Queue. 2. For this incredibly simple example you do not need any classes except a Tester class. As usual, give it a Run method. 3. Within Run(), create an instance of a Queue.
Queue myQueue = new Queue();

4. Add a string to the queue.


myQueue.Enqueue("Hello");

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-3

Collections and Strings


5. Now add five integer values.
for (int i = 1;i<6;i++) { myQueue.Enqueue(i*10); }

6. You are ready to review the contents of the queue. Write a small method to print all the values in the queue. Generalize this method to take any enumerable container; youll use it again in later examples.
public static void PrintValues( IEnumerable myCollection ) { foreach (object i in myCollection) { Console.Write("{0} ", i.ToString()); } Console.WriteLine(); }

The one parameter to the PrintValues method is an object of type IEnumerable. All of the .NET collections support this interface (which is why Queue has a method GetEnumerator). 7. Call PrintValues, passing in the queue.
Console.Write( "myQueue values:\t" ); PrintValues( myQueue );

The results are shown in Figure 1. You can see that the values are displayed in the order in which they were added to the queue.

Figure 1. Printing the values in myQueue.

11-4

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Collections
8. Remove an element from the queue and display it. Remember that the Dequeue() method both removes the element and returns it.
Console.WriteLine( "\n(Dequeue)\t{0}", myQueue.Dequeue() );

9. Display the queue again to show that the element was removed.
Console.Write( "myQueue values:\t" ); PrintValues( myQueue );

The results of running the code to this point are shown in Figure 2. You can see that Hello is removed from the queue and displayed. When the queue is redisplayed, the first element, Hello, is now missing.

Figure 2. Dequeing an item.

10. Remove the next element and display the queue again.
Console.WriteLine( "\n(Dequeue)\t{0}", myQueue.Dequeue() );

Console.Write( "myQueue values:\t" ); PrintValues( myQueue );

11. Finally, display the first element in what remains of the queue, but do not remove it. To accomplish this, use the Peek method. Like Dequeue, Peek returns the first element in the queue, but unlike Dequeue it does not remove it. You can prove this to yourself by displaying the queue before and after calling Peek.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-5

Collections and Strings


Console.WriteLine( "\n(Peek) \t{0}", myQueue.Peek() );

Console.Write( "myQueue values:\t" ); PrintValues( myQueue );

The result of running all of this code is shown in Figure 3. You can see that Peek displays the first element in the queue, but does not remove the element; the queue is unchanged by calling Peek.

Figure 3. Peeking at the next element in the queue.

Stacks
A stack offers last in, first out semantics. That is, the last item you add to the Stack will be the first item extracted. The penultimate item added will be the second item to come out, and so forth. The classic analogy for a stack is a stack of dishes at a buffet table. The last dish placed on top of the stack is the first dish to pop off the stack. The operating system uses a stack (referred to as the stack) to hold program instructions and parameters to methods, as well as local variables. As you work with the stack, items are pushed onto the stack. As the items are destroyed, they are popped off the stack. Stacks offer a number of methods and properties, the most important of which are shown in Table 2.

11-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Collections
Method/Property Count Clear() GetEnumerator() Peek() Pop() Push() Description Get the number of elements in the stack. Remove all objects from the stack. Return an enumerator for iterating over thesStack. Return an object at the top of the stack without removing it. Return and remove top element of the stack. Add an element to the stack.

Table 2. Methods and properties of stacks.

As with Queue, the Count property tells you how many objects are in your stack. As with Queue, you can call Clear() to remove all the elements of the stack. And once again you use the GetEnumerator() method to iterate over the stack. Peek works as it does with Queue; that is, you retrieve the top element in the stack but you do not remove it. The equivalent to Dequeue is Popretrieve and remove the top element. The equivalent to Enqueue is Push.

Try It Out!
See Stack.sln The best way to see how to work with a stack is to write another simple example program. 1. Create a new console application and name it Stack. 2. Once again youll use a very simple program, putting all the work in the Run() method of the Tester class. 3. Within Run(), create an instance of a stack.
Stack myStack = new Stack();

4. Populate the stack with seven integer values.


for (int i = 1;i<8;i++) { myStack.Push(i*10); }

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-7

Collections and Strings


5. Display the stack using the same PrintValues method you used with queues. Again, Stack implements IEnumerable, so this is safe to do.
Console.Write( "stack values:\t" ); PrintValues( myStack );

6. To demonstrate how Pop removes an element much as Dequeue did for Queue, call Pop, display the results, and then show the stack. Do this twice, removing two elements from the stack.
// Remove an element from the stack. Console.WriteLine( "\n(Pop)\t{0}", myStack.Pop() );

// Display the stack. Console.Write( "myStack values:\t" ); PrintValues( myStack );

// Remove another element from the stack. Console.WriteLine( "\n(Pop)\t{0}", myStack.Pop() );

// Display the stack. Console.Write( "myStack values:\t" ); PrintValues( myStack );

7. Call Peek() to look at the top element in the stack, but dont remove it. Print the stack again to show that it was unchanged by the call to Peek():

11-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Collections
a// Display the stack. Console.Write( "myStack values:\t" ); PrintValues( myStack );

// View the first element in the stack Console.WriteLine( "\n(Peek) myStack.Peek() ); \t{0}",

// Display the stack. Console.Write( "myStack

The result of this work is shown in Figure 4.

Figure 4. Peeking at the stack.

Stacks and Arrays


You can copy the entire contents of a stack into an array at an arbitrary index into the array. For example, you can copy the stack from the previous example into an array of integers. 1. To see this at work, first create an array of integers.
int[] targetArray; targetArray = new int[15];

Interestingly, you are free to use collection semantics with an array as well. That is, rather than using the declaration of the array, you can declare an C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-9

Collections and Strings


instance of the Array class by calling the static CreateInstance method of Array, passing in the type of Array to create and the size:
Array targetArray=Array.CreateInstance( typeof(int), 15 );

This line is identical in effect to the lines above; it declares targetArray to be an instance of an array of integers of size 15. 2. Populate the array with values. Again, you can do this in one of two ways, using traditional array semantics, or using methods of the Array class. To use traditional semantics write:
for (int i = 0; i < 15; i++) targetArray[i] = i * 100 + 100;

This assigns values to each of the first 15 elements in the array. The first element will be 100 (0 * 100 + 100), the second will be 200 (1 *100 + 100), and so forth. As an alternative, you can use the SetValue method of the Array class rather than the traditional array indexing:
for (int i = 0; i < 15; i++) targetArray.SetValue(i*100+100,i);

The two for loops accomplish exactly the same work. 3. Pass the array to the PrintValues method. Because Array supports IEnumerable, PrintValues will display the contents of the array.
Console.WriteLine( "\nTarget array: PrintValues( targetArray ); ");

4. Copy the stack to the target array, starting at index 6. This overwrites the array with the contents of the stack.
myStack.CopyTo( targetArray, 6 );

5. Display the results using PrintValues. 11-10 C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Collections
Console.WriteLine( "\nTarget array after copy: PrintValues( targetArray ); ");

The results are shown in Figure 5. You can see that the stack values are 50, 40, 30, 20, 10. The array values are 100 through 1500. After you copy the stack over the array at index 6, the seventh through eleventh members of the array have been overwritten with the values from the stack.

Figure 5. Copying a stack over an array.

6. You can also copy the entire stack to a new array by calling ToArray(). This returns a new array of objects that you can again pass to PrintValues.
// Copy the entire source Stack // to a new standard array. Object[] myArray = myStack.ToArray();

// Display the values of the new standard array. Console.WriteLine( "\nThe new PrintValues( myArray ); array:" );

The results are shown in Figure 6. This time a new array is created with the five elements of the stack.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-11

Collections and Strings

Figure 6. Creating a new array from the stack.

Dictionary
Perhaps the most useful collection in the .NET Framework is the Dictionary. A Dictionary is a collection that creates key/value pairs. A classic dictionary collection, of course, is Websters Unabridged Dictionary of the English Language, in which each word is a key, and the value returned by the key is the definition of the word. In .NET, the most common Dictionary collection is the HashTable. A HashTable is a highly efficient dictionary. When you think of a HashTable, think of a series of numbered buckets. Each entry in the Dictionary is stored in a numbered bucket based on a hashvalue returned by the key. The advantage of HashTables is that they are highly efficient. When programmers talk about efficiency, they mean either that the approach takes little memory or that it is very fast. In this case, a HashTable is very fast; objects can be retrieved from a HashTable based on their key, very quickly.

Object.GetHashCode
HashTables work by asking the objects they store for a hashcode. This hashcode allows quick retrieval of the object. Because GetHashCode is a method of object, every object (both intrinsic and user-defined) implements the method. You are free to override the method in your own classes, providing a hashcode by whatever mechanism you think best.

11-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Collections
When two or more objects return the same hashcode, it is known as a collision. In .NET HashTables, collisions are handled by having each bucket hold its own contents in an ordered list. Once the hashcode identifies a bucket, a binary search is conducted of the ordered list within that bucket. Binary searches are very fast.

HashTables as Dictionaries
HashTables are dictionaries because they implement the IDictionary interface. The IDictionary interface dictates that a HashTable must have a public indexer:
object this[object key] { get; set; }

The index value is an object, known as the key. What is returned is also an object. The IDictionary interface also dictates two additional properties: keys and values. The keys property returns a collection of all of the keys. The values property returns a collection of all the values. The IDictionaryEnumerator interface allows you to iterate over a dictionary as you might iterate over another collection (i.e., using foreach). HashTables offer a number of methods and properties, the most important of which are shown in Table 3.
Method/Property Add() Count Clear() Contains ContainsKey GetEnumerator() Item() Description Add an entry with a key/value pair. Get the number of elements in the HashTable. Remove all objects from the HashTable. Is the element in the HashTable? Is the key in the HashTable? Return an enumerator for iterating over the HashTable. Indexer for the HashTable.

Table 3. Methods and properties of HashTables.

The Add method is used to add items to the HashTable, just as Add is used by ArrayList. As with Queue and Stack, the Count property tells you how many objects are in your HashTable. Once again, you can call Clear() to remove all the elements of the HashTable. And, as with the other collections, the GetEnumerator() C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-13

Collections and Strings


method iterates over the HashTable; however this time what is returned is an item of type IDictionaryEnumerator. Item is the indexer for HashTable; this is the underlying method for indexing supported by other programming languages that do not allow you to overload operators.

Try It Out!
See HashTable.sln To see how to work with HashTables, youll create a new console application. 1. Create a new console application named HashTable. 2. Create a HashTable object using the keyword new.
Hashtable hashTable = new Hashtable();

3. Populate the HashTable by calling the Add method. The arguments to Add are the key and the value. In this case youll be saving names (as the value) keyed to the persons social security number. NOTE To avoid using a valid social security number, weve used intentionally invalid social security numbers here.

For each value you add youll provide two strings: the key and the value itself.
hashTable.Add("000-12-3456","Ayn Rand"); hashTable.Add("000-13-3938","John Galt"); hashTable.Add("000-14-3837","Hank Reardon"); hashTable.Add("000-15-4837","John Adams"); hashTable.Add("000-16-4738","Sam Adams");

4. You can retrieve any value just by providing the key to the HashTable.
Console.WriteLine("hashTable[\"000-14-3837\"]: {0}", hashTable["000-14-3837"]);

This line of code prints the string hashTable[000-14-3837] followed by whatever value is returned by passing the string 000-14-3837 to the HashTable. 11-14 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Collections
NOTE Notice that you must escape the double quotes in the WriteLine statement (\) so that the quote marks will actually be printed.

The results are shown in Figure 7. You can see that the value (Hank Reardon) has been retrieved using the key 000-14-3837.

Figure 7. Retrieving a value from the HashTable.

5. Retrieve the collection of keys by calling hashTable.Keys. What is returned is a collection: an object implementing the ICollection interface. You may instantiate an object of type ICollection and pass that to a foreach loop (all ICollection objects support IEnumerator).
Console.WriteLine("\nKeys:"); ICollection keys = hashTable.Keys; foreach (string key in keys) Console.WriteLine("{0}", key);

The results are shown in Figure 8. The entire collection of keys is displayed.

Figure 8. Iterating over the Keys collection.

6. Iterate over the Values collection just as youve iterated over the Keys collection.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-15

Collections and Strings


Console.WriteLine("\nValues:"); ICollection values = hashTable.Values; foreach (string value in values) Console.WriteLine("{0}", value);

Again, you are passing the ICollection object to the foreach loop, displaying all the values as shown in Figure 9. The Keys and Values collections give you access to the entire set of data within the HashTable.

Figure 9. Iterating over the Values collection.

7. You are free to extract the iterator yourself, using GetEnumerator. You can then use that iterator to iterate over the two collections, explicitly extracting the records by calling the Key and Value properties of the iterator.

11-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Collections
Console.WriteLine( "\nUsing the Dictionary enumerator...");

IDictionaryEnumerator enumerator = hashTable.GetEnumerator();

while(enumerator.MoveNext()) Console.WriteLine("\t{0}:\t{1}", enumerator.Key, enumerator.Value);

The reason that IEnumerator was extended to IDictionaryEnumerator was explicitly to add the Key and Value properties. The result of iterating explicitly is shown in Figure 10. By using the dictionary enumerator you are able to iterate over all the records, displaying the relationship between keys and values.

Figure 10. Using the IDictionaryEnumerator.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-17

Collections and Strings

Strings
C# inherits many of its characteristics from the C family of languages (C and C++). One striking limitation in these languages is the limited support they provide for strings. A string is a set of characters, typically used to hold names, identifiers, and other text for display. C# corrects this limitation, making strings first-class members of the language. In C#, strings are immutable.

Key Term
Immutable Immutable objects cannot be changed. When you appear to modify a string what you get back is not a changed string, but a new instance of string!

When you modify a string you actually create a new one. The original string remains unchanged. In most cases this has little impact, except that repeatedly modifying strings can be somewhat inefficient (lots of copies must be made) and has the potential to slow down your program. You can solve this by using the StringBuilder class (discussed later). Strings are also sealed.

Key Term
Sealed Classes that are sealed may not be derived from.

It is not legal to derive a class from String. There are no sub-classes of String. The formal definition of string is:
public sealed class String : IComparable, ICloneable, IConvertible

You can see that the string class supports three interfaces. IComparable allows strings to be sorted, as discussed in a previous chapter. ICloneable allows strings to be duplicated. IConvertible provides for easy type conversion between strings and other types.

11-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Strings
It turns out that the C# string (lowercase s) class is just a synonym for the .NET Framework String class (uppercase s). You are free to use either designation; there is no functional difference. Most C# programmers will use the (lowercase) string class, recognizing that it maps directly to the .NET String class.

String Manipulation
The .NET String class provides extensive support for manipulating strings, concatenating strings, and comparing strings. Because String is a class, these are implemented as class methods (either static or instance) of the String class.

Try It Out!
See String.sln By far the best way to learn how to manipulate strings is by writing a small test program. 1. Create a new console application named Strings. 2. Create two strings, as shown below.
string s1 = "hello"; string s2 = "HELLO";

3. Notice that the two strings are identical except for case. You can compare two strings by matching case (in which case these strings will not evaluate as equal to one another) or you can evaluate them by ignoring case (in which case these two strings are equal). The code calls the static method compare, and gets back an integer value that indicates which string is greater or lesser. The value 0 indicates they are the same.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-19

Collections and Strings


int result;

// compare two strings, case sensitive result = string.Compare(s1, s2); Console.WriteLine("Compare s1: {0}, s2: {1}, result: {2}", s1, s2, result);

//overloaded version (true = ignore case

result = string.Compare(s1,s2, true); Console.WriteLine( "Compare insensitive s1: {0}, s2: {1}, result: {2}\n", s1, s2, result);

The result of running this code is shown in Figure 11. The first comparison is case sensitive, and the lowercase string is evaluated as being smaller than the second string (as indicated by the return value 1). The second comparison is case insensitive, and the return value of 0 indicates that the strings are equal. Table 4 shows how to interpret the result of a comparison operation.

Figure 11. Comparing two strings.

Value Any value less than 0 Zero Any value greater than 0

Interpretation The first string is less than the second. The strings are equal. The first string is greater than the second.

Table 4. Interpreting the results of a string comparison.

11-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Strings

Concatenation
You can concatenate two strings either by using the static method Concat or by using the overloaded + operator. 1. Concatenate the two strings s1 and s2, using the Concat operator, and display the results.
// concatenation method string s3 = string.Concat(s1,s2); Console.WriteLine( "s3 concatenated from s1 and s2: {0}", s3);

2. Concatenate the same two strings using the overloaded + operator.


// use the overloaded operator string s4 = s1 + s2; Console.WriteLine( "s4 = s1 + s2: {0}", s4);

The result of running this code is shown in Figure 12. There is no difference in the result whether you use the static method or the overloaded + operator.

Figure 12. Concatenating strings.

Copying Strings
Just as there are two ways to concatenate strings, there are two ways to compare strings. You may use the Copy instance method, or you may choose to use the overloaded assignment operator (=). 1. Copy string s4 to a new string s5 with the Copy method.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-21

Collections and Strings


// the string copy method string s5 = string.Copy(s4); Console.WriteLine( "\ns5 = string.Copy(s4): {0}", s5);

NOTE

The special escape sequence \n at the beginning of the string passed to WriteLine adds an extra new line (white space) to make the output easier to read.

2. Copy the resulting string s5 to a new string s6, using the overloaded assignment operator.
// use the overloaded operator string s6 = s5; Console.WriteLine("s6 = s5: {0}", s6);

The output from running this code is shown in Figure 13. The result is identical whether you use the Copy method or the assignment operator.

Figure 13. Using Copy or the assignment operator to compare strings.

String Equality
There are three ways to determine whether two strings are identical. All three return a Boolean value of true if the two strings are equal. The first uses the static Equals method, the second uses an instance equals method, and the third uses the overloaded equality operator (==). 1. Test whether string s6 is equal to string s5 by using the static equals method. 11-22 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Strings
// static equals method Console.WriteLine( "\nTest static: Equals(s6,s5)?: {0}", string.Equals(s6,s5));

2. Test again using the instance method.


// instance equals method Console.WriteLine( "Test instance: s6.Equals(s5)?: {0}", s6.Equals(s5));

3. Test one more time using the overloaded operator.


// overloaded operator Console.WriteLine( "Test operator: s6==s5?: {0}", s6 == s5);

The results are shown in Figure 14.

Figure 14. Using three methods to test equality.

String Properties
Strings provide a number of properties. Among the more useful of the string properties are the length property and the indexer. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-23

Collections and Strings


1. Display the length of a string.
Console.WriteLine( "\nString s6 is {0} characters long. ", s6.Length);

2. Find the character at offset 4 (the fifth character).


Console.WriteLine( "The 5th character is {0}\n", s6[4]);

The results are shown in Figure 15. The length property returns the length of the entire string. The index property returns the character at the specified index.

Figure 15. Testing the length and using an indexer.

3. Create a for loop to display all the characters in turn.


for (int i = 0; i < s6.Length; i ++) Console.WriteLine("String[" + i + "]: {0}", s6[i]);

The result is shown in Figure 16. You iterate through the string, using the index operator to pick out each of the characters in turn.

11-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Strings

Figure 16. Iterating through the characters in a string.

Verbatim Strings
C# supports the concept of a verbatim string. This is a string in which all the characters are accepted without interpretation by the compiler. If a quote symbol or other special character is within the string it is accepted as is. This can greatly simplify working with complicated strings. 1. Create a verbatim string.
string s7 = @"Liberty Associates, Inc. provides custom .NET development, and Consulting";

2. Display the verbatim string to the console.


Console.WriteLine("Verbatim string: {0}", s7);

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-25

Collections and Strings


The results are shown in Figure 17. The verbatim string is displayed exactly as it was entered; even the new implicit newline characters were preserved.

Figure 17. The compiler accepts verbatim strings without interpretation.

Finding Substrings
C# provides a number of methods for finding substrings within strings. For example, you can test whether a string ends with a sequence of characters, using the EndsWith method. You can also obtain the index of the first instance of a substring within another string, using the IndexOf method. 1. Add code to test whether the string s7 ends with the string Consulting.
Console.WriteLine("s7:{0}\nEnds with Consulting?: {1}\n", s7, s7.EndsWith("Consulting") );

11-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Strings
2. Find the index of the first instance of the substring within the outer string.
// return the index of the substring Console.WriteLine( "\nThe first occurrence of Consulting "); Console.WriteLine ("in s3 is {0}\n", s3.IndexOf("Consulting"));

The result of running this code and scrolling to just the new output is shown in Figure 18. First test whether the verbatim string (s7) ends with the characters Consulting. This returns a Boolean value, in this case True. Then return the offset into the string where Consulting appears (99).

Figure 18. Finding the first occurrence of a substring.

Inserting New Strings


You can use that offset to insert a new substring into the outer string, using the Insert method of string. 1. Start by instantiating an integer variable to hold the offset of Consulting within the string s7.
int indexOfConsulting = s7.IndexOf("Consulting");

2. Use that index to insert the new value, the string excellent into string s7 at the offset held by indexofConsulting.
string s8 = s7.Insert(indexOfConsulting,"excellent ");

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-27

Collections and Strings


3. Display the result to the console.
Console.WriteLine("s8: {0}\n",s8);

The result is shown in Figure 19. You have inserted the substring excellent into string s7 at the offset of the string Consulting. The rest of the outer string (the word Consulting) is pushed right to make room for the new substring.

Figure 19. Inserting a substring into a string.

Note that you can combine the three steps of Getting the index Using the index in the Insert statement Displaying the results

all into a single C# statement:


Console.WriteLine(s7.Insert(s7.IndexOf("Consulting"), "excellent "));

You have to examine this statement pretty carefully to be certain what is going on. The best way to read a statement like this is inside out. The innermost statement is:
s7.IndexOf("Consulting")

This returns the index of the substring Consulting (99), and this value is passed to the outer statement:

11-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Strings
s7.Insert(99, excellent)

This results in a returned string, and that string is passed to the outermost Console.WriteLine statement.

Splitting Strings
Often you will have a string that consists of tokens that you want to parse. For example, you may want to parse an English sentence into words, or you may want to pick out an IP address from a log entry. The C# string class provides some support for splitting a string into its constituent parts. The Split method takes an array of delimiters and returns an array of the substrings. If you call Split on a string with six words separated by spaces and you pass in an array that contains a space character, youll get back an array of six strings, one for each of the words in the original string. 1. Create a string filled with words to parse and display it.
string s9 = "One Two Three Four Five Liberty Associates"; Console.WriteLine("\n\nString s9: {0}", s9);

2. Create two constants for the delimited characters and store the constants in an array.
// constants for the space and comma characters const char Space = ' '; const char Comma = ',';

// array of delimiters to split the sentence with char[] delimiters = new char[] { Space, Comma };

3. Create a string to assemble the results and a local counter variable to keep track of how many words were found.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-29

Collections and Strings


string output = ""; int ctr = 1;

4. Split the string s9 with the delimiter array. The result is an array of the substrings; iterate over that array with a foreach loop.
foreach (string subString in s9.Split(delimiters)) {

5. Within the foreach loop, assemble the output string with the counter (indicating the number of words found), followed by the substring, and then a new line.
foreach (string subString in s9.Split(delimiters)) { output += ctr++; output += ": "; output += subString; output += "\n"; }

6. Display the results, as shown in Figure 20. Each of the seven words is individually separated from the original string.

Figure 20. Parsing the words in a sentence.

11-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Strings

The StringBuilder Class


Strings are immutable. When you append to a string, you actually create a new string:
output += ctr++; output += ": "; output += subString; output += "\n";

The result was not that the string output was incremented, but rather a new string object was created as the result of each statement. This is terribly inefficient. You can rewrite the previous block of code using a StringBuilder. A StringBuilder is a class designed to encapsulate a strings constructor. StringBuilders are used with Strings to accomplish highly efficient modification of strings. 1. Add a new string to parse, and reset the ctr variable.
string s10 = "One Two Three Four Five Liberty Associates"; Console.WriteLine("\n\nString s10: {0}", s10); ctr = 1;

2. Create a new output string; this time use StringBuilder.


StringBuilder sbOut = new StringBuilder();

3. Recreate the foreach loop, this time using the StringBuilder object.
foreach (string subString in s10.Split(delimiters)) { sbOut.Append(ctr++); sbOut.Append(": "); sbOut.Append(subString); sbOut.Append("\n"); }

4. Display the string youve built in the StringBuilder by calling ToString. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-31

Collections and Strings


Console.WriteLine(sbOut.ToString());

The results are identical, as shown in Figure 21. The StringBuilder is somewhat more cumbersome to use, but the code runs faster. You wont notice the difference in this tiny program, but when you make many modifications in a tight loop, using the StringBuilder can improve the performance of your application and use less memory.

Figure 21. Using the StringBuilder.

You can modify the way you assemble the output string by using the AppendFormat instance method of StringBuilder. So, rather than writing
foreach (string subString in s10.Split(delimiters)) { sbOut.Append(ctr++); sbOut.Append(": "); sbOut.Append(subString); sbOut.Append("\n"); }

you can accomplish the same output by writing: 11-32 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Strings
foreach (string subString in s10.Split(delimiters)) { sbOut.AppendFormat( "{0}: {1}\n", ctr++, subString); }

Notice that you use the same formatting options in AppendFormat as you would with Console.WriteLine. The output is identical.

Regular Expressions
You didnt know it, but you cheated a bit in the previous example, by lopping off the end of the name of the company, Liberty Associates, Inc. If you try splitting on the entire name of the company, the results are slightly different. 1. Add a new string with the full name of the company. Display it and reset the ctr variable.
string s11 = "One Two Three Four Five Liberty Associates, Inc."; Console.WriteLine("\n\nString s11: {0}", s11); ctr = 1;

2. Point the variable sbOut to a new StringBuilder and parse the new string. Display the results.
sbOut = new StringBuilder();

// split the string and then iterate over the // resulting array of strings foreach (string subString in s11.Split(delimiters)) { sbOut.AppendFormat( "{0}: {1}\n", ctr++, subString); } Console.WriteLine(sbOut.ToString());

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-33

Collections and Strings


The results are shown in Figure 22. This time the parse did not go quite as expected.

Figure 22. Splitting the full name of the company.

This almost worked. The first seven words were parsed appropriately, but why is word 8 listed as blank? The answer is that the comma after Liberty Associates was seen as a delimiter, as was the space after the comma! You need to program the Split method to match either a space or a comma. Unfortunately, there is no way to convey that with a normal string. For that kind of advanced string manipulation you need a Regular Expression.

Regular Expressions Defined


Regular Expressions are a language for describing and manipulating text. You apply a Regular Expression to a string, and you get back a new string. Often that new string is a substring of the original or a modification of the original.

11-34

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Strings

Regex: Implementing Regular Expressions


In C#, Regular Expressions are encapsulated in the Regex class. This class implements a more powerful form of Split that returns an array of substrings based on a Regular Expression. In addition, you can extract the Match collection, which contains all the matches to the Regular Expression.

Try It Out!
See Regular Expressions.sln To see how this works, youll rewrite the previous example using a Regular Expression. 1. Create a new console application named RegularExpressions. 2. In the Run method, add a definition of a string to parse.
string s1 = "One Two Three Four Five Liberty Associates, Inc.";

3. Recreate the delimiters from the previous example.


// constants for the space and comma characters const char Space = ' '; const char Comma = ',';

// array of delimiters to split the sentence with char[] delimiters = new char[] { Space, Comma };

4. Display the string, set up the counter, create the StringBuilder, and parse using the String.Split method to show the problem again.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-35

Collections and Strings


Console.WriteLine("\n\nString s1: {0}", s1); int ctr = 1;

StringBuilder sbOut = new StringBuilder();

// split the string and then iterate over the // resulting array of strings foreach (string subString in s1.Split(delimiters)) { sbOut.AppendFormat( "{0}: {1}\n", ctr++, subString); } Console.WriteLine(sbOut.ToString())

5. Create a Regex object to hold the regular expression.


Regex theRegex = new Regex(" |, ");

In Regular Expressions, the | character represents or. This Regular Expression will match on a space or a comma followed by a space. (For a complete treatment of Regular Expressions, see Mastering Regular Expressions by Friedel, OReilly 1997. ISBN: 1-56592-257-3). 6. Reset the StringBuilder and the counter
sbOut = new StringBuilder(); ctr = 1;

7. Call the instance method Split on the Regex object and iterate over the array of strings returned. Print their contents.
foreach (string subString in theRegex.Split(s1)) { sbOut.AppendFormat( "{0}: {1}\n", ctr++, subString); } Console.WriteLine(sbOut);

11-36

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Strings
This time the output is what you would hope, as shown in Figure 23. Because youve split the string using a regular expression, the comma followed by a space after Associates is seen as a single delimiter, and the string is tokenized appropriately.

Figure 23. Using a regular expression to split the string.

Regular Expressions offer a powerful mechanism for string manipulation. You can use Regular Expressions to pick out tokens from within large text files, and to produce new strings for storage or display.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-37

Collections and Strings

Summary
The .NET Framework provides a number of fully debugged and tested collection classes that you can use in your applications. The Queue class provides first in, first out collection semantics. The Stack class provides last in, first out collection semantics. The HashTable provides a fast and efficient Dictionary class to match keys to values. Collection classes implement IEnumerable to support iteration. Collection classes often provide a Count property to get the number of items, as well as methods to add and remove (and possibly peek at) their contents. Strings are immutable and sealed. Strings provide methods for comparison and manipulation. The StringBuilder class encapsulates a string constructor. Regular Expressions are encapsulated in the Regex class.

11-38

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Strings

Questions
1. If you have a limited resource and want to provide access to that resource from a collection, retrieving from the collection on a first come, first served basis, which collection class should you use? 2. How can you retrieve all the contents of a HashTable, matching each key to its value? 3. What are the implications of the fact that strings are immutable? 4. If you have a variable numDogs and a second variable numCats, how can you use StringBuilder to create a string that looks like We have 9 dogs and 5 cats? 5. What is a verbatim string? 6. What are Regular Expressions?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-39

Collections and Strings

Answers
1. If you have a limited resource and want to provide access to that resource from a collection, retrieving from the collection on a first come, first served basis, which collection class should you use?
To support first come, first served, you should use a Queue.

2. How can you retrieve all the contents of a HashTable, matching each key to its value?
Ask the HashTable for its enumerator (GetEnumerator()) and then iterate with a while loop: while(enumerator.MoveNext(). For each entry retrieved, access enumerator.Key and enumerator.Value.

3. What are the implications of the fact that strings are immutable?
Every time you modify a string you actually create a new one. This calls the strings constructor and has an impact on performance.

4. If you have a variable numDogs and a second variable numCats, how can you use StringBuilder to create a string that looks like We have 9 dogs and 5 cats?
You can use the StringBuilders instance method AppendFormat, passing in the substitution parameters as you would for Console.WriteLine: myStringBuilder.AppendFormat(We have {0} dogs and {1} cats, numDogs, numCats);

5. What is a verbatim string?


A verbatim string begins with the at symbol (@) and what follows is taken as a literal string, character for character, including characters that would otherwise have to be escaped.

6. What are Regular Expressions?


Regular Expressions are a language for describing and manipulating text.

11-40

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Strings

Lab 11: Collections and Strings

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-41

Lab 11: Collections and Strings

Lab 11 Overview
In this lab youll learn how to work with standard .NET collections: stacks and queues. To complete this lab, youll need to work through two exercises: Storing Data in a Queue Storing Data in a Stack

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

11-42

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Storing Data in a Queue

Storing Data in a Queue


Objective
In this exercise, youll create and manipulate a Queue class.

Things to Consider
Queues are used for FIFO (First In, First Out) storage of data. A Queue class holds an object. You may pass in any kind of object and mix and match the objects held in the queue. When you remove an object from the Queue a reference to that object is returned to you.

Step-by-Step Instructions
1. Open the file Queue Starter.sln in the Queue Starter folder. 2. Instantiate a Queue object.
Queue queue = new Queue();

3. Add a string ("Hello") to the queue.


queue.Enqueue("Hello");

4. Add ten integers to the queue.


queue.Enqueue(i*10);

5. Output the progress to the console and then display the queue.
Console.Write( "queue values:\t" ); PrintValues( queue );

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-43

Lab 11: Collections and Strings


6. Remove an element from the queue.
Console.WriteLine( "\n(Dequeue)\t{0}", queue.Dequeue() );

7. Output the progress to the console and then display the queue.
Console.Write( "queue values:\t" ); PrintValues( queue );

8. Remove another element from the queue.


Console.WriteLine( "\n(Dequeue)\t{0}", queue.Dequeue() );

9. Output the progress to the console and then display the queue.
Console.Write( "queue values:\t" ); PrintValues( queue );

10. Display the first element in the queue but do not remove it.
Console.WriteLine( "\n(Peek) \t{0}", queue.Peek() );

11. Output the progress to the console and then display the queue.
Console.Write( "queue values:\t" ); PrintValues( queue );

12. Create the PrintValues method to iterate a collection. Given an object that implements IEnumerable, iterate over the collection and call ToString on each member.

11-44

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Storing Data in a Queue


public static void PrintValues( IEnumerable myCollection ) { foreach (object i in myCollection) { Console.Write("{0} ", i.ToString()); } Console.WriteLine(); }

13. Compile and run the program as shown in Figure 24.

Figure 24. Final exercise results.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-45

Lab 11: Collections and Strings

Storing Data in a Stack


Objective
In this exercise, youll create and manipulate a Stack class.

Things to Consider
Stacks are used for LIFO (Last In, First Out) storage of data. A Stack class holds an object, you may pass in any kind of object and mix and match the objects held in the Stack. When you remove an object from the Stack a reference to that object is returned to you.

Step-by-Step Instructions
1. Open Stack Starter.sln in the Stack Starter folder. 2. Instantiate a Stack object.
Stack myStack = new Stack();

3. Populate the stack.


myStack.Push(i*10);

4. Output the progress to the console and then display the Stack.
Console.Write( "stack values:\t" ); PrintValues( myStack );

5. Remove an element from the stack.


Console.WriteLine( "\n(Pop)\t{0}", myStack.Pop() );

11-46

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Storing Data in a Stack


6. Output the progress to the console and then display the stack.
Console.Write( "myStack values:\t" ); PrintValues( myStack );

7. Remove another element from the stack.


Console.WriteLine( "\n(Pop)\t{0}", myStack.Pop() );

8. Output the progress to the console and then display the stack.
Console.Write( "myStack values:\t" ); PrintValues( myStack );

9. Display the first element in the stack.


Console.WriteLine( "\n(Peek) myStack.Peek() ); \t{0}",

10. Output the progress to the console and then display the stack.
Console.Write( "myStack values:\t" ); PrintValues( myStack );

11. Declare an array of 15 integers.


int[] targetArray = new int[15];

12. Populate the array.


for (int i = 0; i < 15; i++) targetArray[i] = i * 100 + 100;

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-47

Lab 11: Collections and Strings


13. Output the progress to the console and then display the values of the target Array instance.
Console.WriteLine( "\nTarget array: PrintValues( targetArray ); ");

14. Copy the entire source Stack to the target Array instance, starting at index 6.
myStack.CopyTo( targetArray, 6 );

15. Output the progress to the console and then display the values of the target Array instance.
Console.WriteLine( "\nTarget array after copy: PrintValues( targetArray ); ");

16. Copy the entire source Stack to a new standard array.


Object[] myArray = myStack.ToArray();

17. Output the progress to the console and then display the values of the new standard array.
Console.WriteLine( "\nThe new PrintValues( myArray ); array:" );

18. Compile and run the program as shown in Figure 25.

11-48

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Storing Data in a Stack

Figure 25. Final exercise results.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

11-49

Lab 11: Collections and Strings

11-50

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Exceptions

Exceptions
Objectives
Use exceptions to handle unexpected but predictable undesirable situations while your program runs. Create exception handlers. Manage the unwinding of the stack in the presence of exceptions. Set the Message and Help properties of exceptions. Create custom exceptions. Manage nested exceptions.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-1

Exceptions

Throwing and Catching Exceptions


C# developers distinguish among three things that might go wrong while their code is running: Bugs User errors Exceptions

A bug is a mistake in your code. Bugs should be found through careful testing, and repaired as quickly as possible. While few modern complex programs can claim to be bug-free, the only strategy for dealing with bugs is to squish them as quickly as possible. User errors must be anticipated and coped with. When you ask the user to enter his name, he may well enter his birthdate. When you ask the user to click a button, he may right-click, click the wrong button, or pull the plug out of his computer. Users are like that, and you have to be ready and you must recover from their errors as well as you can. Exceptions, however, are neither bugs nor a result of user errors. Exceptions are situations that are predictable, undesirable, and unpreventable. For example, when you try to read a file from the operating system, the file may not exist. Or the file may be in use by another application. Or you may not have enough memory to open the file. Each of these is an exceptional, but predictable circumstance. In C#, exceptions encapsulate this concept of an exceptional circumstance, and they offer you an object-oriented way to handle the exception and to recover gracefully.

Exception Terminology
When an exceptional circumstance arises, an exception object is thrown (some programmers talk about raising an exception, but the common C# term is to throw the exception). When an exception is thrown, an exception handler catches the exception and takes corrective action. When the exception is thrown, all execution stops. If there is an exception handler in the same method, then processing switches to the exception handler. If not, then the stack is unwound until a handler is found. When the stack is unwound, control passes to the method that called the method that threw the exception. If that calling method has no handler, the stack is unwound further, to the method that called that method, and so forth.

12-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions


This process of unwinding is illustrated in Figure 1. The illustration starts in the upper left corner. A method SomeDangerousThing is called (see 1). Within SomeDangerousThing, a second method, SomeOtherFunc is invoked (see 2). Within SomeOtherFunc an exception is thrown. There is no handler in SomeOtherFunc, so the stack is unwound and the exception is passed to the calling method, SomeDangerousThing (see 3). SomeDangerousThing does not have a handler, so the stack is further unwound, and the exception is passed to the first method (see 4). This first method does have a handler (see the catch block). The exception is handled, and program flow continues in this outermost method just below the catch block.

Figure 1. Unwinding the stack.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-3

Exceptions

System.Exception
When you catch an exception, you may catch it by type. This allows you to narrow your exception handler down to a specific type of exception. For example, you might create an exception handler to catch all arithmetic exceptions with this code:
catch(System.ArithmeticException) { // }

System.ArithemeticException derives from System.Exception and is supplied by the .NET Framework. Put the type of the exception in parentheses after the catch statement, and put the handler inside braces, as shown in the previous code. The .NET Framework provides System.Exception, and a number of derived types. You are free to derive your own types from System.Exception as well. In any case, all exceptions must be of type System.Exception or a class derived from System.Exception. If you do not provide a type for the catch statement, it will catch exceptions of any type at all:
catch { // }

The code above is identical in its effect to writing:


catch(System.Exception) { // }

Either of these statements will catch any exception that comes their way.

Exception Handling Order


Because exception handlers are evaluated in the order they appear in the method, you will want to put your more specialized exception handlers before your more general exception handlers. Since System.DivideByZeroException derives from System.ArithmeticException, you will want to place the

12-4

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions


DivideByZeroException handler before the more general ArithmeticException handler:
catch (System.DivideByZeroException) { // } catch (System.ArithmeticException) { // } catch { // }

If the order were reversed, the DivideByZeroException would never be called.


catch (System.ArithmeticException) { // } catch (System.DivideByZeroException) { // } catch { // }

In the order shown here, if a DivideByZero exception were to be raised, the exception would match the more general ArithmeticException and would be handled in that catch block. The DivideByZero handler would never run. If you put these back into the correct order,
catch (System.DivideByZeroException) { // } catch (System.ArithmeticException) { // } catch { // }

the first handler catches a DivideByZeroException. If another arithmetic exception is thrown, it will be ignored by DivideByZero and handled by the second handler. All other exceptions will be ignored by the first and second handler, but caught by the third.

Closing Resources
C# has garbage collection, so you dont have to worry that objects once created might hang around in memory if an exception takes you out of the normal program flow. With that said, there are other resources that are not managed by the garbage collector and can be left in an unacceptable state if an exception is thrown. Imagine, for example, that in a method you open a file, take action, and then close the file. It is imperative that you close the file so that other code can get to the file. If, however, that action throws an exception, there is a danger that the file will not be closed:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-5

Exceptions
SomeMethod() { OpenFile(); TakeDangerousAction(); CloseFile(); // will you get here?? }

If the method TakeDangerousAction throws an exception (or calls a method that throws an unhandled exception) the call to CloseFile may never take place and the file may be left open. You could try to fix this by adding a catch block, and closing the file in the catch block, but this method is prone to error.
SomeMethod() { try { OpenFile(); TakeDangerousAction(); CloseFile(); // will you get here?? } catch { CloseFile(); // error prone } }

The problem with this approach is that changes made in the try block must be duplicated in the catch block. You might add additional catch blocks (for specialized errors) and each will need the call to CloseFile. Sooner or later youll forget to update one or the other and your program will stop working. Or worse, it will work for a long time and then fail unexpectedly. What you need is a way to ensure that the CloseFile() method will be called whether or not an exception is thrown. That is what the finally block is for. The code in a finally block is guaranteed to be called whether or not the catch block is called.

12-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions


SomeMethod() { try { OpenFile(); TakeDangerousAction(); } catch {

CloseFile(); } }

// guaranteed to get here

Rules for finally Blocks


Finally blocks can be created without a catch block, but you must have a try block.
SomeMethod() { try { OpenFile(); TakeDangerousAction(); } finally { CloseFile(); } } // guaranteed to get here

Finally must not exit with break, continue, return, or goto. These unconditional branching statements are covered elsewhere in this course.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-7

Exceptions

Try It Out!
See Exceptions.sln To see how to use exceptions, youll create a simple test application and add exception handling. 1. Open a new console application and name it Exceptions. 2. Create a simple Tester class with a Run() method. 3. Within Run() display where you are and call Function1(). When you return from Function1() display that you are ready to exit from Run().
public class Tester { public void Run() { Console.WriteLine("In Run()"); Function1(); Console.WriteLine("Exiting Run..."); }

4. Add Function1. Have it invoke Function2. Before and after invoking Function1, display your progress.
public void Function1() { Console.WriteLine("In function 1"); Function2(); Console.WriteLine("Exiting function 1"); }

5. Write Function2. Within Function2, display where you are, but throw an exception.

12-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions


public void Function2() { Console.WriteLine("In

Console.WriteLine("Exiting function 2"); }

Notice that you throw an exception by creating one on the heap with the new keyword. In this case, youll throw an instance of System.Exception, passing no parameters to the constructor. 6. Run the application. The results are shown in Figure 2. You can see that the Just In Time debugger has caught the exception. Looking at the output, you see the beginning of Run, Function1, and Function2, but none of the functions complete.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-9

Exceptions

Figure 2. Throwing the exception.

7. Click Yes and proceed into the debugger. You can get to the same place by running the code in the debugger (press F5). In either case, you find yourself in the development environment. The current line of execution is the line in which the exception is thrown. The Debugging dialog box offers you the option to break there or to continue, as shown in Figure 3. Notice in the lower right corner that the call stack shows that the static method Main called the instance method Run, which called Function1 which in turn called the current method: function2.

12-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions

Figure 3. Catching the exception.

8. Click on Break, and press SHIFT+F5 to stop debugging. See Exceptions2.sln To avoid crashing, your program needs a catch block to handle the exception.

9. Rewrite Function2: surround the exception with a try block. Add a catch block to handle the exception; in this case youll just display a message indicating that you caught the exception.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-11

Exceptions
public void Function2(){ Console.WriteLine("In function 2");

try { Console.WriteLine("Entering Try..."); throw new System.Exception(); Console.WriteLine("Exiting Try..."); } catch { Console.WriteLine("Caught exception..."); } Console.WriteLine("Exiting function 2"); }

10. Run the application. The results are shown in Figure 4. The catch block allows your program to recover. You now see Function2 return, Function1 returns, and Run returns. All is right with the world.

Figure 4. Catching the exception.

Unwinding the Stack


Often you will not have a catch block (or even a try block) in the particular method that throws an exception. Unhandled exceptions are passed back to the calling method, and you may have an exception handler there.

12-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions

Try It Out!
See Exceptions3.sln To see how a calling function might handle an exception, youll rewrite the previous example. 1. Reopen the previous example. 2. Take the try/catch block out of function2.
public void Function2() { Console.WriteLine("In function 2"); throw new System.Exception(); Console.WriteLine("Exiting function 2"); }

3. Add a try/catch block to Function1. Function1 will now call Function2 from within its try block. If an unhandled exception is thrown in Function2, it will be caught in the try block of Function1.
public void Function1() { Console.WriteLine("In function 1"); try { Console.WriteLine("Entering Try..."); Function2(); Console.WriteLine("Exiting Try..."); } catch { Console.WriteLine("Caught exception..."); } Console.WriteLine("Exiting function 1"); }

4. Run the application. The results are shown in Figure 5. Examine Figure 5 carefully and compare it with Figure 4. They are different in a very important way. In Figure 4 you see that Function2 exits cleanly and the C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-13

Exceptions
message Exiting Function2 is displayed. That message does not appear in Figure 5.

Figure 5. Catching a calling function.

In this example, the method Function2 did not exit cleanly; an exception was thrown and not handled. The stack was unwound and the exception was handled in Function1, but the final line of Function2 never executes. This is a critical distinction.

Catching Specific Exception Types


Until now youve been using the generic System.Exception. The .NET Framework provides a number of specialized exception types to help you manage various exceptional circumstances.

Try It Out!
See Exceptions4.sln In the next example, you will create a method that under one circumstance throws a DivideByZero exception, and under a different circumstance throws an ArithmeticException. Your code must differentiate between these two exception types. It is important to understand that DivideByZeroException derives from ArithmeticException, which in turn derives from System.Exception. Thus, DivideByZeroException is an ArithmeticException. 1. Reopen the previous example. 2. Create a new method, DoDivide. This method will take two doubles and return the quotient as a double. If the divisor is zero you will throw the specialized DivideByZeroException because it is illegal to divide by zero.

12-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions


Normally, it is just fine to divide zero by another number, but in this case, youre not going to allow that, and youre going to throw the more general ArithmeticException. This is a bit contrived (since dividing zero by another number is mathematically just fine) but it will illustrate how exceptions are handled.
public double DoDivide (double dividend, double divisor) { if (divisor == 0) throw new System.DivideByZeroException(); if (dividend == 0) throw new System.ArithmeticException(); return dividend/divisor; }

3. You will call this method from Function2, The first time you call youll divide 12 by 15.
public void Function2() { Console.WriteLine("In function 2"); double x = 12; double y = 15; Console.WriteLine("Trying {0} / {1}...", x,y); Console.WriteLine ("{0} / {1} = {2}", x, y, DoDivide(x,y));

4. Assign the value zero to the local variable y and call DoDivide again.
Console.WriteLine("Trying {0} / {1}...", x,y); Console.WriteLine ("{0} / {1} = {2}", x, y, DoDivide(x,y));

5. When you are done with Function2, display that and exit the method.
Console.WriteLine("Exiting function 2"); }

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-15

Exceptions
Notice that there is no try or catch block in Function2. Youll catch any exceptions in Function1. 6. Rewrite Function1 to call Function2 within a try block, but specialize your exception handling. Youll need three catch blocks. The first will catch a DivideByZeroException, the second will catch the more general ArithmeticException, and the third will catch any exception at all.
public void Function1() { Console.WriteLine("In function 1"); try { Console.WriteLine("Entering Try..."); Function2(); Console.WriteLine("Exiting Try..."); }

catch (System.DivideByZeroException) { Console.WriteLine( "Divide by zero exception caught"); } catch (System.ArithmeticException) { Console.WriteLine( "ArithmeticException exception caught"); }

catch { Console.WriteLine( "Caught unknown exception..."); } Console.WriteLine("Exiting function 1"); }

7. Run the program. The results are shown in Figure 6. The first time through, the DoDivide method works fine and no exception is thrown; 12/15 = 0.8. The second time through, you try to divide 12 by 0 and that 12-16 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions


does throw an exception. The exception is caught and the correct specific message is displayed.

Figure 6. Catching specialized exceptions.

8. Try reversing the catch blocks in Function1.


public void Function1() { Console.WriteLine("In function 1"); try { Console.WriteLine("Entering Try..."); Function2(); Console.WriteLine("Exiting Try..."); } catch (System.ArithmeticException) { Console.WriteLine( "ArithmeticException exception caught"); } catch (System.DivideByZeroException) { Console.WriteLine( "Divide by zero exception caught"); }

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-17

Exceptions
catch { Console.WriteLine("Caught unknown exception..."); } Console.WriteLine("Exiting function 1"); }

9. Run the application again. The program wont compile. The error message, on the line for the DivideByZero exception is: A previous catch clause already catches all exceptions of this or a super type (System.ArithmeticException). The compiler recognizes that it is impossible to catch a DivideByZero exception if you place the ArithmeticExcpetion first!

Finally
In the previous example, Function1 calls Function2 and Funtion2 throws an exception. The catch block in Fuction1 catches the exception and does not crash, but the line after the call to Function2, Exiting Try is never invoked. This line doesnt do anything important, so no real harm is done, but there are circumstances where not invoking the lines following the call to a function might be disastrous. For example, if Function1 were to open a file and then call Function2 and then close the file, the exception in Function2 might prevent the file from ever being closed. You can solve that problem with a finally block. The keyword finally creates a block of code that will run whether or not an exception is handled. The finally block can only follow a try block: you cant have finally without try, though you can have finally without a catch block.

Try It Out!
See Exceptions5.sln In this next exercise, youll rewrite the previous example to illustrate the use of the finally block. 1. Reopen the previous example. 2. In the try block within Function1 open a file (youll mimic this by displaying the words Opening a file).

12-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions


public void Function1() { Console.WriteLine("In function 1"); try { Console.WriteLine("Entering Try..."); Console.WriteLine("Opening a file..."); Function2(); Console.WriteLine("Exiting Try..."); } catch (System.DivideByZeroException) { Console.WriteLine("Divide by zero exception caught"); } catch (System.ArithmeticException) { Console.WriteLine("ArithmeticException exception caught"); }

catch

Console.WriteLine("Caught unknown exception..."); }

3. You must close the file. You cant put it in the try block because Function2 might throw an exception. Instead, youll put it in the finally block where it is guaranteed to run whether or not there is an exception.
finally { Console.WriteLine("Closing the file..."); }

5. Run the application. The results are shown in Figure 7. Notice that the finally block has executed even though an exception was caught.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-19

Exceptions

Figure 7. Executing the finally block.

6. Comment out the call to set y to 0. This removes the need to throw an exception. 7. Run the application again. The results are shown in Figure 8. This time no exception is thrown, and you see the display of Exiting Try. Notice, however, that the message Closing the file is still displayed; the finally block is invoked whether or not an exception is thrown.

Figure 8. No exception was thrown.

The Exception Object


Until now youve been dealing with exceptions as sentinel objects; their very presence signals a problem. Exceptions are classes, however, and as such they 12-20 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions


have methods and properties. System.Exception, for example, has a few very useful properties. The Message Property stores a message describing the exception. Each .NET Framework exception type has a default message, but you are free to set the message as well. One overloaded version of the constructor for System.Exception takes a message parameter; whatever string you pass in is stored as the message for that exception. A second useful property is the HelpLink. This is a URL that you can store with the exception to guide the user to more documentation about the exception itself. A third very helpful property is the StackTrace. By displaying the StackTrace the developer can examine the exact location in the code where the exception was thrown. You can take control of this information to write it to a log file or to display it to the user.

Try It Out!
See Exceptions6.sln To see how these properties are used, youll rewrite the previous example to create and manipulate instances of .NET exceptions. 1. Reopen the previous example. 2. Rewrite DoDivide. If the divisor is 0, instantiate a DivideByZeroException object.
public double DoDivide (double dividend, double divisor) { if (divisor == 0) { DivideByZeroException e = new DivideByZeroException();

3. Add a help link for this exception. Typically youd provide a URL to a specific document. In this case, just provide a general URL to a Web site.
e.HelpLink = "http://www.LibertyAssociates.com";

4. Now that the exception has been set the way you want it, throw the exception.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-21

Exceptions
throw e;

5. Modify the code that catches the DivideByZero exception to display the default message, along with the HelpLink you created and the StackTrace created by the CLR.
catch (System.DivideByZeroException e) { Console.WriteLine("Divide by zero exception: {0}", e.Message );

Console.WriteLine("\nHelp link: {0}", e.HelpLink);

Console.WriteLine("\nStack trace: {0}", e.StackTrace); }

Notice that this time you provide an identifier for the DivideByZeroException. Here you use the letter e as the identifier; this is a common idiom. You can display the Message property by writing e.Message, just as you would with any other object. 6. Run the application; the results are shown in Figure 9. You can see the exception thrown, and the default message is displayed: Attempted to divide by zero. The HelpLink property is used to display a URL for more information, and the stack trace tells you the exact line on which the exception occurs. Notice that the stack trace also shows the line on which Function1 calls Function2, and on which Run calls Function1. This is a complete stack trace to help you track down exactly what was happening in your code.

12-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions

Figure 9. Exception properties.

7. Modify the creation of the DivideByZero exception. This time, pass a custom message to the constructor.
public double DoDivide (double dividend, double divisor) { if (divisor == 0) { DivideByZeroException e = new DivideByZeroException( "No quick road to infinity!");

8. Comment out the line with the stack trace (to simplify the output).

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-23

Exceptions
catch (System.DivideByZeroException e) { Console.WriteLine("Divide by zero exception: {0}", e.Message );

Console.WriteLine("\nHelp link: {0}", e.HelpLink);

// Console.WriteLine("\nStack trace: {0}", // e.StackTrace); }

9. Rerun the application. The results are shown in Figure 10. Your custom message is displayed. When you instantiate the DivideByZeroExceptionObject the string you pass as a parameter is sent up to the base class System.Exception and assigned to the Message property.

Figure 10. A custom exception message.

Creating Custom Exception Types


The .NET Framework provides a number of specialized exception types, but if you cant find what you want, you are free to create your own. You may then throw these exceptions and catch them just as you might throw and catch any of the built-in types.

12-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions


The only restriction on your built-in types is that they must ultimately derive from System.Exception. You are free to derive from an existing exception type or from System.Exception directly.

Rethrowing Exceptions
So far, whenever youve thrown an exception, youve caught it in a catch block, handled it, and moved on. There are times, however, when you will take some action in the exception handler and then throw the exception again. You can rethrow the same exception just by writing.
throw;

You might decide to throw a new exception, however. You do that by creating the new exception and throwing it from within the exception handler for the first exception.

Inner Exceptions
Throwing a new exception is fine, but perhaps you dont want to lose the information stored in the original exception. You can wrap the old exception inside the new exception! Every exception has a reference to another exception, known as the inner exception. When your exception is created the InnerException property is null, but you are free to assign an exception to it. For example, you might catch a DivideByZero exception and take some action. You might then create a new System.Exception object to throw, but youll nest the original DivideByZero inside it to preserve the original message, as shown in Figure 11. The original DivideByZero exception is housed within the new System.Exception object.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-25

Exceptions

Figure 11. Nesting an exception.

When you catch the System.Exception object you might decide to throw a new exception, nesting the original exception within the new, custom exception. Of course, the Exception object you are nesting is itself nesting the DivideByZero object, as shown in Figure 12.

Figure 12. Cascading nested exceptions.

12-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions

Try It Out!
See Custom Exceptions.sln To see how custom exceptions work, youll create a simple console application and add a custom exception type. Youll also nest exceptions to see the advantage of keeping an exception history. 1. Create a new console application named CustomExceptions. 2. Create a custom exception type, BozoException. The constructor will take two parameters: a string message and an object of type System.Exception inner.
public class BozoException : System.Exception { public BozoException( string message, Exception inner): base(message, inner) { } }

BozoException derives from System.Exception, and it chains up to the Exception constructor, passing along the two parameters to its base class constructor. System.Exceptions constructor is overloaded to take these two objects. The first, message is assigned to the Message property. The second, inner, is assigned to the InnerException property. This is how inner exceptions are stored. 3. Create a Tester class with seven methods, each of which uses Console.WriteLine to display its progress. The first method is the static method Main, which instantiates a Tester and calls Run.
public static void Main() { Console.WriteLine("In Main()"); Tester t = new Tester(); t.Run(); Console.WriteLine("Exiting Main..."); }

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-27

Exceptions
4. Create the Run method. Other than display its progress, Runs only job is to invoke Method1.
public void Run() { Console.WriteLine("In Run()"); Method1(); Console.WriteLine("Exiting Run..."); }

5. Method1 opens a file and then invokes Method 2.


Console.WriteLine("In Method 1"); try { Console.WriteLine("Entering Try..."); Console.WriteLine("Opening a file..."); Method2(); Console.WriteLine("Exiting Try..."); }

6. Method2 invokes Method3, which invokes Method4, which in turn invokes Method5. Method5 throws a DivideByZeroException, passing in a custom message.
public void Method5() { throw new DivideByZeroException( "Method5 Divided by zero exception"); }

7. The stack is unwound and the exception is caught by Method4 (the method that called Method5). Give Method4 a catch block to catch ArithmeticException objects. Method4 will take partial action based on this exception. In this case it just prints a message to the console, but in a real application it might take other actions based on registering the exception. It then rethrows the exception, exactly as is, making no modifications to it. 12-28 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions

public void Method4() { try { Method5(); } catch (System.ArithmeticException e) { Console.WriteLine( "Method4 caught an arithmetic exception. Rethrowing..."); throw; // just toss it back out there } }

Notice that the catch block is looking for a base class of DivideByZeroException. Since DivideByZeroException derives from ArithmeticException it is-an ArithmeticException and so the catch block matches. Note also that in order to rethrow the exception you use the throw keyword. 8. The stack is unwound and the exception is offered to Method3. Method3 has a handler for DivideByZero exception. In the handler Method3 creates a new exception object (in this case, of type System.Exception). It creates the new exception object and passes in two parameters: a string and the original exception. The original exception is assigned to the InnerException property of the new exception, then the new exception is thrown.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-29

Exceptions
public void Method3() { try { Method4(); } catch (System.DivideByZeroException e) { Exception ex = new Exception( "Method3 Caught divide by zero", e); throw ex; } }

9. Once again the stack is unwound, this time to Method2. Method2 has a catch block for System.Exception, and so catches the thrown exception. In the exception handler, it creates a new instance of the custom exception type BozoException, passing in a new message and the old exception. Notice that the exception it passes in is the System.Exception object created in Method3s catch block, which in turn has as a nested exception, the original DivideByZeroException object. This System.Exception object is now the InnerException property in the new BozoException.
public void Method2() { try { Method3(); } catch (System.Exception e) { BozoException bozo = new BozoException( "Method2 Custom Exception Situation!",e); throw bozo; } }

12-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions


10. The catch block then throws this BozoException, again unwinding the stack. Control returns to the catch block for Method1. In that catch block, you will print out the message from the exception and all its nested exceptions.
public void Method1() { Console.WriteLine("In Method 1"); try { Console.WriteLine("Entering Try..."); Console.WriteLine("Opening a file..."); Method2(); Console.WriteLine("Exiting Try..."); }

catch (BozoException e) { Console.WriteLine("\n{0}",e.Message); Console.WriteLine("Exception history..."); Exception inner = e.InnerException; while (inner != null) { Console.WriteLine("{0}", inner.Message); inner = inner.InnerException; } } finally { Console.WriteLine("Closing the file..."); } Console.WriteLine("Exiting Method 1"); }

You start by printing the message from the exception:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-31

Exceptions
catch (BozoException e) { Console.WriteLine("\n{0}",e.Message);

To print the inner exception messages, you start by creating a local object of type Exception and assigning to it the InnerException property of the exception you caught.

Exception inner = e.InnerException;

You then create a while loop to test if inner is null. You print the message, and then you assign to inner its own InnerException property, thus reassigning the nested exception object to inner.
while (inner != null) { Console.WriteLine("{0}", inner.Message); inner = inner.InnerException; }

This is a tricky bit of code, so make sure you understand how it works before going forward. At the start of this catch block you have the following structure:
BozoException System.Exception DivideByZeroException

BozoException has an InnerException property of type System.Exception, which in turn has an InnerException property of type DivideByZeroException. When you assign inner to e.InnerException, e is the BozoException and inner is now a reference to System.Exception. When you write
inner = inner.InnerException

inner is the System.Exception so InnerException is the DivideByZeroException. At the end of the assignment, inner refers to the DivideByZeroException! 12-32 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions


11. Run the application. The results are shown in Figure 13. You see the exception history printed, much as you might expect.

Figure 13. Displaying inner exceptions.

To really understand how this works, you need to put this code into the debugger. 12. Place a breakpoint in Method5 where the exception will be thrown, as shown in Figure 14. Run to the breakpoint by pressing F5.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-33

Exceptions

Figure 14. Setting the breakpoint.

13. Press F11 to step into the code. Control transfers to the Catch block in the calling method, Method4. Step in; youll see the message displayed and the exception is rethrown. 14. The stack is unwound when the exception is rethrown and control transfers to the catch block in the calling method, Method3. This time you create a new exception and throw that. 15. Continue to step in; youll find control passes to the catch block in Method2 where a new exception of type BozoException is created. Continue to press F11; youll step into the constructor for your custom exception, and youll see the parameters passed to the base class constructor. 16. Continue stepping back into Method 2; the new BozoException is thrown. 17. The stack continues to unwind and you find yourself back in the catch block of Method1. Before you step further, examine the Locals window. You should see the exception, e. 18. Undock the Locals window to make it nice and big, and expand e, as shown in Figure 15. When you expand e, youll find System.Exception, the base class. Expand that as well. Inside the base class youll find many 12-34 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions


fields, including _innerException. This is the underlying field supporting the InnerException property.

Figure 15. Viewing the nested objects.

19. Expand innerException and youll find the properties of the inner exception object. One of the properties will be _innerException. This is the innerException field of the nested exception. 20. Expand the inner exception object and youll find a System.DivideByZeroException. Inside youll find its base class Sytem.ArithemeticException. In that youll find System.Exception and inside that will be the _innerException field. That innermost innerException field is null, just as youd expect. 21. Continue stepping through the code. You can watch as inner is assigned to the object referred to by the InnerException property. 22. Each message is displayed in turn until the object inner is null. The loop then finishes, and youll step into the finally block where the file is closed, and Method 1 unwinds to the Run method, which in turn unwinds to Main, and the program ends.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-35

Exceptions

Summary
Exceptions are thrown to flag undesirable, but predictable problems in the running of your program. Exceptions should not be used to manage bugs or user errors. Exceptions are thrown and caught. The catch block handles the exception and allows your program to continue. When an exception is thrown, control moves to the catch block. If there is no appropriate catch block, the stack is unwound until a suitable handler is found. A finally block is guaranteed to run whether or not there is an exception. A finally block does not require a catch block, but it does require a try block. You may derive new exceptions from System.Exception or any other existing exception class. Exceptions are presented to their handlers in the order the handlers are written. You must put more specialized handlers before more general handlers. The exception type has a number of useful properties, including Message, HelpLink, and StackTrace. The Message property allows you to create a custom message. You may pass the message to the constructor of the exception. The HelpLink property allows you to provide a URL for help files. The StackTrace exception is supported by the CLR and allows you to display or log the line on which the exception is thrown, along with a complete stack trace of the method calls. An exception can be rethrown with the keyword throw. You may nest one exception within another using the InnerException property.

12-36

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions

Questions
1. Name three valid circumstances for throwing exceptions and two invalid circumstances. 2. How do you ensure that a resource will be closed (or otherwise disposed of), in the presence of exceptions? 3. How do you pass a custom message to an exception? 4. How do you pass a URL to provide a Help file when an exception is thrown? 5. Can a custom exception derive from Object?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-37

Exceptions

Answers
1. Name three valid circumstances for throwing exceptions and two invalid circumstances.
A program might throw an exception for any number of reasons, typically when a resource is not available. Three good examples include (1) running out of memory, (2) attempting to open a file and the file is missing or not valid to open, and (3) attempting to contact another machine on the network and your program times out. You do not want to use an exception for (1) a bug in your code or (2) invalid user data entry.

2. How do you ensure that a resource will be closed or otherwise disposed, in the presence of exceptions.
Be sure to create a try block around any potentially dangerous code, and put the close/dispose code inside a finally block.

3. How do you pass a custom message to an exception?


You may pass a message to the constructor; it will be assigned to the Message property. The property is read-only, so you must set the message when you construct the object.

4. How do you pass a URL to provide a Help file when an exception is thrown?
To set the URL of a Help file, use the read/write HelpLink property of the exception.

5. Can a custom exception derive from Object?


All exceptions must derive from System.Exception, which of course, ultimately derives from Object.

12-38

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions

Lab 12: Exceptions

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-39

Lab 12: Exceptions

Lab 12 Overview
In this lab youll learn how to work with exceptions. To complete this lab, youll need to work through two exercises: Throwing and Catching Exceptions Creating Custom Exceptions

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

12-40

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions

Throwing and Catching Exceptions


Objective
In this exercise, youll throw and catch exceptions and work with finally blocks.

Things to Consider
Exceptions are caught in the order most-derived to least derived. The finally block is executed whether or not an exception is thrown. The .NET Framework provides a number of standard exception classes. All Exception classes must ultimately derive from System.Exception.

Step-by-Step Instructions
1. Open Exceptions Starter.sln in the Exceptions Starter folder. 2. Create a try block and display it to the console.
try { Console.WriteLine("Opening file..."); Console.WriteLine("Calling SecondFunction");

3. Call the second function, display it to the console, and end the try block.
SecondFunction(); Console.WriteLine("Exiting Try..."); }

4. Create a catch block to catch DivideByZeroException. Display to the console that DivideByZeroException was caught.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-41

Lab 12: Exceptions


catch (System.DivideByZeroException) { Console.WriteLine("Divide by zero exception caught"); }

5. Create a generic catch block. Display to the console that an exception was caught.
catch { Console.WriteLine("Caught unknown exception..."); }

6. Create a finally block. Display to the console that a finally block was caught.
finally { Console.WriteLine("Closing the file..."); }

7. Implement the SecondFunction. Display the progress to the console.


public void SecondFunction() { Console.WriteLine("In SecondFunction");

8. Create two double variables and initialize them to small values.


double x = 10; double y = 5;

9. Call DoDivide and display the results.


Console.WriteLine ("{0} / {1} = {2}", x, y, DoDivide(x,y));

12-42

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Throwing and Catching Exceptions


10. Set the second variable to zero.
y = 0;

11. Call DoDivide and display the results.


Console.WriteLine ("{0} / {1} = {2}", x, y, DoDivide(x,y));

12. Implement DoDivide. Test for divide by zero and if found, throw DivideByZeroException.
public double DoDivide (double a, double b) { if (b == 0) throw new System.DivideByZeroException(); return a/b; }

13. Compile and run the application as shown in Figure 16.

Figure 16. Final exercise results.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-43

Lab 12: Exceptions

Creating Custom Exceptions


Objective
In this exercise, youll see how to create and work with your own custom exception class, and how to nest exceptions in multiple throw statements.

Things to Consider
When you catch an exception and then throw a new exception, you may house the original exception as an inner exception within the new one. Exceptions stack one within the other, so that you may retrieve the entire history of inner exceptions. Custom exceptions may have methods and members, but it is not uncommon to use an exception object as a signal; its very existence signals the special kind of exception.

Step-by-Step Instructions
1. Open Custom Exceptions Starter.sln in the Custom Exceptions Starter folder. 2. Create a custom exception class derived from System.Exception.
public class CustomException : System.Exception {

3. Create a constructor that takes two parameters. The first is a string for the exception message. The second is of type Exception and represents the inner exception. Pass both to the base class constructor.
public CustomException( string message, Exception inner): base(message, inner) { }

12-44

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Custom Exceptions


4. Create a try block and output the progress to the console.
try { Console.WriteLine("Opening a file...");

5. Call SecondMethod. Output the progress to the console and end the try block.
SecondMethod(); Console.WriteLine("Exiting Try..."); }

6. Catch the CustomException.


catch (CustomException e)

7. Display that youve caught the custom exception and display the exceptions message.
Console.WriteLine("First Method Caught custom exception"); Console.WriteLine("{0}\n",e.Message);

8. Display that you are going to show the exception history.


Console.WriteLine("Displaying exception history...");

9. Display all the inner exceptions.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-45

Lab 12: Exceptions


Exception inner = e.InnerException; while (inner != null) { Console.WriteLine("{0}", inner.Message); inner = inner.InnerException; }

10. Create a finally block. Within the finally block, display that you are closing the file.
finally { Console.WriteLine("Closing the file..."); }

11. Create a SecondMethod to call the first method.


public void SecondMethod() {

12. Make the method call within a try block.


try { ThirdMethod(); }

13. Catch any Exception object and create a new instance of CustomException, passing in this text as the exception text: Ave, Caeser, morituri te salutant. Pass the exception you caught as a second parameter.

12-46

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Custom Exceptions


catch (System.Exception e) { CustomException Custom = new CustomException( "Ave, Caeser, morituri te salutant.",e); throw Custom; }

14. Create the ThirdMethod. This method creates a try block and within it, calls FourthMethod.
public void ThirdMethod() { try { FourthMethod(); }

15. Create a catch block and catch DivideByZeroException. Create a new generic exception and pass in the string ThirdMethod caught divide by zero as well as the original exception.
catch (System.DivideByZeroException e) { Exception ex = new Exception( "ThirdMethod caught divide by zero", e); throw ex; }

16. Create a FourthMethod.


public int FourthMethod() {

17. Create two integer variables. Initialize them to five and zero. Divide the first by the second. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

12-47

Lab 12: Exceptions


int x = 5; int y = 0; return x / y;

18. Compile and run the application as shown in Figure 17.

Figure 17. Final exercise results.

12-48

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Delegates

Delegates
Objectives
Understand what delegates are. See the use of delegates for the indirect invocation of methods. Explore delegates as static members. Define delegates as static properties.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-1

Delegates

Introduction to Delegates
There are times when you need to invoke a method indirectly; when you need to call a method but you cant know at compile time which method youll call. Delegates are a way to identify a method at run time and to invoke that method indirectly (through the delegate rather than by writing the name of the delegate into your code). Technically, delegates are reference types that encapsulate a method with a specific signature and return type. The equivalent to delegates in C++ is a pointer to a member function. In C#, however, delegates are first class typesafe members of the language. A delegate is said to encapsulate a method. The methods a given delegate may encapsulate are described by the delegate definition. Each delegate defines the return type and parameter list of the methods it can encapsulate. For example, a given delegate might be defined as:
void myDelegate(string s1, int i2);

This defines myDelegate as a delegate that can encapsulate any method that returns void and that takes two parameters; the first must be a string and the second must be an int. You can use this delegate to represent any method with this return type and signature.

Why Use Delegates?


Delegates are used whenever you cant know at compile time, which method out of a set of similar methods will be invoked. You can also use delegates to support the publish/subscribe idiom and handle events, as will be described later in this course. Delegates are the mechanism through which C# supports callback methods. A callback method allows you to invoke a method and say to that method When you are done with your work, call this method. To accomplish this, you must pass in a method to callback, and you do so using a delegate.

13-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Encapsulating a Method

Encapsulating a Method
Objective
The objective of this example is to create a class with a delegate that encapsulates a method that will be invoked at run time. This demonstrates the definition of a delegate and invokes a method through that delegate.

The Details
See Delegates1.sln Start by creating the Couple class to hold an ordered pair of objects. Couple stores these objects in a private array and initializes the array in its constructor:
public class Couple { object[] theCouple = new object[2];

public Couple(object first, object second) { theCouple[0] = first; theCouple[1] = second; }

The Couple class contains a display method that shows which object is first and which is second.
public void Display() { Console.WriteLine("theCouple[0]:{0}", theCouple[0].ToString()); Console.WriteLine("theCouple[1]:{0}", theCouple[1].ToString()); }

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-3

Delegates
Couple also has a SetOrder method that sets the order of the objects. To do this, Couple calls a method, determined at run time, which decides whether the objects should be re-sorted so the second object comes first. The job of the method passed in to SetOrder is to take the two objects as arguments, and return a Boolean: true if the objects are currently in the wrong order and should be reversed, or false if they are already in the right order and should not be reversed. Couple defines the signature and returns the type of method it needs using a delegate:
public delegate bool Reverse(object lhs, object rhs);

Reverse is a public delegate for a method that returns bool and takes two parameters, both of type object. The SetOrder method takes an instance of such a delegate as a parameter and then calls the delegated method to see if it should reverse the order of the elements in its internal array:
public void SetOrder(Reverse theDelegatedFunction) { if (theDelegatedFunction(theCouple[0],theCouple[1])) { object temp = theCouple[0]; theCouple[0] = theCouple[1]; theCouple[1] = temp; } }

If the delegated function returns true, the order of the internal objects is reversed.

Creating an Employee Class


You now need to create two classes, instances of which may be stored in a Couple. The first class will be a simple Employee class. An employee will have two private members, an int representing the employee id and a string representing the employees name. These will be initialized in the constructor.

13-4

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Encapsulating a Method
public class Employee { private int empID; private string name; public Employee(string name, int ID) { this.name = name; empID = ID; }

To be stored in a Couple, the Employee must support a method that can be encapsulated by the Reverse delegate. This method must return a bool indicating whether the employees are out of order. You can imagine setting that order either by the ID or by the name. Employee supports methods for both.
public static bool SecondIDLower( object firstEmployee, object secondEmployee) { Employee first = (Employee) firstEmployee; Employee second = (Employee) secondEmployee; return first.empID > second.empID; }

public static bool SecondAlphaLower( object firstEmployee, object secondEmployee) { Employee first = (Employee) firstEmployee; Employee second = (Employee) secondEmployee; return (string.Compare(first.name, second.name))>0; }

Notice that neither of these methods is named Reverse. The Reverse delegate encapsulates these methods, but they can be named whatever the class designer decides is appropriate. Finally, the Employee class overrides the ToString method to print the employees name and employee id:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-5

Delegates
public override string ToString() { return name + ": " + empID.ToString(); }

Creating a Game Class


Lets use a second class to store in the Couple to demonstrate that the Couple is flexible enough to work with different types of objects. The GamePiece class has two member variables: an int named value and a string for the name of the piece. It offers only one delegatable method, LeftIsStronger, and it overrides ToString to print the name and value of the piece.
public class GamePiece { private int value; private string name; public GamePiece(string name, int value) { this.name = name; this.value = value; }

public static bool LeftIsStronger( object left, object right) { GamePiece lhs = (GamePiece) left; GamePiece rhs = (GamePiece) right; return lhs.value > rhs.value; }

public override string ToString() { return name + ": " + value.ToString(); } }

13-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Encapsulating a Method

Testing the Application


To test the functionality of the Couple object, youll instantiate two Employee objects and then put them into a Couple. You can then ask that couple to display the Employees.
Employee joe = new Employee("joe",25); Employee fred = new Employee("fred",35); Couple employees = new Couple(joe, fred); employees.Display();

You are now ready to instantiate the delegate, passing in the method youll use to sort the Employee objects.
Couple.Reverse employeeByID = new Couple.Reverse(Employee.SecondIDLower);

This defines employeeByID as an instance of the Reverse delegate defined within the Couple class. It encapsulates the method SecondIDLower of the Employee class within the newly created employeeByID delegate. You can pass that delegate to the SetOrder method of the Couple object:
employees.SetOrder(employeeByID);

You can then display the results of the sort:


Console.WriteLine("after sort by id..."); employees.Display();

Having sorted by id, you are ready to create a second instance of the delegate, this time encapsulating the method that sorts alphabetically. Rather than creating an instance of the delegate in a temporary variable, youll instantiate the delegate within the call to SetOrder:
employees.SetOrder(new Couple.Reverse(Employee.SecondAlphaLower));

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-7

Delegates
The logic here is that the new statement creates a new Reverse delegate, encapsulating the method SecondAlphaLower and that delegate is passed as a paramter to SetOrder. You can display the results by calling Display:
Console.WriteLine("After sort by alpha..."); employees.Display();

You are now ready to prove that the delegate can encapsulate not only different methods from the same class, but also methods from other classes. To demonstrate this, youll instantiate a pair of Game pieces, and store them in a Couple object and display them. Youll then create a new instance of the Reverse delegate and pass that to the SetOrder method of the Couple youve created, displaying the results:
GamePiece scout = new GamePiece("Scout",2); GamePiece general = new GamePiece("Marshall",10); Couple gamePieces = new Couple(general,scout); gamePieces.Display(); Couple.Reverse theGameDelegate = new Couple.Reverse(GamePiece.LeftIsStronger); gamePieces.SetOrder(theGameDelegate); Console.WriteLine("after sort pieces by values..."); gamePieces.Display();

Running the Program


The results of running the program are shown in Figure 1.

13-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Encapsulating a Method

Figure 1. Invoking methods with delegates.

The first two lines show the output after adding joe and fred, and you can see the result of sorting by id. Since the ids were already in the correct order, there was no change. Sorting by alpha, however, reversed the order of the objects in the array. The next part of the output indicates the creation of the Marshall and the Scout and the initial display shows that the Marshall is listed first. After sorting by value the game pieces are reversed.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-9

Delegates

Delegates as Static Methods


While the previous example works well, there is a problem with the design as presented so far. The test class knows a bit too much about the internals of the Employee and Game class. To create the delegate, the Test class must instantiate a delegate, passing in the Employee method. Youd like the responsibility for creating the delegate, and knowing which method to use, to be encapsulated within the Employee (or Game) class itself. That is, a client of a Couple should not need to know which Employee or Game method to invoke. See Delegates2.sln You can solve this by providing static instances of the delegates within the Employee or Game class. For example, you can modify the Employee class to add two new declarations:
public static readonly Couple.Reverse EmployeeByID = new Couple.Reverse(SecondIDLower); public static readonly Couple.Reverse EmployeeByAlpha = new Couple.Reverse(SecondAlphaLower);

This declares EmployeeByID to be a static read-only (not modifiable) instance of a Reverse Delegate, and instantiates the static instance with the method SecondIDLower. Similarly, EmployeeByAlpha is a static instance of the delegate, encapsulating SecondAlphaLower. The test class no longer needs to create an instance of the delegate. It can just invoke SetOrder, passing in the static delegate instance:
employees.SetOrder(Employee.EmployeeByID);

After displaying the results, it can call SetOrder again, ordering the couple alphabetically:
employees.SetOrder(Employee.EmployeeByAlpha);

Similarly, the Game class can create a static instance of the delegate:
public static readonly Couple.Reverse GamePieceByStronger = new Couple.Reverse(LeftIsStronger);

13-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Delegates as Static Methods


Nothing else in the Game class changes; the static delegate allows the test program to call SetOrder on the Couple housing two game pieces just by passing in the correct static delegate:
gamePieces.SetOrder(GamePiece.GamePieceByStronger);

The Static Declaration in Detail


The static declaration is somewhat complicated. Lets take it apart:
public static readonly Couple.Reverse GamePieceByStronger = new Couple.Reverse(LeftIsStronger);

The first keyword is public. This indicates that the static delegate will be visible to methods of other classes (in this case, the test class). The static keyword indicates that the delegate is accessed through the class, rather than through an instance of the class. The readonly keyword indicates that once instantiated, the static delegate GamePieceByStronger cannot be modified. Couple.Reverse is the type. The object GamePieceByStronger is defined to be a Reverse delegate as defined within the Couple class. GamePieceByStronger is the identifer: the name of the delegate. The keyword new allocates memory for the instance of the delegate. Couple.Reverse following new is the type of the object being instantiated. LeftIsStronger is passed in to the delegate as the method to encapsulate. Notice that since this occurs within the definition of the Employee class, there is no need to specify the class for the method LeftIsStronger.

The output of the program using static delegates is shown in Figure 2. You can see that it is unchanged from Figure 1. Using static delegates gives you a cleaner design, but does not change the output in any way.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-11

Delegates

Figure 2. Static delegates.

13-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Delegates as Properties

Delegates as Properties
While creating the delegates as static methods works, it has the disadvantage that the static delegates are instantiated whether or not they are ever used. Static members are created when the application begins, and there is no test to see if they are ever used in the running of the program. With just three such delegates, there is little harm in creating them at the start of the program. On the other hand, if you had 500 or 50,000 such delegates, and you were going to invoke only a handful, this would be a tremendous waste of memory. You really want the ability to use the delegates as if they were members of the class held in the Couple, but only by having the delegate instantiated on demand. This is a perfect use for properties. See Delegates3.sln To implement the delegates as static properties, modify the declaration of the delegates in both Employee and GamePiece. For example, within Employee, the two delegate declarations become:
public static Couple.Reverse EmployeeByID { get { return new Couple.Reverse(SecondIDLower); } }

public static Couple.Reverse EmployeeByAlpha { get { return new Couple.Reverse(SecondAlphaLower); } }

Each of these properties is read-only; there is a get, but no set accessor. The get returns a new instance of the delegate with the appropriate method encapsulated. Unlike static members, the static property does not create the delegate until the get accessor is invoked. By making the property static, the test class does not need an instance of Employee to invoke the delegate, it can refer to the delegate through the class:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-13

Delegates
employees.SetOrder(Employee.EmployeeByID);

This provides the same syntax as the static member, with the appropriate encapsulation within Employee. This time, however, rather than instantiating the delegate when the Employee class is created, the delegate is created only when it is requested. The GamePiece can also be modified to use properties, rather than a static member:
public static Couple.Reverse GamePieceByStronger { get { return new Couple.Reverse(LeftIsStronger); } }

The test program is unchanged from the example using static members:
public class Test { public void Run() { Employee joe = new Employee("joe",25); Employee fred = new Employee("fred",35); Couple employees = new Couple(joe, fred); employees.Display(); employees.SetOrder(Employee.EmployeeByID); Console.WriteLine("after sort by id..."); employees.Display();

employees.SetOrder(Employee.EmployeeByAlpha); Console.WriteLine("After sort by alpha..."); employees.Display();

GamePiece general = new GamePiece("Marshall",10); GamePiece scout = new GamePiece("Scout",2);

13-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Delegates as Properties
Couple gamePieces = new Couple(scout,general); gamePieces.Display(); gamePieces.SetOrder(GamePiece.GamePieceByStronger); Console.WriteLine("after sort pieces by values..."); gamePieces.Display();

public static void Main() { Test t = new Test(); t.Run(); } }

Most important, the output is unchanged, as shown in Figure 3.

Figure 3. Using static properties.

Using Non-Static Properties


See Delegates4.sln You can modify the delegates to use non-static properties. For example, you can rewrite EmployeeByID to be non-static just by removing the static keyword.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-15

Delegates
public { get { return new Couple.Reverse(SecondIDLower); } } Couple.Reverse EmployeeByID

This will continue to work, but youll need to refer to the delegate through an instance rather than through the class:
employees.SetOrder(joe.EmployeeByID);

While this code will compile and run, it is not a good design. You are calling a delegate on an instance, but semantically, the instance should not own the delegate, the class should. The delegate was made a static property to allow the client to refer to the delegate through the class, rather than through an instance:
employees.SetOrder(Employee.EmployeeByID);

13-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

MultiCast Delegates

MultiCast Delegates
One usage of delegates is to support the Publish and Subscribe design pattern. The idea is that one class in your code may publish an action or event and other classes may subscribe to that event. For example, a clock might publish a second change event, saying I will notify anyone who is interested that a second (or minute or hour) has passed. Other classes may subscribe to that event so that they can be notified each time the interval has passed. Multicast delegates can be used to call a series of methods all at the same time. You encapsulate all the methods in the one multicast delegate and when you invoke the methods through the delegate each participating method is called. Whenever you create a delegate that returns void, C# actually creates an object of type Multicast delegate for you. All multicast delegates must return void. If you think about this for a while, it makes a lot of sense. Since you will be invoking multiple methods, there would be no way for each of the invoked methods to return a value; the values would be lost. In the next example, you will create a Delegate class whose entire purpose is to publish a MultiCast delegate, StringDelegate and to provide a method, Display that invokes that delegate. You will treat StringDelegate as a multicast delegate and you will encapsulate a number of matching methods with that one delegate. See Delegates5.sln To get started, create a new console application and crate the following class:
public class DelegateClass { public delegate void StringDelegate(string s);

public void Display(StringDelegate d, string s) { d(s); } }

The first line of code in this class creates the StringDelegate that encapsulates a method that returns void and takes a single string parameter. The Display method takes two parameters: an instance of the delegate and a string to display. It then invokes the delegated method, passing in its own second parameter (s) as the argument to the invoked method.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-17

Delegates
To see how this delegate can work as a multicast delegate, youll need to create a class with multiple methods that can be encapsulated by this delegate. Youll create a simple Employee class, giving that class two member variables. The first will hold the employees ID, the second will hold the employees name,
public class Employee { private int empID; private string name;

The constructor for the Employee class will initialize these values:
public Employee(string name, int ID) { this.name = name; empID = ID; }

Youll override ToString() so that the Employee class can display the Employees name and ID:
public override string ToString() { return name + ", employee ID: " + empID; }

With the preliminaries taken care of, you are ready to give the Employee class methods that can be encapsulated by the delegate. The first method is WriteString. This simple method does nothing but write the string provided to the console:
public static void WriteString(string s) { Console.WriteLine("Writing string {0}", s); }

13-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

MultiCast Delegates
The next method that can be encapsulated by the delegate is StoreString. In a real application this would write the string to the database. To simplify this example, youll just have it write a message to the console:
public static void StoreString(string s) { Console.WriteLine("Storing string {0}", s); }

Finally, youll add a method named LogString. This method writes the string to a special log file, but again youll simplify the program by having it write to the console:
public static void LogString(string s) { Console.WriteLine("Logging string {0}", s); }

That completes the Employee class. You are ready now to test the multicast delegate, which you will do in the test class. Following the idiom used throughout this course, the Test class will have a Static Main() method that will instantiate an instance of Test and call the Run method. In the Run method, you will create a new Employee object, joe, passing in a string for his name and an int to represent his employeeID:
public class Test { public void Run() { Employee joe = new Employee("joe",25);

You will then declare three variables of the type StringDelegate:


DelegateClass.StringDelegate Writer, Logger, Storer;

Notice that Writer, Logger, and Storer are all declared to be of type DelegateClass.StringDelegatethat is to be of the type of delegate declared within the DelegateClass. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-19

Delegates
Once declared, each of the delegate objects must be instantiated, passing in the appropriate method. The method passed in must meet the signature of the delegate; that is it must return void and take a single string as a parameter.
Writer = new DelegateClass.StringDelegate( Employee.WriteString); Logger = new DelegateClass.StringDelegate( Employee.LogString); Storer = new DelegateClass.StringDelegate( Employee.StoreString);

You are now ready to invoke the methods through the delegate. To do so, you must instantiate an object of type DelegateClass, so that you can call the instance method Display.
DelegateClass theDelegateClass = new DelegateClass();

With that instance, you can call the Display method, passing in each of the delegates in turn:
Console.WriteLine("Calling Writer delegate..."); theDelegateClass.Display(Writer,joe.ToString());

Console.WriteLine("Calling Logger delegate..."); theDelegateClass.Display(Logger,joe.ToString());

Console.WriteLine("Calling Storer delegate..."); theDelegateClass.Display(Storer,joe.ToString());

Notice that until now you have used only simple delegates, each call to Display invokes only a single method. You will now create a new instance of StringDelegate, named multiCast. NOTE There is nothing magical in this name, youll call it multiCast simply to distinguish it; it will work just as well if you name it Fred.

DelegateClass.StringDelegate multiCast;

13-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

MultiCast Delegates
The new delegate, multiCast is an instance of StringDelegate just like all the others. What makes it special is how youll use it. Youll begin by assigning to it the Writer and the Logger delegates. In C# you create multicast delegates by using the overloaded + operator.
multiCast = Writer+Logger;

You can add additional delegates using the += operator:


multiCast += Storer;

When you invoke the Display method, passing in the new delegate and a string, each of the delegated methods are invoked. That is, the methods encapsulated by Writer, Logger, and Storer will each be invoked in turn.
Console.WriteLine( "\nCalling all three through multicast..."); theDelegateClass.Display(multiCast,joe.ToString());

You can then remove one delegate from the multicast delegate using the -= operator, and call again, this time invoking only the remaining two methods:
Console.WriteLine( "\nRemoving Logger and invoking again..."); multiCast -= Logger; theDelegateClass.Display( multiCast,joe.ToString());

The results of running this program are shown in Figure 4. You can see that the first invocation of each of the delegates prints the expected message. When the multicast delegate is invoked the first time, all three delegated methods are called. You then removed one delegate and called the multicast delegate again, causing just two methods to be called.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-21

Delegates

Figure 4. MultiCast delegates.

Study the output carefully, you want to be sure that it matches your expectations based on the code shown earlier.

13-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

MultiCast Delegates

Summary
Delegates are reference types that encapsulate a method with a specific signature and return type. You will use delegates when you have a number of similar methods that can be invoked at run time, but you cant know at compile time which method to invoke. Delegates are instrumental in callbacks. Multicast delegates can invoke multiple methods with a single call. Multicast delegates support publish/subscribe. Multicast delegates must return void.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-23

Delegates

(Review questions and answers on the following pages.)

13-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

MultiCast Delegates

Questions
1. Name two places you might use a delegate? 2. What is the advantage of making a delegate a static member of the class rather than creating the delegate in the invoking class? 3. What is the advantage of making a delegate a property rather than a static member of a class? 4. Why make the property static? Why not use an instance property? 5. Can multicast delegates have any signature and return type? 6. What is the advantage of using a multicast delegate?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-25

Delegates

Answers
1. Name two places you might use a delegate?
Delegates are useful in callback methods and whenever you need to invoke a method at run time, but you cant know at compile time which method youll invoke. Methods are also useful with events.

2. What is the advantage of making a delegate a static member of the class rather than creating the delegate in the invoking class?
The goal is to encapsulate knowledge of which method will be invoked within the appropriate class. The invoking test class should not need to know which method within the called class will handle the delegated functionality.

3. What is the advantage of making a delegate a property rather than a static member of a class?
Static delegates are instantiated whether or not they are invoked. By making the delegate a property, you instantiate the delegate only when and if you invoke it.

4. Why make the property static? Why not use an instance property?
The semantics of the delegate are that they belong to the class rather than to the instance. The delegated functionality is understood to be a capability of the entire class, like any method is, rather than of a specific instance of that class.

5. Can multicast delegates have any signature and return type?


Multicast delegates must return void.

6. What is the advantage of using a multicast delegate?


Multicast delegates allow you to invoke multiple methods with a single method call. Youll see the power of multicast methods when events are considered.

13-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

MultiCast Delegates

Lab 13: Delegates

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-27

Lab 13: Delegates

Lab 13 Overview
In this lab youll learn to work with delegates and to implement delegates as properties. Youll also work with multicast delegates. To complete this lab, youll need to work through two exercises: Invoking Methods with Delegates Creating Multicast Delegates

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

13-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Invoking Methods with Delegates

Invoking Methods with Delegates


Objective
In this exercise, youll create a class called SetOfTwo that holds any two objects. This class declares a delegate named Reverse to reverse the order of the objects in the collection. You will implement two classes whose objects may be held in a SetOfTwo. The first is a Cat class. You may sort cats by name or by age. The second is a Book class. You may sort books by ISBN. You will delegate to the sortable classes (i.e., Cat and Book) the responsibility for implementing the delegated methods. You will also delegate to the class the responsibility for instantiating the delegate, which they will implement as a property.

Things to Consider
The class defining the collection defines the signature of the delegate. The class implementing the delegate defines the semantics of the delegated method. So, the Cat class defines what it means to sort Cat objects. The implementing class can provide the delegate as a property, so that the client class does not need to know the semantics of sorting (e.g., a Cat).

Step-by-Step Instructions
1. Open Delegates Started.sln in the Delegates Started folder. 2. Implement the collection class SetOfTwo.
public class SetOfTwo {

3. Define a delegate, Reverse that takes two objects as parameters and returns a Boolean value.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-29

Lab 13: Delegates


public delegate bool Reverse(object lhs, object rhs);

4. Implement the underlying data store as an array of two objects.


object[] theSet = new object[2];

5. Implement a constructor.
public SetOfTwo(object first, object second) { theSet[0] = first; theSet[1] = second; }

6. Define a method, SetOrder, that takes the Reverse delegate as a parameter.


public void SetOrder(Reverse theDelegatedFunction) {

7. Within SetOrder invoke the delegated function, passing in the first and second member of the underlying array. If you get back a true value, reverse the order of the members of the array.
if (theDelegatedFunction(theSet[0],theSet[1])) { object temp = theSet[0]; theSet[0] = theSet[1]; theSet[1] = temp; }

8. Define a Display method that displays the two members of the array by calling their ToString methods.

13-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Invoking Methods with Delegates


public void Display() { Console.WriteLine( "theSet[0]:{0}",theSet[0].ToString()); Console.WriteLine( "theSet[1]:{0}",theSet[1].ToString()); }

9. Define a Cat class to store in a SetOfTwo collection.


public class Cat {

10. Give the Cat class an age (int) and a name (string).
private int age; private string name;

11. Implement the constructor.


public Cat(string name, int age) { this.name = name; this.age = age; }

12. Define a property to return an instance of the delegate that will invoke a method called SecondYounger (implemented below). Call the property CatByAge.
public static SetOfTwo.Reverse CatByAge { get { return new SetOfTwo.Reverse(SecondYounger); } }

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-31

Lab 13: Delegates


13. Define a property to return an instance of the delegate that will invoke a method called SecondNameFirst (implemented below). Call the property CatByName.
public static SetOfTwo.Reverse CatByName { get { return new SetOfTwo.Reverse(SecondNameFirst); } }

14. Implement the SecondYounger method to match the delegate. Return true if the second cat is younger than the first.
public static bool SecondYounger(object firstCat, object secondCat) { Cat first = (Cat) firstCat; Cat second = (Cat) secondCat; return first.age > second.age; }

15. Implement SecondNameFirst to match the delegate. Return true if the second name comes first alphabetically.
public static bool SecondNameFirst(object firstCat, object secondCat) { Cat first = (Cat) firstCat; Cat second = (Cat) secondCat; return (string.Compare(first.name, second.name))>0; }

16. Override ToString to return the cats name and age.

13-32

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Invoking Methods with Delegates


public override string ToString() { return name + ": " + age.ToString(); }

17. Implement a Book class to store in the SetOfTwo.


public class Book {

18. Give the Book two members that are both strings: an ISBN and a name.
private string isbn; private string name;

19. Implement the Book constructor.


public Book(string name, string isbn) { this.name = name; this.isbn = isbn; }

20. Define a property to return an instance of the delegate that will invoke a method called RightComesFirst (implemented below). Call the property BookByISBN.
public static SetOfTwo.Reverse BookByISBN { get { return new SetOfTwo.Reverse(RightComesFirst); } }

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-33

Lab 13: Delegates


21. Implement the RightComesFirst method to match the delegate. Return true if the second books ISBN comes before the first books ISBN alphabetically.
public static bool RightComesFirst(object left, object right) { Book lhs = (Book) left; Book rhs = (Book) right; return (string.Compare(rhs.isbn, lhs.isbn))>0; }

22. Instantiate two Cats, frisky and boots with different ages.
Cat frisky = new Cat("frisky",2); Cat boots = new Cat("boots",3);

23. Create an instance of SetOfTwo named kitties. Pass in your two objects.
SetOfTwo kitties = new SetOfTwo(frisky, boots);

24. Display the two objects.


kitties.Display();

25. Invoke SetOrder on the collection, passing in the delegate to sort the cats by age.
kitties.SetOrder(Cat.CatByAge);

26. Display the sorted collection.


kitties.Display();

27. Invoke SetOrder on the collection, passing in the delegate to sort the cats by Name. 13-34 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Invoking Methods with Delegates


kitties.SetOrder(Cat.CatByName);

28. Display the sorted collection.


kitties.Display();

29. Create a SetOfTwo collection with the two books.


SetOfTwo books = new SetOfTwo(progCS,progASPNET);\

30. Display the collection.


books.Display();

31. Call SetOrder, passing in the delegate to sort the books by ISBN.
books.SetOrder(Book.BookByISBN);

32. Display the sorted books.


books.Display();

33. Compile and run the application as shown in Figure 5.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-35

Lab 13: Delegates

Figure 5. Final exercise results.

13-36

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Multicast Delegates

Creating Multicast Delegates


Objective
In this exercise, youll create a multicast delegate and add and subtract delegated methods from the multicast.

Things to Consider
Multicast delegates can call multiple delegated methods in sequence. You may add to and subtract methods from the multicast delegate. Any delegate that is defined to return void may be a multicast delegate.

Step-by-Step Instructions
1. Open Multicast Started.sln in the Multicast Started folder. 2. Create a class named ClassWithDelegate.
public class ClassWithDelegate {

3. Define a Delegate that takes a string and returns void.


public delegate void StringDelegate(string s);

4. Define a method, Display, that takes a delegate and a string and invokes the delegate, passing in the string.
public void Display(StringDelegate d, string s) { d(s); }

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-37

Lab 13: Delegates


5. Implement a Cat class.
public class Cat { private int age; private string name;

6. Implement the constructor.


public Cat(string name, int age) { this.name = name; this.age = age; }

7. Override ToString to show the name and age.


public override string ToString() { return name + ", Cat age: " + age; }

8. Implement a method, StoreString, which can be used with the delegate.


public static void StoreString(string s) { Console.WriteLine("Storing string {0}", s); }

9. Implement a method, LogString, which can be used with the delegate.


public static void LogString(string s) { Console.WriteLine("Logging string {0}", s); }

13-38

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Multicast Delegates


10. Create an instance of Cat frisky.
Cat frisky = new Cat("frisky",5);

11. Create three instances of the delegate: Writer, Logger, and Storer.
ClassWithDelegate.StringDelegate Writer, Logger, Storer;

12. Instantiate the three Delegates with the three implementing methods.
Writer = new ClassWithDelegate.StringDelegate( Cat.WriteString); Logger = new ClassWithDelegate.StringDelegate( Cat.LogString); Storer = new ClassWithDelegate.StringDelegate( Cat.StoreString);

13. Create a new instance of ClassWithDelegate called theDelegateClass.


ClassWithDelegate theDelegateClass = new ClassWithDelegate();

14. Output the progress to the console and then invoke the Display method with Writer.
Console.WriteLine("Calling Writer delegate..."); theDelegateClass.Display(Writer,frisky.ToString());

15. Output the progress to the console and then invoke the Display method with Logger.
Console.WriteLine("Calling Logger delegate..."); theDelegateClass.Display(Logger,frisky.ToString());

16. Output the progress to the console and then invoke the Display method with Storer. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-39

Lab 13: Delegates


Console.WriteLine("Calling Storer delegate..."); theDelegateClass.Display(Storer,frisky.ToString());

17. Create another instance of the Delegate named multiCast.


ClassWithDelegate.StringDelegate multiCast;

18. Initialize multiCast with Writer and Logger.


multiCast = Writer+Logger;

19. Output the progress to the console and then invoke the multicast delegate.
Console.WriteLine("\nCalling multicast..."); theDelegateClass.Display(multiCast,frisky.ToString());

20. Add Storer to multiCast.


multiCast += Storer;

21. Output the progress to the console and then invoke the multicast delegate.
Console.WriteLine("\nCalling multicast..."); theDelegateClass.Display(multiCast,frisky.ToString());

22. Output the progress to the console and then remove the Logger delegate.
Console.WriteLine( "\nRemoving Logger and invoking again..."); multiCast -= Logger; theDelegateClass.Display( multiCast,frisky.ToString());

23. Compile and run the application as shown in Figure 6.

13-40

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Multicast Delegates

Figure 6. Final exercise results.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

13-41

Lab 13: Delegates

13-42

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Events

Events
Objectives
Understand what events are. See how events are declared. Discover how events are raised. See how events are handled.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

14-1

Events

Defining Events
An event is your classs way of signaling that something has happened. Typically, an event represents a change in state for your class. Events may be raised when buttons are pressed, text box contents change, items in lists are selected and so forth. The designer of each class decides what events, if any, that class will raise, and under what conditions. The purpose of an event is to notify other interested classes that your class has changed in some specific way. For example, the page may want to know when the Submit button is clicked, a list box may want to know when a text box has been modified, or a display class may want to know when a network read completes. In C# you define an event with the keyword event. The event keyword allows you to specify a delegate that will be used to invoke one or more methods when the event is fired. Most events are UI actions (button clicks, list changes), but there are other system events that you may want to respond to as well. For example, a clock object might raise an event each time the second changes. A stream object might raise an event when the data has been received from the disk or across the network. Events work together with delegates to support the publish and subscribe design pattern. For example, a button might publish the Click event as shown in Figure 1.

Figure 1. A button publishes the Click event.

Other windows might subscribe to that event as shown in Figure 2.

14-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Defining Events

Figure 2. Windows subscribe to the event.

When the button is clicked, all the subscribers are notified as shown in Figure 3.

Figure 3. Button notifies the subscribers.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

14-3

Events

Advantages of Event-Driven Programming


In the early days of computing the user interacted with the computer in a very proscribed way. The user worked his way through each screen, filling in data and working through a series of menus and forms. Todays Graphical User Interface (GUI) based programs are far more interactive. The user may, at any time, press a button, interact with a list or other complicated control, or choose from a variety of menus. This interactive programming is known as eventdriven. The user performs actions that raise events that the program responds to. The support for events and delegates in C# allows the programmer to manage the complexities that arise out of event-driven programming in a highly objectoriented and component-oriented way. Each individual object publishes the events it raises, and any other class may choose to subscribe to those events or to ignore them.

14-4

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing Events

Implementing Events
In C#, events are implemented using multicast delegates. You will remember that multicast delegates must return void. By convention, events in .NET are encapsulated by multicast delegates that take two parameters. The first, of type object represents the object raising the event. The second, of type EventArgs (or a class deriving from EventArgs) contains data that may be useful to the class handling the event.

Programming Events: An Example


To understand how to program an event, you will create an office class that fires an event each time its list of Employees changes. An office will contain information about a business office, including a list of their Employees. The office will also, of course, contain other information, such as the address, phone number, and so forth.

Objective
The office will publish an event each time an employee is hired or fired; that is, each time the collection of employees changes. Your office class will publish an event OfficeChanged, as shown in Figure 4.

Figure 4. Publishing OfficeChanged.

Other classes may subscribe to the event. In this program, youll create a class Tester that subscribes to the OfficeChanged event as shown in Figure 5.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

14-5

Events

Figure 5. Tester subscribes to the OfficeChanged event.

When the event fires, Tester is, as shown in Figure 6. On notification, the Tester prints the number of employees currently in the office.

Figure 6. Tester is notified of the event.

14-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing Events

The Code in Detail


See Events.sln The office class holds a collection of all the Employees. Youll begin by creating a simple class to represent the Employee:
// Employee class to hold in office collection public class Employee { string name; string empID; // employees id

// constructor public Employee(string name, string ID) { this.name = name; empID = ID; }

// properties (get only) public string EmpID { get { return empID; } }

public string Name { get { return name; } } }

This simple class has two private member variables, name and empID, both of which can be accessed through Properties (Name and EmpID, respectively). This class exists only to allow objects to be stored in the Office. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

14-7

Events
The Office class itself could have private members representing the address, city, phone, and so forth. Youll simplify the class into two members. The first will be an array of Employee objects. Again, in a commercial application you might store this information in a database, but to keep things simple here youll use an Array:
public class Office { // backing store for list of employees private Employee[] employees;

The only other private member is count, an integer member variable that you will initialize to zero. This counter will be incremented when new members are added to the array.
// highest index in employees array private int count = 0;

Declaring the Delegate


The Office class declares a multicast delegate, OfficeChangedHandler. Like all multicast delegates it must return void. The convention is for delegates that will be used by events to take two parameters. The first is of type object, and the second is of type EventArgs or a class that derives from OfficeEventArgs.
// delegate to create event with public delegate void OfficeChangedHandler( object sender, OfficeEventArgs e);

Declaring the EventArgs Class


The class OfficeEventArgs is designed by you, specifically to handle the OfficeChanged event. This little helper class is defined in the same file as the Office class:

14-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing Events
// Event arguments for office events public class OfficeEventArgs : EventArgs { public int count; public OfficeEventArgs(int count) { this.count = count; } }

The constructor to the OfficeEventArgs class takes a single int parameter, count, and uses that parameter to initialize its only member variable, count. When you construct an instance of OfficeEventArgs you pass in the current count of the number of employees in the office, and this value is made available to any interested classes (e.g., any classes that subscribe to the event).

Declaring the Event


Returning to the Office class, the next line of code after the declaration of the delegate is the declaration of the event itself:
// event officeChanged raised when an employee // is added or removed public event OfficeChangedHandler OfficeChanged;

The event name is OfficeChanged. The delegate for the OfficeChanged event is the OfficeChangedHandler delegate previously defined and described above. When the OfficeChanged event is fired, the methods encapsulated by the OfficeChangedHandler multicast delegate are invoked.

Firing the Event


When the class detects that the event should be raised (e.g., when an Employee is added or modified), the method adding the Employee calls the private helper method NotifyOfficeChanged, passing in an instance of OfficeEventArgs. For example, the indexer has both a get and a set part. The set part is changing the office, and so must raise the event. It does so by instantiating an object of type OfficeEventArgs, passing in the new count to the constructor. It then uses that object as a parameter to the NotifyOfficeChanged method, which is responsible for raising the event.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

14-9

Events
public Employee this[int index] { get { if (index < 0 || index >= employees.Length) { // handle bad index } return employees[index]; } set { if (index >= employees.Length) { // handle error } else { employees[index] = value; if (count < index) count = index; // modify the office

// the set method has been called // the office is changing // create an officeEventArgs object // and call the method to raise // the event OfficeEventArgs e = new OfficeEventArgs(count); NotifyOfficeChanged(e); } } }

The method NotifyOfficeChanged checks to see if any classes have registered delegates of type OfficeChangeHandler with the event. It does this by testing whether the event is null. The event will return null if there are no registered delegates, and it will return non-null if anyone wants to be notified.

14-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing Events
If the event is not null, the methods are invoked by raising the event and passing in the two parameters: the sender (in this case the office itself) and the OfficeEventArgs object it received as a parameter. It sends a reference to the office itself by using the this keyword:
// raise the event protected virtual void NotifyOfficeChanged( OfficeEventArgs e) { if (OfficeChanged != null) OfficeChanged(this,e); }

Handling the Event


To see the event at work, you need a class that wishes to be notified when the office has changed. Youll create a very simple class, OfficeWatcher that will register an event handler in its constructor:
// class to watch for office // change events public class OfficeWatcher { // constructor: register an event handler public OfficeWatcher(Office office) { // add a delegate to the office's event // pass in a reference to my method to // call when the event fires office.OfficeChanged += new Office.OfficeChangedHandler(OnOfficeChanged); }

The event is registered using the += operator, which you will remember is used by multicast delegates to add a new delegate to its notification list. The argument to the delegate constructor created with the keyword new is OnOfficeChanged, which is a method of OfficeWatcher:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

14-11

Events
public void OnOfficeChanged( object sender, OfficeEventArgs e) { Console.WriteLine( "Office changed. Count now {0}",e.count); }

This simple test method just updates the UI with the current count of the employees in the office, but you can imagine that a real application might take other action based on the event.

Testing the Event


To test the event handler, youll create a Tester class, and in its instance method, Run, youll create an array of employees, and use these to instantiate an office object.
public class Tester {

static void Main() { // Make an instance and call Run Tester t = new Tester(); t.Run(); }

// instance method for testing public void Run() { // create a temporary // array of employees Employee[] empArray = new Employee[5];

14-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing Events
// fill the array with // Employee objects empArray[0] = new Employee("John Galt","001"); empArray[1] = new Employee("Figaro Barbara", "002"); empArray[2] = new Employee("Wotan Warrior", "003"); empArray[3] = new Employee("George Washington", "004"); empArray[4] = new Employee("John Adams", "005");

// create the office object // passing in the array of Employees Office office = new Office(empArray);

You are ready to create an OfficeWatcher to watch that office. Pass in a reference to the office you just created as a parameter to the constructor for OfficeWatcher:
// create the officeWatcher, // pass in a reference to the office // you just created OfficeWatcher watcher = new OfficeWatcher(office);

The OfficeWatchers constructor will use that reference to register an event handler with the office it is watching. To test the notification, add two employees. The first is added by calling the Add method:
Console.WriteLine("Adding Sam Adams..."); office.Add(new Employee("Sam Adams", "008"));

The Add method of Office delegates responsibility for adding the new Employee to the indexer. The indexer raises the event.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

14-13

Events
// add a single Employee to the office public void Add(Employee employee) { // no notification, will be done by this[]. this[count] = employee; }

This addition of Sam Adams should raise the event. The test program then goes on to call the indexer directly:
Console.WriteLine("Adding Thomas Jefferson..."); office[6] = new Employee("Thomas Jefferson", "007");

Finally, the test program displays the current contents of the array:
Console.WriteLine("\nDisplaying office employees..."); office.DisplayEmployees();

The results of running this are displayed in the console window, as shown in Figure 7. You can see that after each Employee is added, the OnOfficeChanged method is called.

Figure 7. Testing the Event Handler.

14-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing Events

Stepping Through the Program


To see how the event is raised, run the program in the debugger. Put a breakpoint at the point in the program where the OfficeWatcher class is created, as shown in Figure 8.

Figure 8. A breakpoint at the instantiation of OfficeWatcher.

You can create the breakpoint by clicking on the line and pressing CTRL+B. This brings up the New Breakpoint dialog box as shown in Figure 9. This window allows you to set the conditions on which this breakpoint will fire, or the number of times the line must be reached before the breakpoint fires (Hit Count). A simpler way to set the breakpoint is to put the cursor in the margin to the far left, and click. This adds an unconditional breakpoint. You can remove the breakpoint by clicking on the red dot.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

14-15

Events

Figure 9. The New Breakpoint dialog box.

Run the program to the Breakpoint. You can do that by pressing F5 or by choosing Debug->Start from the menu. The program will run to the breakpoint, and then stop. The current line is indicated by a yellow arrow, which will be superimposed over the red dot, as shown in Figure 10.

Figure 10. Hitting the breakpoint.

You can step-in to the constructor, by pressing F11. In the constructor you can see the registration of the event, as you expect. Continuing to step will bring you back to the test program where you print the string Adding Sam Adams to the console, and then you step into the Add method of office. Stepping in brings you into the constructor for the Employee you are adding (resulting from calling new Employee). You then step into the Add method of the Office class, which calls the indexer.

14-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing Events
As you continue to step through, youll see the construction of the OfficeEventArgs object, passing in the current count as a parameter, as shown in Figure 11.

Figure 11. Creating the OfficeEventArgs object.

This invokes the constructor where the local member count is set to the value in Office.Count (5), as shown in Figure 12.

Figure 12. The OfficeEventArgs constructor.

Continue to step through the office indexer. On the final line you call the helper method NotifyOfficeChanged, and you pass in the OfficeEventArgs object just created. If you look at the Locals window you will find e (the OfficeEventArgs object), which you can open (click on the + sign). Youll see that it consists of two parts: the parent object (of type System.EventArgs) and the count member, whose value is 5. This is circled and highlighted in Figure 13 and expanded in Figure 14.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

14-17

Events

Figure 13. Examining the OfficeEventArgs object.

Figure 14. Examining the OfficeEventArgs object (close up).

Step into the NotifyOfficeChanged method. Here you test whether the event is not null. Since the OfficeWatcher has registered a delegate, this test will succeed, and the method will be invoked. To see this, set a breakpoint on the OfficeWatcher.OnOfficeChanged method. Stepping through the notification will bring you to your new breakpoint; the method has been called as a result of the event firing. This is where the count is displayed to the console, using the value placed into the OfficeEventArgs object by the office.

14-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing Events

Summary
Events allow your objects to notify interested other classes about changes in their state. Events are implemented using multicast delegates. An event evaluates to null if no delegates are registered. When the event fires, all the registered delegates are called. By convention delegates for events take two parameters: an object of type object, and an object of type EventArgs (or a class derived from EventArgs). The classes that register with the event provide an event handler to respond to the event.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

14-19

Events

(Review questions and answers on the following pages.)

14-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing Events

Questions
1. What are events used for? 2. What is the relationship between events and delegates? 3. What is the customary signature of event delegates? 4. Can more than one method be invoked for a given event? 5. Can a class have more than one event? 6. Why would you derive from EventArgs?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

14-21

Events

Answers
1. What are events used for?
Events signal a change in the state of a class. They are typically used to inform other classes that something has happened, e.g., a button click or a selection in a list box.

2. What is the relationship between events and delegates?


Events are implemented using multicast delegates. The delegate manages the notification of the interested methods when the event fires.

3. What is the customary signature of event delegates?


In .NET, events return void (as they must since they use multicast delegates) and by convention they take two arguments: an object and an EventArgs object.

4. Can more than one method be invoked for a given event?


Yes, it is common for methods of many different classes to register with a given event.

5. Can a class have more than one event?


Yes, many objects have more than one event. For example, a User Interface control might have events for adding data, removing data, resizing the control, drawing the control, and so forth.

6. Why would you derive from EventArgs?


You provide a class derived from EventArgs when you need to offer specialized data or methods that are not supplied by the base class. In the example shown in this chapter, you derive from EventArgs to provide the current count of the offices array of Employee objects.

14-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Implementing Events

Lab 14: Responding to Events

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

14-23

Lab 14: Responding to Events

Lab 14 Overview
In this lab youll learn to register and respond to events. To complete this lab, youll need to work through two exercises: Responding to Events Extending Custom Event Handlers

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

14-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Responding to Events

Responding to Events
Objective
In this exercise, youll create and register events and respond to events.

Things to Consider
Classes publish events so that interested observers can respond. The class responding to the event uses a delegate to determine which method is invoked when the event is raised. You can specialize the information provided when the event is raised by deriving from System.EventArgs.

Step-by-Step Instructions
1. Open Events Starter.sln in the Events Starter folder. 2. Create a Cat class. Give it two member fields: name (a string) and age (an int).
public class Cat { string name; int age;

3. Implement the Cat constructor.


public Cat(string name, int age) { this.name = name; this.age = age; }

4. Implement properties for Age and Name. C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

14-25

Lab 14: Responding to Events


public int Age { get { return age; } }

public string Name { get { return name; } }

5. Create class Kennel to hold a collection of Cat objects.


public class Kennel {

6. Store the Cat objects in an Array called kitties.


private Cat[] kitties;

7. Declare a variable to hold the count of objects.


private int count = 0;

8. Declare a delegate for an event handler method (returns void, takes two arguments: an object and a KennelEventsArgs object).
public delegate void KennelChangedHandler( object sender, KennelEventArgs e);

9. Declare an event based on the delegate. 14-26 C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Responding to Events
public event KennelChangedHandler kennelChanged;

10. Define a method that raises the event if a handler is registered.


protected virtual void NotifyKennelChanged( KennelEventArgs e) { if (kennelChanged != null) kennelChanged(this,e); }

11. Define a Kennel constructor that takes a variable number of Cat objects.
public Kennel(params Cat[] catList) {

12. Allocate space for the Cats in the array.


this.kitties = new Cat[256];

13. Copy the Cats passed in to the constructor into the array.
foreach (Cat c in catList) { this.kitties[count++] = c; }

14. Define a method to add a single Cat to the Kennel. No notification, will be done by this[].
public void Add(Cat kitty) { this[count] = kitty; }

15. Define an int based indexer if an item is added to the array to raise the event. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

14-27

Lab 14: Responding to Events


public Cat this[int index] { get { if (index < 0 || index >= kitties.Length) { // handle bad index } return kitties[index]; } set { if (index >= kitties.Length) { // handle error } else { kitties[index] = value; if (count < index) count = index;

KennelEventArgs e = new KennelEventArgs(count); NotifyKennelChanged(e);

} } }

16. Define a method that iterates through the collection, displaying each Cat in turn.

14-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Responding to Events
public void DisplayKitties() { foreach (Cat c in kitties) { if (c != null) { Console.WriteLine("{0} is {1} years old", c.Name, c.Age); } } }

17. Create the KennelEventArgs class (what class must it derive from?). The class has a single member variable: count (an int).
public class KennelEventArgs : EventArgs { public int count;

18. Implement the KennelEventArgs constructor.


public KennelEventArgs(int count) { this.count = count; }

19. Create the KennelWatcher class to watch for the Kennel change event.
public class KennelWatcher {

20. Implement the KennelWatcher constructor. It takes one parameter: an object of type Kennel.
public KennelWatcher(Kennel kennel) {

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

14-29

Lab 14: Responding to Events


21. Add a delegate to the Kennels event. Pass in a reference to the KennelWatcher method to call when the event fires.
kennel.kennelChanged += new Kennel.KennelChangedHandler(OnKennelChanged);

22. Implement the method to wrap in the delegate.


public void OnKennelChanged( object sender, KennelEventArgs e) { Console.WriteLine( "Kennel changed. Count now {0}",e.count); }

23. Create a temporary array of Cat objects called kittyArray.


Cat[] kittyArray = new Cat[5];

24. Fill the array with Cat objects.


kittyArray[0] = new Cat("Frisky",3); kittyArray[1] = new Cat("Figaro", 2); kittyArray[2] = new Cat("Wotan", 4); kittyArray[3] = new Cat("Washington", 6); kittyArray[4] = new Cat("Boots", 5);

25. Create the Kennel object passing in the array of Cats.

14-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Responding to Events
Kennel kitties = new Kennel(kittyArray);

26. Create the KennelWatcher object, passing in a reference to the Kennel you just created.
KennelWatcher watcher = new KennelWatcher(kitties);

27. Add Cats to the Kennel object, writing the progress to the console.
Console.WriteLine("Adding Boots..."); kitties.Add(new Cat("Boots", 8));

Console.WriteLine("Adding Kant..."); kitties[6] = new Cat("Kant", 7);

Console.WriteLine("\nDisplaying kitties..."); kitties.DisplayKitties();

28. Compile and run the program as shown in Figure 15.

Figure 15. Final exercise results.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

14-31

Lab 14: Responding to Events

Extending Custom Event Handlers


Objective
In this exercise, youll create a second observer of the KennelChanged event.

Things to Consider
More than one class may be interested in the event. Classes that respond to an event should not know about one another. The class raising the event does not know which classes are interested.

Step-by-Step Instructions
1. Open Events Extended Starter.sln in the Events Extended Starter folder. 2. Add a second class, KennelAuditer, to subscribe to the event.
public class KennelAuditer { public KennelAuditer(Kennel kennel) {

3. Create a constructor for KennelAuditor that takes a Kennel object.


public KennelAuditer(Kennel kennel) {

4. Register to receive the event.


kennel.kennelChanged += new Kennel.KennelChangedHandler(OnAuditableEvent);

5. Implement the method to wrap in the delegate. 14-32 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Extending Custom Event Handlers


public void OnAuditableEvent( object sender, KennelEventArgs e) { Console.WriteLine( "Audit note: Kennel changed. Count now {0}",e.count); }

6. Add an instance of KennelAuditer.


KennelAuditer auditer = new KennelAuditer(kitties);

7. Compile and run the program as shown in Figure 16.

Figure 16. Final exercise results.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

14-33

Lab 14: Responding to Events

14-34

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Windows Forms

Windows Forms
Objectives
Create Windows applications using the command line compiler. Create Windows applications using Visual Studio .NET. Explore Windows controls and Windows Forms. Set properties on forms and controls. Create event handlers for Windows Forms. Use XML comments to generate documentation.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-1

Windows Forms

Creating Windows Applications


The entire reason to learn C# is to build applications. Otherwise, this is all an academic exercise in computer science. While C# may one day be a cross platform language, for the immediate future its principal (if not only) reason to exist is to support development on the .NET platform. .NET applications come in three flavors: Windows Applications, Web Applications and Web Services. This chapter will focus on Windows Applications. There was a time, not very long ago, when the distinction between a desktop application and a Web application was clear and unmistakable. Today, and increasingly, these distinctions are blurring. Many desktop applications integrate with the Web. For example, Microsofts Streets and Tips program uses the Web to update its list of construction delays, and Nortons Antivirus program is fully integrated with the Web to keep itself up to date with the latest virus cures. Web applications, on the other hand, are doing more and more work on the client, further reducing the distinction between Web and Windows applications. In the final analysis, the real distinction is this: who is responsible for the User Interface? If the UI is a browser showing HTML sent from a server, its called a Web application; otherwise its called a desktop application. In .NET, Windows desktop applications are built using Windows Forms. The goal of Windows Forms is to bring the Rapid Application Development environment made famous in Visual Basic 6 to C# and other .NET application languages. The model is quite simple: you create a form object that derives from System.Windows.Forms.Form. You then create controls, such as System.Windows.Forms.Label or System.Windows.Forms.Button. You set the text, location, and size of your controls and then set up event handlers. The event handlers are compiled in what is known as a code behind page. Finally, you add the controls youve created to your forms controls collection and you run the application, passing in the form to the applications Run method.

Writing Windows Apps by Hand


As a rule, you will write your Windows Applications in Visual Studio .NET. This integrated Development Environment (IDE) offers tremendous support for building windows applications quickly and painlessly. The forms builder alone will save you many hours of hand-coding your application.

15-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Windows Applications


That said, there is nothing magical about the IDE and you are certainly free to write your applications by hand, using nothing more than a simple text editor and the command line compiler.

Try It Out!
See HelloWorldText.cs To prove this to yourself, write your first Windows application in Notepad.

1. Open Notepad and create a new file. Save it as HelloWorld.cs. 2. Add two using statements to the top of the file to inform the compiler which namespaces youll be using in this application.
using System; using System.Windows.Forms;

These designations simply save you time. Rather than having to declare your Label object as
System.Windows.Forms.Label output

you can add the using System.Windows.Forms designation to the top of the file and then just write:
Label output

3. Create your form class (myForm), and derive from the System Form class:
public class myForm: Form {

4. Add two controls as private member fields. One is a Label, while the other is a Button.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-3

Windows Forms
// a label to display text private Label output;

// a cancel button private Button cancel;

5. Create the class constructor. In the constructor youll start by initializing your controls.
public myForm() { // create the objects output = new Label (); cancel = new Button ();

6. The title on the form is held as a member field Text; you can set that to something appropriate.
// set the form's title Text = "Hello World From Windows";

7. You are now ready to set the location, size, and text for the output label.
// set up the output label output.Location = new System.Drawing.Point (20, 30); output.Text = "Hello World From Windows!"; output.Size = new System.Drawing.Size (200, 25);

Because you are doing this by hand, you must set the Location by hand. You do that by creating a Point object, passing in the coordinates (20,30) for the object, and passing that Point object to the Location property of the control. Set the size of the control by creating a System.Drawing.Size object, passing in the size to the constructor. This size struct is then assigned to the Size property of the control. 8. Set the Location, Size, and Text of the button as well.

15-4

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Windows Applications


// set up the cancel button cancel.Location = new System.Drawing.Point (150,200);

cancel.Text = "&Cancel"; cancel.Size = new System.Drawing.Size (110, 30);

9. You must assign an event handler to the Click event of the button. This uses events and delegates as explained elsewhere in this course.
// set up the event handler cancel.Click += new System.EventHandler (this.OnCancelClick);

Youve told the compiler that Click events for the button will be handled by the OnCancelClick method, which you will write in just a moment. 10. Add the controls to the forms Controls collection.
// Add the controls Controls.Add (cancel); Controls.Add (output);

11. That completes the constructor. You now need only to write the OnCancelClick method that youve wired as the event handler for the button. When the user clicks the Cancel button you want to exit the application. You do this with the static Exit method of the Application class.
// handle the cancel event protected void OnCancelClick( object sender, System.EventArgs e) { Application.Exit(); }

By convention, all Windows events take two parameters. The first, of type object, represents the control sending the event (in this case the button). The second is an object of type EventArgs that provides additional information about the event. In the case shown here, neither parameter is used in the event handler. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-5

Windows Forms
12. All that is left is to start the application. Like all C# applications, this one begins with a static method Main. In this static method youll call the static Run method of the Application, passing in a new instance of the form youve just created.
// Run the app public static void Main() { Application.Run(new myForm()); }

Build the Application:


13. Save the file as HelloWorld.cs and close the file in Notepad. 14. To build the application navigate to the Visual Studio .NET program group and choose Visual Studio Tools. Within the Tools group choose Visual Studio .NET Command Prompt. This opens a Command window with the environment set properly for building .NET applications. 15. Navigate to the directory in which youve saved your file. Take a directory; you should see just HelloWorld.cs. 16. Enter csc HelloWorld.cs. The compiler prints a message and returns a command prompt. 17. Type dir to get a directory listing; you should now see HelloWorld.exe. 18. Type HelloWorld and the application opens, as shown in Figure 1. The application is quite simple; the label displays your text and the button is drawn waiting for you to click it. 19. Click the Cancel button and the application exits.

15-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Windows Applications

Figure 1. Running HelloWorld.

Writing Windows Apps with Visual Studio .NET


While that wasnt very difficult, large applications are quite tedious to craft by hand. Visual Studio .NET makes the task much easier, as youll see in the next exercise.

Try It Out!
See HelloWorldVS.sln To see how much easier it is to work with Visual Studio, youll write the same application again, this time using the IDE to drag the controls onto a form. 1. Open Visual Studio .NET and click on New Project. 2. Under Project Types, choose Visual C# Projects and under Templates choose Windows Application. Name the project HelloWorldVS, as shown in Figure 2. Click OK to create the project.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-7

Windows Forms

Figure 2. Creating a new project.

3. A new project is created. Depending on how your system is configured, you should see the Toolbox open on the left, a form designer in the center, and the Solution Explorer and Properties window on the right, with a number of windows available at the bottom, as shown in Figure 3. If any of these windows are not open, you can open them from the View menu or the Debug window. If the Toolbox does not appear, but a small rectangle with the word Toolbox is visible in the upper left, click on the rectangle, the Toolbox should open. You can then pin it in place, using the Pushpin icon.

15-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Windows Applications

Figure 3. Visual Studio .NET windows.

4. Drag a Label to the form. Place it where youd like it. 5. Use the Properties window to set the text property of the label to Hello World From Windows! 6. Drag the label to size it big enough to hold the text. 7. Drag a button onto the form. Set its text property to Cancel. 8. Drag the button to where you want it on the form, then click on the form and resize it to the size you want. 9. Double-click on the button to open its Click event handler. Visual Studio brings you to the code-behind page in the button1_Click event handler. It has already wired the handler for you; no need to do that by hand. 10. Add the code for the event handler, just as you did previously.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-9

Windows Forms
private void button1_Click( object sender, System.EventArgs e) { Application.Exit(); }

11. Press CTRL+F5 to run the application. The application starts up and the window opens, as shown in Figure 4. You can press Cancel to close the application and return to the development environment.

Figure 4. Running the application from Visual Studio .NET

Enhancing Your Application


You can see that even with the worlds simplest Windows application, Visual Studio .NET has made your life a lot easier. You are ready now to enhance this application a bit. There is a tremendous amount you can do; this chapter will only highlight a few of the tools available.

Try It Out!
See HelloWorld WindowsVSNET. sln To get started, just modify the existing application to see how you can add features with very little effort.

1. Reopen the project from the previous example. 2. Click on the form itself, and go to the Properties window. Set the name of the form to HelloWorld and set the Text to Hello World From

15-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Windows Applications


Windows!. When you leave the Text property, you should see the title of the Form change. 3. Click on the BackColor attribute in the Properties window. A dropdown opens. Click on Custom to open a color palette. Choose a pleasing color and youll see the background color for the form change to your choice. 4. Click on the label and change the text to HelloWorld. Change the name to lblHello. Click on the font attribute and set the font to Comic Sans MS. Set the size to 14. 5. Change the text on the Cancel button to Bye and add a second button with the text ChangeText. Name the new button btnChange. Move both buttons to the upper left of the form. 6. Click on the first button, then shift click on the second to select them both. Use Format|Make Same Size to make the buttons the same size. Use Format|Align to align the two buttons on their left sides, then use Format|Vertical spacing to space the buttons. 7. Click on the form to show its sizing handles, and resize the form, so that it looks like Figure 5. Feel free to adjust the form to fit your own aesthetic sensibilities.

Figure 5. Resizing and formatting the form.

8. You are ready to wire up the event handlers. Double-click the Bye button and verify that the code has not changed in the event handler. 9. Return to the designer and double-click on btnChange. You are now in the event handler for the btnChange click event. Add the following text:
lblHello.Text = "Goodbye world!";

When the user clicks on the btnChange button the text in the label will change. 10. Navigate in the page behind to find the Main method. Change the argument to run to new HelloWorld. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-11

Windows Forms
static void Main() { Application.Run(new HelloWorld()); }

You renamed the form, and you must use the new name of the form when you create it. Scroll up to see that the name of the class was changed as well, and that it is an instance of this newly-named class that you are creating.
public class HelloWorld : System.Windows.Forms.Form

11. Build the application and run it. When it opens, click on btnChange to see the effect, as shown in Figure 6. When you click on the btnChange button, the text in the label is altered. Click Bye to exit the program.

Figure 6. After clicking Change Text.

Controls
The key to Windows Forms applications is the plethora of controls available in the Toolbox. Writing a good Windows application is more than just dragging controls onto a form, but that is a good starting point. Put the right controls on the form and wire up useful event handlers, and you are well on your way to building powerful Windows applications. This is a course on C# and not on Windows application development, so this chapter just scratches the surface on what is available. Nonetheless, looking at how to build Windows applications provides powerful insight into the utility of the techniques taught in the chapters on language fundamentals.

15-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Windows Applications

Try It Out!
See WinForm Controls.sln To get started, youll build a simple form-based application with various controls. The final product is shown in Figure 7. This is a form that might be used by a salesperson to take an order for a new computer. Choose various options, and when you click Order, the order is summarized in the space below the Order button.

Figure 7. A complex order form.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-13

Windows Forms
1. Create a new Windows Forms application and name it WinFormControls. 2. Drag a single check box control onto the form and place it where you want it. Name the box chkService and set the text to Full service. 3. Run the application. Your check box is drawn and you can check and uncheck it (though nothing interesting will happen when you do). The result is shown in Figure 8. You dont get much functionality, but its not bad for 30 seconds work.

Figure 8. Testing the check box.

4. Add a group box to the form and make it bigger then youll need. 5. Add three radio buttons within the group box (just drag them onto the group box). Align them more or less one below the other. 6. Name the three radio buttons btnLaptop, btnDesktop, and btnWorkStation. 7. Set their text to Laptop, Desktop, and Work Station respectively. 8. Shift click through the three buttons to select them all. 9. Choose Format|Align|Lefts to align their left-hand edges. 10. Choose Format|VerticalSpacing|MakeEqual to set equal spacing between all three controls.

15-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Windows Applications


11. Choose Format|VerticalSpacing|Increase (or decrease) if you need to change the amount of space between the controls. Keep the vertical spacing fairly tight. 12. Move the three buttons to the upper left of the group box and resize the group box to fit. 13. Click on the group box and change its text to Computer Type as shown in Figure 9.

Figure 9. Setting the group box.

14. Click on the rbDesktop control and set its checked property to true. When you run the application, this button will default to checked. Run the application to test it, as shown in Figure 10. You are free to click on other buttons, but this way the default choice is Desktop.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-15

Windows Forms

Figure 10. Testing the radio button checked property.

15. Drag a button onto the form, and name it btnOrder. Set its text to Order. 16. Lengthen the form to make room, and drag a label under the order button. Name the label lblOutput. Widen the label and lengthen it to hold a good bit of text. Delete the text in its text property (so that it is blank). 17. Double-click on btnOrder to set the event handler. You want to assemble the output string. Start by creating a string named output. Add code to test whether the chkService check box is checked. If so, modify the output string:
string output = "Your order...\n"; if (chkService.Checked == true) output += "You ordered the full in-house service\n";

18. Add code to the event handler to check which radio button is checked, and to modify the output accordingly.
if (rbLapTop.Checked) output += "Laptop computer\n"; else if (rbDeskTop.Checked) output += "Desktop computer\n"; else output += "Workstation computer";

15-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Windows Applications


19. Add code to display the results in the label.
lblOutput.Text = output;

20. Run the program to test it. Click on choices and click Order to see the results displayed in the label, as shown in Figure 11. If the output is wrapping where you dont expect, or is cut off, you may need to expand the label field.

Figure 11. Testing the button event handler.

Get It Working/Keep It Working


The approach you are taking with this application is a very powerful design approach: get something working and keep it working. Rather than adding all the controls, setting all the properties and then trying the program, you are adding small changes and repeatedly testing. This is a very effective way to write complex applications.

Events
When you double-clicked the Order button you went to the event handler for the OnClick event. This is the default event for the button, but of course button supports many other events. To see how to wire up other events, click on the button and then click on the lightning bolt button in the Properties window. A list of events is shown, as illustrated in Figure 12. You are free to fill in the C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-17

Windows Forms
name of event handlers for any of the events. For example, type in the name OnDragDrop next to the DragDrop event and press ENTER. You are taken to the OnDragDrop event handler.

Figure 12. Events for the button.

If you made a mistake and did not want to create this event, just go back to the form and delete it from the events list. Do not delete the event handler in the code-behind or youll leave behind the code that adds the method to the event. You can remove that by hand, but it is cleaner to let the IDE do it for you.

Adding More Controls


See WinForm Controls.sln To see how some of the other controls work, youll add a few more controls to the application you created in the previous Try It Out.

Try It Out!
1. Add a checked list box and name it cblFeatures. Youll probably have to lengthen the form and move the list box and Order button out of the way.

15-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Windows Applications


2. You will want to populate the list box when the form is created. To do this, double-click on the form. This opens the default event handler for the form, Form1_Load. This is the event that fires when the form is loaded. 3. Add code to the Form Load event handler to populate the list box. The list box has a member Items, which is a collection of the items in the list. You can add to that collection using the AddRange method that takes an array of objects. Youll pass in an array of eight strings:
cblFeatures.Items.AddRange( new object[8] { "Monitor", "Tape backup", "Zip drive", "Modem", "Extra Ram", "Speakers", "CD", "CDRW" } );

4. Modify the order_click event handler to pick up the choices made by the user. The check box list contains a list of items that are checked, CheckedItems. You can iterate over that list with a foreach loop, adding each string to the output. Modify the event handler to look like the following:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-19

Windows Forms
private void btnOrder_Click( object sender, System.EventArgs e) { string output = "Your order...\n"; if (chkService.Checked == true) output += "You ordered the full in-house service\n";

if (rbLapTop.Checked) output += "Laptop computer\n"; else if (rbDeskTop.Checked) output += "Desktop computer\n"; else output += "Workstation computer";

foreach (string s in cblFeatures.CheckedItems) output += "Feature: " + s + "\n";

lblOutput.Text = output; }

5. Build and run the application. Click on various features and then click Order. The results should look something like Figure 13.

15-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Windows Applications

Figure 13. Testing the check box list box.

6. You can add a regular (not checked) list box in much the same way. Drag a list box onto the form and name it lbModel. Once again, populate this in the form load event:
for (int i = 0; i<20; i++) { lbModel.Items.Add("Model X" + i); }

This creates model numbers such as ModelX0, ModelX1, and so forth. 7. Adjust the event handler for the button to display the model.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-21

Windows Forms
if (lbModel.SelectedIndex != -1) output += "Model: " + lbModel.Items[lbModel.SelectedIndex] + "\n";

If no item is checked, the SelectedIndex property of the list box will be 1, otherwise the SelectedIndex property can be used as an index into the Items collection of the list box. 8. Youll add one more control: a combo box to list the salesperson taking the order. Drag a combo box onto the form, and name it cbSalesPerson. 9. Initialize the combo box in the form load event handler.
cbSalesperson.Items.AddRange( new object[4] { "Jesse", "Mr. Galt", "Milo", "JW" } );

Once again you are filling the list from an array of Strings. This time, however, you want to set the text for the drop-down list box to a prompt that is not in its list of choices. 10. Set the Text property of the combo box to Choose Sales Person.
cbSalesperson.Text = "Choose sales person";

11. Add code in the button event handler to pick up the choice of salesperson.
if (cbSalesperson.Text != "Choose sales person") output += "Sold by: " + cbSalesperson.Text + "\n";

12. Test the application. Click on the various features and then click Order, as shown in Figure 14. You should see a summary of all your choices in the label below the Order button.

15-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Windows Applications

Figure 14. Testing the controls.

Color and Font Properties


While the form works, it doesnt look great. The job of laying out a truly professional-looking form is beyond the scope of this book (and beyond my capabilities!) but there are some properties you can use to add color to your form. For example, you can set the background color of the list boxes, change the text color of the text itself, and change the font as shown in Figure 15.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-23

Windows Forms

Figure 15. Adding a splash of color.

Date/Time Picker
This chapter has barely scratched the surface on the various features and attributes of these controls and these are only some of the standard controls available to you. There is also a suite of new sophisticated advanced controls provided by the Frameworks. In this section youll take a look at just one: the DateTimePicker control. The purpose of the DateTimePicker control is to provide easy access to dates and times (as you might guess). Youll use it to add a control for setting the delivery date of the order you are building.

15-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Windows Applications

Try It Out!
See Controls.sln Youll make one minor modification to the form you just built, adding the new control and using it to set the delivery date. 1. Reopen the project you were just working on. 2. Expand the form to make room for the new control. 3. Drag a dateTimePicker control onto the form. Name the dateTimePicker object dpDelivery. 4. The dateTimePicker has a number of powerful properties. Youll take control over the format of the date by setting the Format property to Custom. 5. Set the CustomFormat field to MMMM dd, yyyy dddd. This causes the date to be displayed as, for example, July 10, 2005 Sunday. 6. In the Order buttons on click method, add the following line.
output += "Delivered on " + dpDelivery.Value.ToString("MM/dd/yy (dddd)") + "\n";

This formats the delivery date to the date and day, as shown in Figure 16.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-25

Windows Forms

Figure 16. Formatting the DateTime

Using the Documentation


All of this is great, but how would you possibly figure out how to do this with the dateTimePicker if this book didnt tell you? Visual Studio .NET comes with documentation. Look up the dateTimePicker class in the Help index and click on All Members. Scroll down to the value property and click on that. You see that the Value property returns a DateTime object. Click on DateTime in the Help file and scroll down to the bottom. One of the links is DateTime Members. Click on that link. One of the instance methods is ToString. Click on that link and youll see that it is overloaded. One of the overloaded versions

15-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Windows Applications


takes a string. Click on that and youll see that the string is named format. The documentation then provides all the formatting characters and what they mean.

Getting Started
You dragged a number of controls onto the form and added event handlers as required. You set properties to change the look and feel of the form. There is much more you can do to make this form useful, but that is beyond the scope of a course on C#. Before going on, however, you may want to play with the form, add new controls, and get a sense of what power lies in this development environment.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-27

Windows Forms

XML Documentation
Youve seen in previous chapters that C# offers both the traditional C-style comments,
/* this is a comment */

and the more modern C++ style comments:


// this is a comment

C-style comments can span lines and can be used to comment-out entire sections of code. There is a third type of comment in C#the XML comment:
/// this is an xml comment

XML comments begin with three slashes and run to the end of the line. XML comments are used to document your code, and with the right tools, these can be used to generate extensive documentation pages. You can use compile times switches to generate an XML document based on your XML comments and you can use XSL to transform these comments into any kind of document youd like. Visual Studio .NET also provides built-in support for XML comment documentation.

Try It Out!
See WinForm Controls.sln To see how XML comments work, return to the previous example and add XML comments to document the file. 1. Reopen the previous example. 2. Scroll to the top of the file and modify the documents above the declaration of the form.

15-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

XML Documentation
/// <summary> /// Form for accepting computer orders /// </summary> public class Form1 : System.Windows.Forms.Form

3. Scroll down to the Form1_Load event handler and type three slashes before the method header. A summary comment springs open. Add a summary within the summary tags and add a description of the parameters within the param tags:
/// <summary> /// Event handler for Form loading /// </summary> /// <param name="sender">The form itself</param> /// <param name="e">EventArgs structure</param>

4. Add a comment block above the event handler for the button click.
/// <summary> /// Button is clicked event /// </summary> /// <param name="sender">The button</param> /// <param name="e">Struct with info about the event</param> private void btnOrder_Click( object sender, System.EventArgs e) {

5. When you are done commenting, choose the menu items Tools|Build CommentWebPages. A dialog box opens as shown in Figure 17. Leave the defaults and click OK.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-29

Windows Forms

Figure 17. Building Comment Web pages.

6. A page is created for each project. In this case there is only one project, WinFormControls, as shown in Figure 18.

Figure 18. A report is created for each project.

15-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

XML Documentation
7. Click on the report for WinFormControls. Click on the + sign next to WinFormControls and the forms are displayed. Click on the Form1 link to show the report about Form1, as shown in Figure 19. Each of the controls have links to documentation, as do all the methods you commented or that were commented automatically by Visual Studio .NET.

Figure 19. The report for Form1.

8. Click on btnOrder_Click. You are taken to a page with information about this method, including its parameters. The descriptions you added in the params element is shown here, as shown in Figure 20.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-31

Windows Forms

Figure 20. The btnOrder_Click Function.

Its Just HTML


The reports themselves are just HTML. To see this, open an Explorer window and navigate to the directory for your project. Youll find a CodeCommentReport directory with a number of images in it. Within that directory youll find a ControlForm directory filled with htm files that represent the various reports you are viewing.

Really, Its XML


The HTML pages were generated from the XML file you created based on the comments. You can also see the XML file itself. 1. Open the project in Visual Studio .NET and click on the project in the Solution Explorer. 2. Choose View/Property Pages. 3. Click on Configuration Properties. 4. Set the XML documentation file property as shown in Figure 21. You may name the XML Documentation File anything you wish.

15-32

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

XML Documentation

Figure 21. Setting the XML Documentation File.

5. Rebuild the application. 6. Navigate to the directory for your project. You should find a file XMLComments.xml (or whatever it is you named the file in Step 4). 7. Double-click the file. A browser opens with the XML for the comments you added, as shown in Figure 22. It is this file that is used to generate the HTML report. Typcially the file is not saved after the report is generated, unless you set the file name as shown in Step 4.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-33

Windows Forms

Figure 22. The XML comments file.

15-34

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

XML Documentation

Summary
There are three types of applications you can build using C# and .NET: Windows applications, Web applications, and Web Services. Windows applications are built using Windows forms. Windows Forms provide a Rapid Application Development (RAD) environment in which you can drag controls onto a form. You create event handlers for the controls on your form. The Visual Studio .NET Integrated Development Environment (IDE) will help with wiring the event handlers. If you double-click on a control the IDE creates an event handler for the default event. You can access alternative events using the lightning bolt button on the properties window. List box controls have an Items collection to manage the items listed in the list box. The Help documentation can be a powerful resource for understanding the properties and methods of controls. You can document your files with XML document comments (///) and then use Visual Studio to generate XML report files.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-35

Windows Forms

(Review questions and answers on the following pages.)

15-36

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

XML Documentation

Questions
1. What is the difference between a Windows application and a Web Forms application? 2. From which class must your Form derive? 3. What is the purpose of the statement Using System.Windows.Forms? 4. How do you add controls to a form? 5. How do you add entries to a list box? 6. What is the method to call to add an array to a list box? 7. How do you generate a report based on your XML comments? 8. How do you generate the XML file itself?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-37

Windows Forms

Answers
1. What is the difference between a Windows application and a Web Forms application?
A Windows application uses Windows Forms and is intended to be run on the desktop. A Web Forms application is served by a Web server and uses a browser for its User Interface.

2. From which class must your Form derive?


You must derive all forms from System.Windows.Forms.Form

3. What is the purpose of the statement Using System.Windows.Forms?


The using statement allows you to refer to objects in the System.Windows.Forms namespace without qualifying the namespace; thus you can write Label rather than System.Windows.Forms.Label.

4. How do you add controls to a form?


You add controls to the forms Controls collection.

5. How do you add entries to a list box?


You add entries to a list box by modifying the List boxs Items collection.

6. What is the method to call to add an array to a list box?


The List Boxs Items collection has an AddRange method that will take an array of objects.

7. How do you generate a report based on your XML comments?


To generate the report from within Visual Studio .NET you use the menu choice Tools/Build Comment Web Pages.

8. How do you generate the XML file itself?


You generate the XML file by navigating to the PropertyPages for the project and setting the XML Documentation File property under Configuration Properties.

15-38

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

XML Documentation

Lab 15: Windows Forms

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-39

Lab 15: Windows Forms

Lab 15 Overview
In this lab youll learn to create Windows Applications using WinForms. To complete this lab, youll need to work through three exercises: Building a Windows Form without Visual Studio .NET Creating Hello World Using Visual Studio Building Applications with Windows Controls

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

15-40

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Building a Windows Form without Visual Studio .NET

Building a Windows Form without Visual Studio .NET


Objective
In this exercise, youll see that building a Windows Forms application by hand is possible, but not necessarily easy.

Things to Consider
Your form must inherit from System.Windows.Forms.Form. You must place each control at a specific location (using System.Drawing.Point). You must size each control (using System.Drawing.Size). Once you create your controls you must add them to the Forms control collection.

Step-by-Step Instructions
1. Open your favorite text editor, such as Notepad or WordPad, and start a new text file. Call it HelloWorld.cs. 2. Add using statements for the namespaces System and System.Windows.Forms.
using System; using System.Windows.Forms;

3. Create a namespace (optional). Dont forget the closing brace at the end of the file.
namespace HelloWorld_Completed {

4. Create a class derived from Form. C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-41

Lab 15: Windows Forms


public class myForm: Form {

5. Create a label to display text.


private System.Windows.Forms.Label output;

6. Create a button to cancel (exit).


private System.Windows.Forms.Button cancel;

7. Create the constructor for the myForm class.


public myForm() {

8. Create the objects: a label and a button.


output = new System.Windows.Forms.Label (); cancel = new System.Windows.Forms.Button ();

9. Set the forms title.


Text = "Hello World From Windows";

10. Set the labels location to 20,30 (use System.Drawing.Point).


output.Location = new System.Drawing.Point (20, 30);

11. Set the labels text to Hello World From Windows!


output.Text = "Hello World From Windows!";

12. Set the labels size to 200,25 (use System.Drawing.Size). 15-42 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Building a Windows Form without Visual Studio .NET


output.Size = new System.Drawing.Size (200, 25);

13. Set the buttons location to 150,200 (use System.Drawing.Point).


cancel.Location = new System.Drawing.Point (150,200);

14. Set the buttons text to &Cancel.


cancel.Text = "&Cancel";

15. Set the buttons size to 110,30 (use System.Drawing.Size).


cancel.Size = new System.Drawing.Size (110, 30);

16. Register the click event handler.


cancel.Click += new System.EventHandler (this.OnCancelClick);

17. Add the controls to the form.


Controls.Add (cancel); Controls.Add (output);

18. End the myForm constructor.


}

19. Implement the click event handler. The only action is to call the static method Application.Exit.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-43

Lab 15: Windows Forms


protected void OnCancelClick( object sender, System.EventArgs e) { Application.Exit(); }

20. Run the app from Main by calling Application.Run and passing in an instance of the form.
public static void Main() { Application.Run(new myForm()); }

21. End the class and namespace.


} } // // end the class myForm end the namespace

22. Open the command window for compiling your class. To do so, open the Visual Studio .NET Command Prompt from the Start|Programs|Visual Studio.NET Tools menu as shown in Figure 23.

Figure 23. Choosing the command prompt.

23. Navigate to the directory with your .cs file. 24. Compile the program with the following command:
csc HelloWorld.cs

25. List the directory; you should now find helloWorld.exe as well as helloWorld.cs. Enter the name helloWorld and press ENTER. Your code should run as shown in Figure 24. 15-44 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Building a Windows Form without Visual Studio .NET

Figure 24. Running the hand-crafted window.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-45

Lab 15: Windows Forms

Creating Hello World Using Visual Studio


Objective
In this exercise, youll recreate the Hello World application using Visual Studio.

Things to Consider
Visual Studio writes much of the infrastructure for you, but the fundamentals are unchanged. Your form will be separated from your code-behind file. The codebehind will house the event handlers. When you place objects on the form, they will be located and sized for you, but you may take explicit control either in the code or through the properties.

Step-by-Step Instructions
1. Create a new Visual C# Windows Application in Visual Studio named WindowsHelloWorld. 2. Drag a label onto the form and set the text to Hello World From Windows! 3. Stretch the label to fit the words. 4. Drag a button onto the form. Name it btnCancel. 5. Set the buttons text to Cancel. 6. Double-click on the Cancel button to open the event handler. 7. Enter the code to close the application in the event handler.
Application.Exit();

8. Press CTRL+F5 to start the application, as shown in Figure 25. 15-46 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Hello World Using Visual Studio

Figure 25. The Hello World application.

9. Click the Close button (X) to close the application.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-47

Lab 15: Windows Forms

Building Applications with Windows Controls


Objective
In this exercise, youll create a Windows form for booking a reservation at a hotel.

Things to Consider
Radio buttons are used to select a single choice among many. Check boxes are used to select multiple choices. When you have many check boxes, consider using a check box list. Drop-down list controls are good for a single selection among many choices. Double-click on a control to set its default event handler.

Step-by-Step Instructions
1. Create a new Windows application named Hotel Reservation. 2. Drag a label onto the form and set its text to Hotel Reservation Form. 3. Set the font size to 14. 4. Set the font to bold. 5. Set the TextAlign property to MiddleCenter. 6. Drag a check box onto the form and name it chkVIP. 7. Set its background color to Green and its text to VIP Customer. 8. Drag a group box onto the form, and set its text to Room Type. 9. Drag a radio button onto the group form, set its name to rbSingle, and its text to Single. 10. Drag a second radio button onto the group form, set its name to rbDouble, and its text to Double. 15-48 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Building Applications with Windows Controls


11. Drag a third radio button onto the group form, set its name to rbSuite, and set its text to Suite. 12. Use the format menu choice to make all three radio buttons the same size, align their left borders, and set their vertical spacing to equal. 13. Resize the group to fit the radio buttons and set the background color of the group to Cornflower blue. 14. Drag a checkedListBox onto the form and set its name to clbFeatures. 15. Click on the Items property and add the following features:
Whirlpool Color TV VCR DVD On-Demand Movies In-Room Wet Bar Free Breakfast Free Lunch Free Meals Late checkout Early check in

16. Set the Sorted property to true and watch the list sort itself. 17. Set the background color to a pleasing pastel. 18. Add a button named btnBook and set its text to Book Reservations. Stretch the button to fit. 19. Set the buttons background color to red, set its forecolor to yellow, and set its font to bold. 20. Drag a label onto the form. Set its name to lblOutput and its text to blank. Your form should now look more or less like Figure 26.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-49

Lab 15: Windows Forms

Figure 26. The hotel reservation form.

21. Double-click on the Book Reservations button to open the event handler. Create a string to hold the output:
string output = "Your reservation...\n";

22. Check if the VIP Customer check box is checked, if so modify the text for the output string.
if (chkVIP.Checked == true) output += "VIP Service!\n";

23. Check which radio button is checked and modify the output string.

15-50

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Building Applications with Windows Controls


if (rbSingle.Checked) output += "Single room\n"; else if (rbDouble.Checked) output += "Double room\n"; else output += "Suite";

24. Add output for each feature that is selected and checked in the list.
foreach (string s in clbFeatures.CheckedItems) output += "Feature: " + s + "\n";

25. Display the output in the label.


lblOutput.Text = output;

26. Build and run the application as shown in Figure 27.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

15-51

Lab 15: Windows Forms

Figure 27. Final exercise results.

15-52

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

ADO.NET

ADO.NET
Objectives
Understand how relational databases are organized. Explore Declarative Referential Integrity, foreign key constraints, database normalization and Structured Query Language. Understand how to join tables to create declarative Select statements. Explore the ADO Object Model. Fill controls from data in a database. Use both the SQL and ADODB Data Providers. Bind data to Windows Forms controls. Model data relations in a DataSet. Display master/child relationships in a DataGrid control.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-1

ADO.NET

Relational Databases
Just about any non-trivial application will need to interact with data. Most often, that data will be held in a relational database (RDB). ADO.NET is the newest technology from Microsoft to interact with underlying databases. The goal of ADO.NET is to provide an object-oriented interface to your data. One of the problems with building object-oriented programs, historically, has been in interacting with databases, which are typically not object oriented. ADO.NET provides a bridge between your program and the underlying data; it presents as a series of objects with which your program can interact, but it manages the transition to the relational structure of your data. ADO.NET also offers a disconnected model of your data, which allows you to build large, scalable applications without a bottleneck at the connection to the database. While ADO.NET does offer a series of classes that are optimized for interacting with SQL Server, you can also use ADO.NET with any ODBC database, whether or not your data is in a relational database. That said, the principal use of ADO.NET will be with one of the major relational databases: typically SQL Server, Access, or Oracle. Before investigating the ADO.NET object model, this chapter provides a primer on using relational databases.

RDB Structure
A relational database is nothing more than a repository of data. Typically a relational data base is organized into tables. Each table is in turn organized into records (rows) and fields (columns). Every row in a relational database has the same fields as all the other rows in that table. Each column in each table holds a specific type of information. The tables in a relational database are related to one another by common fields. To see how this works, youll examine the Northwind relational database that ships with both SQL Server and Access. The Northwind Database describes a fictional food products company. The tables in the database encapsulate all the companys information about its products, customers, suppliers, and employees, as well as the details of every order the company has filled. For example, the Orders table has a variety of columns, as shown in Figure 1. You can see that each row in the order table will record the CustomerID, EmployeeID, OrderDate, and so forth.

16-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Relational Databases

Figure 1. The Orders table.

Normalization
When you examine an order, youll want the customers name and address. Rather than sorting the name and address with each order, you store only the CustomerID. You can then use the CustomerID to look up the customers information in the Customer table, which in turn is organized by CustomerID. This relationship is illustrated in Figure 2. The CustomerID field in Orders establishes a relationship with the CustomerID field in Customers.

Figure 2. Orders to Customer.

This is much more efficient than storing the name and address with every order, and it is much safer. If you later change the customers address, you C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-3

ADO.NET
need only update one record in Customers, rather than all the records for that customer in Orders.

Keys
The CustomerID is a primary key in the Customers table (indicated by the key symbol in Figure 2).

Key Terms
Primary key Foreign key A primary key uniquely identifies a record in a database table. A primary key from a different table.

Foreign key constraint A constraint on the table that enforces that you create valid records and disallows invalid deletions or updates based on a foreign key relationship.

The CustomerID is a foreign key in the Orders Table. In this case, CustomerID is used in Orders to relate the order to the correct record in the Customers table. You can (of course) have many orders with the same CustomerID, but you can have only one record in Customers with that CustomerID. The use of a foreign key in the Orders table creates a foreign key constraint. This foreign key constraint ensures that you may not add a record in Orders without a valid CustomerID. That is, the CustomerID must be a Primary key to a row in the Customers table. In addition, you may not delete a record in Customers whose CustomerID is used in a record in Orders. This prevents you from having Orders with a (now) invalid CustomerID.

Declarative Referential Integrity (DRI)


This process of establishing constraints on your database to ensure that your data remains valid is called Declarative Referential Integrity. DRI helps you avoid database corruption and reduces programming errors. You are enlisting the database in rejecting operations that might otherwise leave your data in an inconsistent (corrupt) state.

16-4

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Relational Databases

Using SQL Server to Examine the Relationships


The diagrams shown previously were created in SQL Server. You can open the Enterprise Manager and examine the structure of the tables. For example, you can examine the structure and contents of the Orders table in a variety of ways. First, you can look at the design of the table itself, using the Design view, as shown in Figure 3. You can see that each field has a data type. The OrderID (highlighted) is of type int. Int fields have a length of 4 bytes. This particular field is marked as an Identity column (see circled area on the bottom). The OrderID is also marked as the primary key for this table. The second field is the CustomerID, marked as being of type nchar with a length of 5. This is a 5-character field. The third field is the EmployeeID, an int. The OrderDate, RequiredDate, and ShippedDate are all datetime objects; a datetime object is a SQL representation of a Julian date that maps well to .NET dates.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-5

ADO.NET

Figure 3. The Orders Table Design dialog box.

A second way to look at this same data is to examine the contents of the database, again using a table view in SQL Server Enterprise Manager, as shown in Figure 4. The table is too wide to show all of it, but you can see here that there are numerous rows, each of which is divided into the columns previously discussed.

16-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Relational Databases

Figure 4. Examining the contents of the table.

The very first record (OrderID 10248) is from a customer whose ID is VINET. You can use that ID to look up that customer in the Customers table as shown in Figure 5.

Figure 5. Looking up a customer in the Customers table.

You found the particular record you were looking for by issuing a select statement:
SELECT * FROM Customers WHERE (CustomerID = VINET)

This statement is written in Structured Query Language (SQL). SQL is often pronounced see-quill. SQL is a declarative language (rather than a procedural or object-oriented language). That is, the goal in SQL is to create a statement that works all at once, rather than in a series of steps. The heart of SQL is the query. The select statement shown above is a very simple example of a query. You can experiment with SQL queries using the Query Analyzer. For example, you can issue a Select query in Query Analyzer to find all the records in Customers where the city field is London. You might extract only a couple of columns, such as the CustomerID and CompanyName, to keep the results simple. This query is illustrated in Figure 6. The results are shown in the C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-7

ADO.NET
bottom pane; six companies were found. Notice that you do not need to include the search field (city) in the results.

Figure 6. Issuing a select statement.

Joining Tables
To find more complex information you may have to join two or more tables together. For example, suppose you are in charge of the Northwind database, and you get a call from one of your customers: the president of Rancho Grande restaurant. She wants to know what products she bought from you in 1998. How do you answer the question? Youll need to examine your Orders table to find all the orders with an OrderDate in the year 1998. You also need to examine the OrderDetails table to get the products ordered. The OrderDetails table records only the ProductID of the product; to get the product name youll need the ProductTable (indexed by ProductID). The relationship among these tables is illustrated in Figure 7.

16-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Relational Databases

Figure 7. Orders to Customers and Products.

You can see that the OrderID and ProductID together make a primary key in Order Details. That makes sense, since any given order detail record will record one product purchased on a given order. The OrderID itself is a primary key in the Orders table. Just as the Orders table has a foreign key relationship through the CustomerID to the Customers table, the OrderDetails table has a foreign key relationship through the ProductID to the Products table. To get started, you might first find all the customerIDs from the customer table where the Company name is Rancho Grande, as shown in Figure 8. The CompanyID for this company is RANCH.

Figure 8. Finding the Company ID.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-9

ADO.NET
To find the orders by this company, youll join the Orders table to the Customers table, finding only those records where the CustomerID matches, as shown in Figure 9. There are a few interesting things to note about this search.

Figure 9. Searching for all orders from a given company.

First, notice that you can alias a table name. For example, in the SQL statement shown in Figure 9, the table name customers is aliased to c; you can refer to the CustomerID field of Customers with c.CustomerID. Second, in this example the customers table is joined to the Orders table with the keyword join. The on keyword sets the condition of the join; not every record is joined to every other record; just those records where the CustomerIDs are the same. The result is as if you had created one very wide table of all the records in Customers joined with all the matching records in Order. Within that, you find only those records where the CompanyName field is equal to Rancho Grande. This narrows the records to five matching orders. You now need to narrow the search to those that occurred in 1998, as shown in Figure 10. This query is just like the previous one, except that the where clause is narrowed to find the matching order dates.

16-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Relational Databases

Figure 10. Selecting only matching dates.

With these three order records found, you are ready to find all the matching records in the Order Details table. Youll join the Order Details table to the Orders table on the orderID field, as shown in Figure 11.

Figure 11. Adding the OrderID table.

Notice that the Order Details table has a space in its name and so must be enclosed in square brackets. The list of matching records is larger than the previous search; this makes sense because now you are widening the table to C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-11

ADO.NET
include all the order details, and there are multiple details records for each order. Until now, youve been getting back all the fields. You really only care about the OrderID, ProductName and OrderDate. With these, you can tell your customer what she ordered and when. Youll need to add in the products table (joined on the Product ID) to get the product name. This is illustrated in Figure 12.

Figure 12. Getting the Product Name and order info.

SQL Is a Declarative Language


The important thing to realize about this last exercise is this: you built this query in successive approximation, but that was just a teaching tool. If you were a SQL expert, you could have written the entire SQL statement all at once. The results are presented in totality. This is very different from how you would handle a procedural language. In a procedural language youd get all the order records for your customer. Youd then use the OrderDate to get all the details. Youd then use the ProductIDs to get all the product names. Here you simply declare it all, with a single select statement:

16-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Relational Databases
select o.OrderID, productName, OrderDate from [order Details] od join orders o on o.orderID = od.OrderID join products p on p.productID = od.ProductID join customers c on o.CustomerID = c.CustomerID

where CompanyName = 'Rancho Grande' and OrderDate >= '1/1/1998' and OrderDate <= '12/31/1998'

The SQL statement has no loops, no real procedures; it just selects the records it needs as a set. It is the difference between saying go find all the orders this customer placed, now get the details, now find the products and give this customer a list of them on the one hand, and saying give me all the product names for all the orders I placed during 1998 on the other. SQL enables you to make the latter declaration; which can be much faster and more efficient, but it does take some getting used to.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-13

ADO.NET

The ADO.NET Object Model


The ADO.NET Object model starts with your data; typically stored in a database. You can read that data using a simple ADO.NET object, DataReader, as shown in Figure 13. The DataReader provides a connected, forward-only firehose cursor on your data.

Figure 13. The DataReader.

More common than using a DataReader is to use the powerful DataAdapter, and the associated Connection and Command objects. These three objects work together to produce a DataSet, which is a disconnected object representing a subset of your data and the relationships among the tables housing your data, as shown in Figure 14.

Figure 14. The DataSet and associated objects.

16-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

The ADO.NET Object Model

The DataSet Object


The DataSet is the heart of the ADO.NET model. It is a disconnected subset of your database not a single table or a recordset, but rather a microcosm of the database itself, complete with tables and, most important, the relationship among the tables. The DataSet, illustrated in Figure 15 has a member variable, DataTableCollection, which contains a set of DataTable objects.

Figure 15. The DataSet object.

Each DataTable object has a DataRowCollection, which in turn has DataRow objects. The DataTable also has a DataColumnCollection object, with, (you guessed it) DataColumn objects. A third collection in the DataTable is the ConstraintCollection, which encapsulates the constraints on the table. Finally, the DataSet also contains a DataRelationsCollection, which contains DataRelation objects that encapsulate the relationships among tables.

DataAdapter
The job of the DataAdapter, illustrated in Figure 16, is to decouple the DataSet from the underlying database. It does this by providing a Fill() method, which retrieves data from the database and fills tables in the dataset. To accomplish C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-15

ADO.NET
this decoupling, the dataAdapter encapsulates the various SQL commands used to manipulate the database (select, insert, delete, and update).

Figure 16. The DataAdapter object.

Command and Connection


The DBCommand object serves to encapsulate the SQL commands you will use to interact with the database. Using the DBCommand class allows you to reuse commands, and to store them and otherwise treat them as objects with which you can interact. The DBConnection object encapsulates your connection to a particular database. DBConnection is instrumental in .NET support for transaction, as youll see in another chapter.

DataView
The DataView class, illustrated in Figure 17, is used for data binding, as you will see later in this chapter. The job of a DataView object is to provide a subset of the data available in a DataTable object.

16-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

The ADO.NET Object Model

Figure 17. The DataView class.

Working with Managed Providers


ADO.NET currently provides support for two managed providers: SQL Server OLE DB

The idea here is straightforward; if you are working with SQL Server, then you use the SQL Server Manager Provider classes. These are optimized for SQL Server and provide the highest performance. If you are working with virtually any other database, you use the OLE DB Managed Provider, which provides a more generic approach to interacting with the database. The SQL Managed Provider is supported by classes in the System.Data.SQLClient namespace, while the OLE DB managed provider is supported by classes in the System.Data.OLEDB namespace. Whenever there is a specialized class in one, there will be a shadow class in the other.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-17

ADO.NET

Try It Out!
See ADOSQL.sln To get started using ADO.NET youll populate a list box with the contents of the Products table from the Northwind database. This first example assumes you are running SQL Server. 1. Create a new Windows Project, named ADOSQL. 2. Drag a list box onto the form, and name it lbProducts. 3. Double-click on the form to open the Form1_Load event handler. This event handler will be called when the form loads. 4. Go to the top of the page and add the Using statement for the SQL Data Provider.
using System.Data.SqlClient;

5. Return to the event handler and create the connection string to connect to your server.
string strConnection = "server=YourServer; uid=sa; pwd=YourPW; database=northwind";

6. Create the command string for a simple select statement.


string strCommand = "Select productName, unitPrice from Products";

7. Create a dataAdapter using the SQL data provider version. Pass the Connection string and command string as parameters to the DataAdapters constructor.
SqlDataAdapter dataAdapter = new SqlDataAdapter(strCommand, strConnection);

8. Create the DataSet. Notice that this is independent of the data provider.
DataSet dataSet = new DataSet();

16-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

The ADO.NET Object Model


9. Fill the DataSet by calling the Fill method on the DataAdapter, passing in the DataSet and the name for the table you are creating within the DataSet.
dataAdapter.Fill(dataSet,"Products");

10. Extract the first table from the DataSet and assign it to an instance of DataTable.
DataTable dataTable = dataSet.Tables[0];

11. Iterate over the DataTables Rows collection, adding the ProductName column to the List box.
foreach (DataRow dataRow in dataTable.Rows) { lbProducts.Items.Add(dataRow["ProductName"] + " (at $" + dataRow["UnitPrice"] + ")"); }

There is a lot to observe in this foreach loop. For each row, you are accessing the column you want through the indexer, which has been overloaded to take a string (the column name):
dataRow["ProductName"]

This returns a string with the value of the ProductName column within the row. You add this to the Items collection in the list box. You then add a space and the string (at $, followed by the value of the UnitPrice column and then the closing parenthesis). 12. Build and run the application: the results are shown in Figure 18. This is pretty painless for extracting fields from a database and filling a list box.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-19

ADO.NET

Figure 18. Filling a list box from SQL Server.

Using the Generic Provider


See ADOOLEDB.sln Before you go on to add new functionality to this application, youll rebuild it using the OLE DB provider, to show that this is equally easy. This example assumes you have the Northwind Access database .mdb file copied to your C: drive in the root directory. 13. Modify the using statement to say:
using System.Data.OleDb;

14. Modify the connection string to connect to the Access database. If your copy of nwind.mdb is not in the root directory of C, be sure to set the appropriate dataSource string.
string strConnection = "provider=Microsoft.JET.OLEDB.4.0; data source=c:\\nwind.mdb";

15. Modify the creation of the dataAdapter to use the OleDbDataAdapter.


OleDbDataAdapter dataAdapter = new OleDbDataAdapter(strCommand, strConnection);

16. Build the application with no other changes. Your application runs, extracting the data from the Access database, as shown in Figure 19. The display is identical; the only change is that now you are accessing the data from Access rather than from SQL Server. 16-20 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

The ADO.NET Object Model

Figure 19. Filling a list box from the Access database.

Data Binding
In the previous examples, you created a DataSet and filled the list box by explicitly extracting a table from the DataSet and iterating over the rows in that table, pulling out column information and adding it to the list box. Windows Forms (and Web Forms) controls offer a more powerful idiom for filling data-bound controls. Rather than hand filling the control, you can set the controls DataSource to the appropriate tables DataView. The control is then filled for you automagically.

Try It Out!
See DataGrid1.sln To see how data binding works, youll create a new project using a more powerful data bound control: the DataGrid. 1. Create a new Windows application and name it DataGrid1. 2. Drag a DataGrid onto the form and resize it as shown in Figure 20. The DataGrid has no detail in it just yet.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-21

ADO.NET

Figure 20. The DataGrid in the Design view.

3. Rename the dataGrid to ProductDataGrid. 4. Right-click on the form and choose View Code. Set the using statement for SQLClient. 5. Click on the Design tab to return to the designer. Double-click on the form to go to the event handler for the Form1_Load event. Create the connection string and command string.
string strConnection = "server=YourServer; uid=sa; pwd=YourPW; database=northwind";

string strCommand = "Select productName, unitPrice, quantityperunit from Products";

6. Create the DataAdapter and the DataSet and fill the DataAdapter.
SqlDataAdapter dataAdapter = new SqlDataAdapter(strCommand, strConnection); DataSet dataSet = new DataSet(); dataAdapter.Fill(dataSet,"Products");

7. This time, instead of extracting a table and iterating over the rows, assign the DefaultView to the DataSource property of the DataGrid. 16-22 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

The ADO.NET Object Model


productDataGrid.DataSource = dataSet.Tables["Products"].DefaultView;

The DefaultView property returns a DataView object. By assigning this DataView to the DataSource property of the DataGrid, the grid has all it needs to display the data in the DataSet, as shown in Figure 21.

Figure 21. Filling the DataGrid.

Modeling Table Relationships


It was stated earlier in this chapter that the DataSet can capture the relationships among tables. One common relationship between two tables is the Master/Detail relationship, in which one table (e.g., Orders) holds a master record, and a second table (e.g., Order Details) holds the detail records. The DataGrid is uniquely useful for displaying the Master/Detail relationship, and works together with the DataSet very cleanly.

Try It Out!
See DataGrid Relations.sln To see modeling table relationships at work, youll create a new Windows application that reveals the relationship between the Orders table and the Order Details table. 1. Create a new Windows application and name it DataGridRelations. 2. Drag a DataGrid onto the form and resize it. Set its Size property to 865, 255. Set the forms size to 888,300. (Feel free to modify these to fit your own aesthetic judgment.) Name the grid dgOrders. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-23

ADO.NET
3. Go to the code behind page and add the using statement.

Creating the SQLCommand and SQL Connection Objects


4. In previous examples you passed the command string and connection string to the DataAdapter object. In this example, youll explicitly create these objects. Go to the code-behind page and add the following members to the class.
public class Form1 : System.Windows.Forms.Form { private SqlConnection connection; private DataSet dataSet; private SqlCommand command; private SqlDataAdapter dataAdapter;

Notice that you do not have to explicitly write System.Data.SqlClient.SqlDataAdapter; the using statement allows you to just write SqlDataAdapter. 5. Return to the form and double-click to open the form1_Load event handler. 6. To create the connection object, you first create the connection string, just as you did previously.
string strConnection = "server=YourServer; uid=sa; pwd=YourPW; database=northwind";

7. Instantiate the connection object, passing in the connection string.


connection = new System.Data.SqlClient.SqlConnection(connectionString);

8. Open the connection by calling the instance method Open on the connection object.
connection.Open();

16-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

The ADO.NET Object Model


9. Create the DataSet object.
dataSet = new System.Data.DataSet();

10. Create the DataCommand object and set its Connection property to the connection you created in Step 8.
command = new System.Data.SqlClient.SqlCommand(); command.Connection=connection;

11. Set the CommandText property of the command to the Select command youll use to obtain all the rows in Orders.
command.CommandText = "Select * from Orders";

12. Create the DataAdapter object and set its SelectCommand property to the command object you created in Step 10.
dataAdapter = new System.Data.SqlClient.SqlDataAdapter();

dataAdapter.SelectCommand= command;

13. Add a Table Mapping to the DataAdapter.


dataAdapter.TableMappings.Add("Table","Orders");

14. You are ready to fill the DataSet with the DataAdapter.
dataAdapter.Fill(dataSet);

15. Bind the default view of the Orders table to the data grid.
dgOrders.DataSource = dataSet.Tables["Orders"].DefaultView;

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-25

ADO.NET
16. Run the application. Youll see the contents of the Orders table displayed in the data grid, as shown in Figure 22.

Figure 22. Displaying the Orders table in a DataGrid.

Now it is time to add the relationship between the Orders table and the Order Details table. As noted earlier, relational databases are built on the idea of one table relating to another. The Orders table relates to the Order Details table in a one-to-many, parent/child relationship; this is often called a master/detail relationship. The relationship is built on the fact that the OrderID is a primary key in Orders and a foreign key in Order Details. Youll expand the data grid to represent both the Orders and the Order Details tables. Note that you only need one dataSet. The idea of a dataSet is to encapsulate a subset of the database, including multiple tables and their relationships to one another. To accomplish this youll need a second command object and a second data adapter. The command object encapsulates the command to the database, and the data adapter is the bridge between the data set and the back-end database. 17. Add two new data members.
private SqlCommand command2; private SqlDataAdapter dataAdapter2;

18. Modify the form1Load event handler to add the new command object. Set the Connection property to the original connection object, and set the CommandText property to a string that will select all the records from Order Details. Place the new code immediately after the call to Fill and before the binding to the DataSource property of dgOrders. 16-26 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

The ADO.NET Object Model


command2 = new System.Data.SqlClient.SqlCommand(); command2.Connection=connection; command2.CommandText = "Select * from [Order details]";

19. Create the new DataAdapter object and set its SelectCommand property to the new Command object you just created. Map the table under the name Details. Then fill the new DataAdapter.
dataAdapter2 = new System.Data.SqlClient.SqlDataAdapter(); dataAdapter2.SelectCommand= command2; dataAdapter2.TableMappings.Add("Table","Details"); dataAdapter2.Fill(dataSet);

You have filled the DataSet with two tables. Each table is mediated by its own DataAdapter, each of which maps a table in the database to a table in the DataSet. Each DataAddapter uses its own command object to encapsulate the select command, but the command objects share a common connection to the database. You are now ready to create the DataRelation object to encapsulate the relationship between the two tables. 20. Create two instances of type System.Data.DataColumn.
DataColumn dataColumn1; DataColumn dataColumn2;

The two data columns will be used to represent the columns that manage the relationship between the two tables: the OrderID columns.

21. You want to assign to the first DataColumn object the OrderID column from the Orders table. To do so, index into the Tables collection of the dataSet, using the string Orders as the index value. The indexer will return a DataTable object that you can assign to a temporary value.
DataTable orderTable = dataSet.Tables["Orders"];

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-27

ADO.NET
22. Index into the Columns collection of the DataTable to retrieve the OrderID column, and assign that to a temporary variable.
DataColumn orderIDColumn = orderTable.Columns["OrderID"];

23. Assign the DataColumn object to dataColumn1.


dataColumn1 = orderIDColumn;

Note that you could have combined Steps 21, 22, and 23 into a single assignment statement.
dataColumn1 = dataSet.Tables["Orders"].Columns["OrderID"];

24. Assign the OrderID column of the Details table to dataColumn2.


dataColumn2 = dataSet.Tables["Details"].Columns["OrderID"];

At this point, dataColumn1 represents the OrderID column from the Orders table, and dataColumn2 represents the OrderID column from the Order Details table. They are the key components of a relationship between the two tables, and you will use them to create a DataRelation object. 25. Create a new DataRelation object and initialize it with the two data columns. The first parameter to the constructor is the name of the relation, passed as a string. You may name the relationship anything you like; for this exercise name the relationship OrdersToDetails.
DataRelation dataRelation = new System.Data.DataRelation( "OrdersToDetails", dataColumn1,

26. Add the Data Relation to the Relations table held by the DataSet.
dataSet.Relations.Add(dataRelation);

16-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

The ADO.NET Object Model


Note that this is a collection of DataRelation objects. The DataSet can model many relationships among its various tables. 27. Bind the DefaultViewManager of the DataSet to the DataSource of the DataGrid. You cant use the Default View of the table, because now you have two tables.
dgOrders.DataSource = dataSet.DefaultViewManager;

28. The DataGrid now has a view on two tables, and it needs to know which is the master table. You tell it by setting its DataMember property.
dgOrders.DataMember = "Orders"; // parent table

29. Run the application. The DataGrid opens as previously shown, but now there is a + symbol next to each order, as shown in Figure 23.

Figure 23. The DataGrid representing the master table.

30. Click on the + symbol next to the fourth record. Quick. A row opens below with a link to the Order Details table, as shown in Figure 24. The link is labeled with the name of the relationship you created in Step 25.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-29

ADO.NET

Figure 24. The DataGrid with a link to the details records.

31. Click on the link. The DataGrid is redrawn with the contents of the details record, as shown in Figure 25. Look closely at the output. The row whose detail you are viewing is shown at the top: OrderID 10285. The fields are summarized and you can scroll through them with the arrow at the end of the list. You can return to the first display by clicking the white arrow in the upper right corner.

Figure 25. The details record.

16-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

The ADO.NET Object Model

Summary
The goal of many real-world programs is to retrieve, display, and update data from a relational database. Relational Databases are organized into tables; each table consists of records (rows) and fields (columns). Relationships among tables are managed through common fields (foreign keys). Normalizing tables removes redundant data and reduces the danger of data corruption. A Primary key uniquely identifies a record in a database table; a Foreign key is a primary key from a different table and is used to establish a relationship between the two tables. A Foreign Key Constraint enforces that you create valid records and disallows invalid deletions and updates based on a foreign-key relationship. Declarative Referential Integrity uses constraints on the database to avoid data corruption. You interact with and manipulate your database using Structured Query Language (SQL). SQL is a declarative language. The principal classes in the ADO.NET object model are the Connection and Command objects and the DataAdapter. These three work together to interact with the DataSet. The job of the Connection object is to encapsulate a connection to the back-end database, while the job of the Command object is to encapsulate a SQL command. The job of the DataAdapter is to decouple the DataSet from the details of interacting with the database. The DataSet represents a subset of the database, complete with multiple tables, and the relationship among the tables. DataSets are disconnected from the database. A DataSet consists of a DataRelation collection and a DataTable collection. The DataTable collection holds DataTable objects, which in turn consist of a DataRow collection, a DataColumn collection, and a Constraint collection. ADO.NET currently offers two managed providers: SQL and OLE DB. You can bind to a data control using the DataSource property. The DataGrid enables you to display the DataRelation encapsulated in a DataRelation object.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-31

ADO.NET

(Review questions and answers on the following pages.)

16-32

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

The ADO.NET Object Model

Questions
1. What does it mean to say that ADO.NET offers a disconnected data set? 2. What is a foreign key constraint? 3. What is normalization? 4. What are DataAdapter objects for? 5. What is the difference between the OLE DB provider and the SQL provider? 6. Why do you pass two DataColumn objects to the constructor of the DataRelation object?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-33

ADO.NET

Answers
1. What does it mean to say that ADO.NET offers a disconnected data set?
The use model with DataSets is that they connect to the Database to be filled and to update the database, but they are otherwise disconnected. Most of the time that you are interacting with the DataSet it is disconnected from the backing database.

2. What is a Foreign Key Constraint?


A foreign key constraint states that there is a relationship between a primary key in one table and that same column, acting as a foreign key, in another, and that the two tables are constrained in the way they add, update or delete records based on that relationship.

3. What is normalization?
Normalization is the process of removing redundant data from tables. The Orders table does not hold the phone number for each customer for each order; rather it holds a customerID and the phone number and other customer-specific data is factored out into the appropriate (customer) table.

4. What are DataAdapter objects for?


DataAdapter objects mediate between a DataSet (which knows about the structure of the data) and the underlying database.

5. What is the difference between the OLE DB provider and the SQL provider?
The SQL Data Provider is optimized for Microsoft SQL Server. To interact with other data sources, use the OLE DB data provider.

6. Why do you pass two DataColumn objects to the constructor of the DataRelation object?
The relationship between two tables will be created by a foreign key relationship. This typically involves matching a column in each of the two tables.

16-34

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

The ADO.NET Object Model

Lab 16: ADO.NET

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-35

Lab 16: ADO.NET

Lab 16 Overview
In this lab youll learn to manipulate databases using the ADO.NET object model. To complete this lab, youll need to work through two exercises: Displaying Customers Displaying Relationships

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

16-36

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Displaying Customers

Displaying Customers
Objective
In this exercise, access the database to retrieve the list of customers, and youll display that list in a list box within a Windows application.

Things to Consider
You cannot display any fields you dont retrieve from the database. There is no need to retrieve fields you wont display. The list box will display a simple string; you must assemble that string from the data you retrieve. Youll need to know the server name, userID, and password for the SQL Server account youre using.

Step-by-Step Instructions
1. Create a new Windows application named Displaying Customers. 2. Drag a list box onto the form and name it lbCustomers. 3. Size the list box and form as shown in Figure 26.

Figure 26. Sizing the list box and form.

4. Double-click on the form to open the Form1_Load method. 5. Add a connection string in it. Be sure to use the correct server name, userID, and password for your SQL Server account.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-37

Lab 16: ADO.NET


string strConnection = "server=YourServer; uid=sa; pwd=YourPassword; database=northwind";

6. Create the command string.


string strCommand = "Select CompanyName, ContactName, ContactTitle from Customers";

7. Create the data adapter.


SqlDataAdapter dataAdapter = new SqlDataAdapter(strCommand, strConnection);

8. Create the data set.


DataSet dataSet = new DataSet();

9. Fill the DataSet object.


dataAdapter.Fill(dataSet,"Customers");

10. Get the first (and only) table from the DataSet.
DataTable dataTable = dataSet.Tables[0];

11. For each row in the table, fill the list.

16-38

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Displaying Customers
foreach (DataRow dataRow in dataTable.Rows) { lbCustomers.Items.Add(dataRow["CompanyName"] + ", Contact: " + dataRow["ContactName"] + " (" + dataRow["ContactTitle"] + ")" );

12. Compile and run the application as shown in Figure 27.

Figure 27. Displaying customers.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-39

Lab 16: ADO.NET

Displaying Relationships
Objective
In this exercise, youll see how table relationships can be reflected within DataSet object, and how you can use a DataGrid object to display these relationships.

Step-by-Step Instructions
1. Create a new Windows Forms project named Data Relations. 2. Drag a data grid onto the form and size it as shown in Figure 28.

Figure 28. Sizing the list box and form.

3. Name the data grid dgRelations. 4. Right-click on the form and select View Code. 5. Add a using statement for System.Data.SqlClient.
using System.Data.SqlClient;

6. Add private member variables for the connection, as well as the command object, the data set, and the data adapter.

16-40

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Displaying Relationships
private SqlConnection connection; private DataSet dataSet; private SqlCommand command; private SqlDataAdapter dataAdapter;

7. Add a second command object and a second dataAdapter.


private SqlCommand command2; private SqlDataAdapter dataAdapter2;

8. Locate the Form1_Load method. In it, create the connection object and open it. Be sure to use the correct server name, userID, and password for your SQL Server account.
string strConnection = "server=YourServer; uid=sa; pwd=YourPassword; database=northwind";

9. Create a new connection object using the connection string.


connection = new System.Data.SqlClient.SqlConnection(connectionString);

10. Open the connection.


connection.Open();

11. Create the DataSet and set the CaseSensitive property.


dataSet = new System.Data.DataSet(); dataSet.CaseSensitive=true;

12. Create the SqlCommand object and assign the connection.


command = new System.Data.SqlClient.SqlCommand(); command.Connection=connection;

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-41

Lab 16: ADO.NET


13. Set the command objects command text to select all the fields from the orders table.
command.CommandText = "Select * from Orders";

14. Create the DataAdapter object and pass in the SQL Command object and establish the table mapping to the orders table.
dataAdapter = new System.Data.SqlClient.SqlDataAdapter(); dataAdapter.SelectCommand= command; dataAdapter.TableMappings.Add("Table","Orders");

15. Tell the DataAdapter object to fill the DataSet.


dataAdapter.Fill(dataSet);

16. Create a second command object, reusing the connection.


command2 = new System.Data.SqlClient.SqlCommand(); command2.Connection=connection;

17. Set the command text to get all fields from the Customers table.
command2.CommandText = "Select * from Customers";

18. Instantiate the second data adapter.


dataAdapter2 = new System.Data.SqlClient.SqlDataAdapter();

19. Set the second data adapters Select command to the second command object.
dataAdapter2.SelectCommand= command2;

20. Map the new table to the Customers table. 16-42 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Displaying Relationships
dataAdapter2.TableMappings.Add("Table","Customers");

21. Fill the dataset with the second dataAdapter.


dataAdapter2.Fill(dataSet);

22. Declare two data column objects.


DataColumn dataColumn1; DataColumn dataColumn2;

23. Create a customerTable DataTable object and initialize it with the Customers table from the DataSet.
DataTable customerTable = dataSet.Tables["Customers"];

24. Assign the CustomerID column in the customerTable DataTable object to dataColumn1.
dataColumn1 = customerTable.Columns["CustomerID"];

25. Assign the customerID column to dataColumn2 in the Orders table.


dataColumn2 = dataSet.Tables["Orders"].Columns["CustomerID"];

26. Instantiate a DataRelation object. Name it CustomerToOrders and pass in the two dataColumn objects.
DataRelation dataRelation = new System.Data.DataRelation( "CustomersToOrders", dataColumn1, dataColumn2);

27. Add the DataRelation object to the Relations collection of the DataSet. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

16-43

Lab 16: ADO.NET


dataSet.Relations.Add(dataRelation);

28. Set the DataSource for the DataGrid to the Default View Manager of the DataSet.
dgRelations.DataSource = dataSet.DefaultViewManager;

29. Set the DataMember property of the DataGrid to the Customers table.
dgRelations.DataMember = "Customers";

30. Compile and run the application as shown in Figure 29.

Figure 29. Displaying relationships.

16-44

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Updating Databases with ADO.NET

Updating Databases with ADO.NET


Objectives
Update the database from changes made in the user interface. Update multiple tables in the database based on user input. Use transactions to support database integrity. Implement transactions with stored procedures. Implement transactions with the Connection object. Use the ACID test to understand transactions.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-1

Updating Databases with ADO.NET

Updating Data
Until now, youve displayed data in various controls but have not modified the data in the database. In many real-world applications, of course, you will want to allow your user to make a purchase, change inventory, update records, or otherwise manipulate your data. ADO.NET provides extensive support for updating data. This can be done quite simply if your application only allows one person at a time to access your data, or it can be quite complex if you need to allow many different people (perhaps in different locations) to update the data at the same time. The steps for a simple update are straightforward: 1. Fill the DataSet from the database. 2. Display the data from the DataSet. 3. Allow the user to indicate the changes to be made. 4. Update the data in the DataSet. 5. Update the data in the database.

Try It Out!
See DataUpdate Working1.sln The easiest way to demonstrate how to update data in the database is to do so in a simple example program. This demonstration program involves a fairly complex user interface, as shown in Figure 1. To make it easier for you to focus on the ADO.NET features rather than on the UI features, weve supplied a starting program, but you are free to write this from scratch if you prefer.

17-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Updating Data

Figure 1. The user interface.

1. Open the project DataUpdateStarter. NOTE Weve provided a starter project (DataUpdateStarter) as well as a completed version (DataUpdateWorking1). As you progress through the chapter youll continue to modify your program. Weve provided files showing the completed interim steps and the names of these files will be shown in the left-hand column.

2. Notice that the starter program includes the using statement:


using System.Data.SqlClient;

3. Notice that the starter program declares three private member variables: a DataAdapter, DataSet, and DataTable.
private SqlDataAdapter dataAdapter; private DataSet dataSet; private DataTable dataTable;

In this exercise youll add event handlers for the form loading and for changing the selection in the list box, as well as for clicking the Update button. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-3

Updating Databases with ADO.NET


4. Double-click on the form to open the event handler Form1_Load. Youll set up your form in two steps. Step 1 is to fill the list box, which youll delegate to a helper method, FillLB(). Factor this out to a separate method, because youll also want to fill the list box after you process the Update buttons click event. Step 2 in the Form1_Load event is to add True and False to the drop-down list for the discontinued field (cbDiscontinued).
private void Form1_Load(object sender, System.EventArgs e) { // call method to fill list box FillLB();

// add true/false to drop down cbDiscontinued.Items.AddRange( new string[2] {"True", "False"}); }

5. In FillLB youll create connection and command strings and use them to create a DataAdapter.
private void FillLB() { // create the connection string string strConnection = "server=YourServer; uid=sa; pwd=YourPW; database=northwind";

// create the command string string strCommand = "Select * from Products";

// create the data set command object and dataset dataAdapter = new SqlDataAdapter(strCommand, strConnection);

6. Complete the FillLB method by creating a new DataSet and filling it from the DataAdapter. Extract the DataTable and use it to fill the list box.

17-4

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Updating Data
dataSet = new DataSet();

// fill the dataset dataAdapter.Fill(dataSet,"Products");

// get the table dataTable = dataSet.Tables[0];

// first, empty the list box lbProducts.Items.Clear();

// for each row in the table, fill the list foreach (DataRow dataRow in dataTable.Rows) { lbProducts.Items.Add(dataRow["ProductName"]); }

7. Run the application. The list box should be filled and the Discontinued drop-down list should be filled with the values True and False. Clicking on a value in the list box has no effect, nor does clicking on Update (see Figure 2).

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-5

Updating Databases with ADO.NET

Figure 2. Testing the Form Load event.

8. When the user clicks on an entry in the list box (e.g., Teatime Chocolate Biscuits), youd like to fill the text fields with the data that corresponds to that record. Return to the designer and double-click on the list box. This creates an event handler for the list box. The default event handler is SelectedIndexChanged, which happens to be just what you want. 9. Get the DataRow from the DataTable that corresponds to the selected index.
DataRow selectedRow = dataTable.Rows[lbProducts.SelectedIndex];

The SelectedIndex property of the list box is the 0-based offset of the row the user clicks on. You use that as an offset into the Rows collection of the table, getting back the corresponding row in the data and assigning that DataRow to the temporary variable selectedRow. 10. The column name can be used as an offset into the selected row to extract the value for that column. You can, assign the value of the ProductName column to the text property of the txtProductName text box.
txtProductName.Text = selectedRow["ProductName"].ToString();

17-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Updating Data
11. Make this kind of assignment for all the remaining text fields.
txtProductID.Text = selectedRow["ProductID"].ToString(); txtSupplierID.Text = selectedRow["SupplierID"].ToString(); txtCategoryID.Text = selectedRow["CategoryID"].ToString(); txtQuantityPerUnit.Text = selectedRow["QuantityPerUnit"].ToString(); txtUnitPrice.Text = selectedRow["UnitPrice"].ToString(); txtUnitsInStock.Text = selectedRow["UnitsInStock"].ToString(); txtUnitsOnOrder.Text = selectedRow["UnitsOnOrder"].ToString(); txtReorderLevel.Text = selectedRow["ReorderLevel"].ToString();

12. You now need to set the value in the cbDiscontinued drop-down list. This one is tricky. You want to set the SelectedIndex property of the drop-down to 0 if the value is true and to 1 if the value is false. Unfortunately, the value in the column is 0 for false and 1 for true, so, you must flip these. Fortunately, the ToString value of 0 is false, so you can use the ternary operator to test it.
cbDiscontinued.SelectedIndex = selectedRow["Discontinued"].ToString() == "False" ? 1 : 0;

The logic of this string is this: assess whether the ToString value of the Discontinued column within the selected row is equal to False; if so, return 1, otherwise return 0. Whatever value is returned, assign that value to the SelectedIndex property of the cbDiscontinued control. 13. Run the program. You should now be able to click on a row and see the corresponding data in the text fields, as shown in Figure 3. In this figure, you can also see the row in the database from which the data is drawn to fill the text boxes. You can see that the 0 in the Discontinued field has appropriately been translated to False in the cbDiscontinued field.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-7

Updating Databases with ADO.NET

Figure 3. Filling the text fields.

14. The next task is to handle the Update button click event. Youll want to formulate an update statement based on the values in the text boxes when the button is clicked. Return to the designer and double-click on the Update button to open the btnUpdate_Click event handler. 15. The first step is to write the message Updating Teatime Chocolate Biscuits to the lblMsg label. To do so, you need the product name of the currently selected row. To get that, youll extract the target row, just as you did in Step 9.
DataRow targetRow =

dataTable.Rows[lbProducts.SelectedIndex];

16. Update the text field of the message and call the static method Application.DoEvents. This method ensures that the UI is updated even if the program is busy processing the next steps in the method.
lbMsg.Text = "Updating " + targetRow["ProductName"]; Application.DoEvents();

17-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Updating Data
17. Having updated the user interface, youre ready to update the database. You will need the value for each of the text fields. To simplify working with the value for the cbDiscontinued drop-down list, first extract that value as an integer, setting the int to 0 if the value is false (the selected index is 1), otherwise setting it to 1.
int dc = cbDiscontinued.SelectedIndex == 1 ? 0 : 1;

18. Create the update statement, extracting values from the various text fields.
string cmd = "update Products set productName = '" + txtProductName.Text.Trim() + "', supplierID = " + txtSupplierID.Text + ", CategoryID = " + txtCategoryID.Text + ", QuantityPerUnit = '" + txtQuantityPerUnit.Text.Trim() + "', UnitPrice = " + txtUnitPrice.Text + ", UnitsInStock = " + txtUnitsInStock.Text + ", UnitsOnOrder = " + txtUnitsOnOrder.Text + ", ReorderLevel = " + txtReorderLevel.Text + ", Discontinued = " + dc.ToString() + " where productID = " + txtProductID.Text.Trim();

19. Update the database. To do so, pass your command string to a helper method youll write in Step 22.
UpdateDB(cmd);

20. With the database updated, update the user interface.


lbMsg.Text = "Updated."; Application.DoEvents();

21. Call FillLB to refill the list box with the newly updated data and close the method.
FillLB(); }

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-9

Updating Databases with ADO.NET


22. Implement the UpdateDB method that you called in Step 19. This method creates a connection to the database and then invokes the command string passed in as a parameter. To begin, create the connection string and instantiate a Connection object with the connection string.
private void UpdateDB(string cmd) { string connectionString = "server=YourServer; uid=sa; pwd=YourPW; database=Northwind";

SqlConnection connection = new SqlConnection(connectionString);

23. Open the connection.


connection.Open();

24. Create a SQLCommand object to encapsulate the update command. Set the commands Connection property to the Connection object you created in Step 22 and set its CommandText property to the command text passed in as a parameter.
SqlCommand command = new SqlCommand(); command.Connection=connection; command.CommandText=cmd;

25. Execute the ExecuteNonQuery method of the command. This is a method specifically designed to be used when your command will not return any records. That is, you are not selecting records, you are updating the database. This method is very efficient.
command.ExecuteNonQuery(); } // end UpdateDB

26. Run the application and scroll to Teatime Chocolate Biscuits. Set the Units In Stock to 24 and click Update. Open the SQL Manager and check the record; youll find it has been updated. The list box is updated as well.

17-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Updating Data
Scroll back to Teatime Chocolate Biscuits and click on it; youll see the new Units In Stock field has been updated, as shown in Figure 4.

Figure 4. Updating the database.

Updating Multiple Tables


In the previous example the CategoryID displayed as an integer. For example, the CategoryID for Teatime Chocolate Biscuits is 3. The value 3, which is retrieved from the Products table, is actually a foreign key into the category. You can see that relationship in a diagram of the two classes. A relationship is drawn between the two classes, as shown in Figure 5. If you hover over the line between the two boxes, the relationship is identified as FK_Products_Categoriesthat is, the foreign key relationship is established between the two tables on the CategoryID field.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-11

Updating Databases with ADO.NET

Figure 5. The relationship between the Products and Categories tables.

Open the Categories table in SQL Enterprise Manager, as shown in Figure 6. You can see that Category 3 is Confections. The second column provides a description, and presumably the Picture column offers a digital picture to be displayed in your user interface.

Figure 6. The Categories table.

A second lookup table like this presents special challenges when updating the records.

17-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Updating Data

Try It Out!
See DataUpdate Working2.sln To see how the second table affects the update of the database, youll return to the previous example but this time youll display the CategoryName in the Category text field, rather than the CategoryID. NOTE This is somewhat artificial; in a real project youd probably have a drop-down list of types to choose among, and then a second UI interface (perhaps a dialog box) to allow the user to modify the categories; but here youll simplify and make the UI a text box on your Details page.

1. Reopen the previous example. 2. Change the CategoryID prompt to say Category and change the name of the text field from txtCategoryID to txtCategory, as shown in Figure 7.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-13

Updating Databases with ADO.NET

Figure 7. Change the txtCategory field.

3. Modify the FillLB method. Change the command string to join the Categories table to the Products table. This will get the Categories fields as well as the Products fields.
string strCommand = "Select * from Products p join Categories c on p.CategoryID = c.CategoryID";

4. Modify the code in lbProducts_SelectedIndexChanged. Set the text for txtCategory to the column Category Name. You get the CategoryName field from the Categories table.

17-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Updating Data
txtCategory.Text = selectedRow["CategoryName"].ToString();

5. Modify the update statement in btnUpdate_Click to remove the update to the CategoryID field.
string cmd = "update Products set productName = '" + txtProductName.Text.Trim() + "', supplierID = " + txtSupplierID.Text + ", QuantityPerUnit = '" + txtQuantityPerUnit.Text.Trim() + "', UnitPrice = " + txtUnitPrice.Text + ", UnitsInStock = " + txtUnitsInStock.Text + ", UnitsOnOrder = " + txtUnitsOnOrder.Text + ", ReorderLevel = " + txtReorderLevel.Text + ", Discontinued = " + dc.ToString() + " where productID = " + txtProductID.Text.Trim();

UpdateDB(cmd);

6. Add a second update statement, this time to update the Categories table.
cmd = "Update Categories set CategoryName = '" + txtCategory.Text.Trim() + "' where CategoryID = " + catID;

UpdateDB(cmd);

7. Run the application. Navigate to Teatime Chocolate Biscuits. Look at the Category; it is set to Confections (which is consistent with the CategoryID of 3 in the previous version). 8. You can now modify the category. Change it from Confections to Yummies and click Update, as shown in Figure 8.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-15

Updating Databases with ADO.NET

Figure 8. Changing the category.

9. Open the Categories table in SQL Server Enterprise Manager. Notice that the CategoryName for CategoryID 3 has been changed to Yummies, as shown in Figure 9.

Figure 9. The underlying database is updated.

17-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Updating Data

Danger Will Robinson!


While the change to the code worked well, you have exposed your program to a serious problem. What happens if the first update cannot be performed for some reason? The way the code is written, the second update (to Categories) may well occur even if the first update (to the other fields) does not. That may be highly undesirable. This brings us to the issue of transactions.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-17

Updating Databases with ADO.NET

Transactions
The idea behind a transaction is simple: you want all of your changes to take effect (updating the Products table and updating the Categories table) or you want none of them to take effect. In the previous example the danger is small; it really doesnt matter that much if the Categories table is updated, even if the Products table is not. The canonical example of where transactions are critical, however, is this: suppose you are writing a banking application. One of the actions youll permit is a transfer of funds. To transfer money from my checking to my savings account, you will take these steps: 1. Issue a database command to add the money into my savings account. 2. Issue a second database command to remove the money from my checking account. You cant do them simultaneously; there are two different tables to update (Checking and Savings). If there is a problem between Steps 1 and 2, someone will be very unhappy. If you add the money to my savings, but never add the money to my checking, the bank loses (Bank error in your favor, collect $50). If you reverse the steps, you run the risk of removing the money from my savings account but never adding it to my checking account; youve just committed a felony. To solve this, you need a way to say that these two steps are really one transaction, and that is where transaction support comes in.

The ACID Test


Database engineers talk about the ACID test for transactions. Transactions must be atomic, consistent, isolated, and durable. Atomic: It is not legal to implement only part of a transaction; the transaction itself is the atomic unit and is indivisible. Consistent: The database must be in a consistent state after the transaction completes. It is legal for the database to be in an inconsistent state during the transaction (e.g., your savings account has diminished but your checking account has not yet received the funds) but when the transaction is complete, the database must be in a consistent state (the accounts balance). Isolated: It must be possible to have simultaneous transactions that do not interfere with one another. One transaction cannot depend on the completion of another; if so, they are really a single transaction.

17-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Transactions
Durable: Once the transaction is committed, the effect must be permanent. It is legal to roll back a transaction before it is committed, but once committed the transaction cannot be undone without a second action (usually, a second transaction). This allows for the creation of audit trails.

Two Flavors
Transaction support in .NET comes in two flavors. SQL Server (and other relational databases) offers transaction support within stored procedures (sprocs). The .NET Connection object offers support for transactions. To use transactions with the Connection object, you call the instance method BeginTransaction, which returns an object of type SqlTransaction (or OLEDBTransaction).

Stored Procedure Transaction Support


A stored procedure is the equivalent of a function that runs SQL statements. Stored procedures can have parameters, and those parameters act as local values within the stored procedure. You write your stored procedure within the database itself. To call a stored procedure in C#, follow these steps: 1. Create the stored procedure in SQL Server (or whatever database you are using). 2. Create a Command object. 3. Set the Command objects text to the name of the stored procedure. 4. Set the Command objects type to the enumerated value Commandtype.StoredProcedure. 5. Call the method Add method on the Parameters collection of the Command object. This returns an object of type Parameter. 6. Set the direction property of the Parameter object to one of the enumerated values Input, Output, or InputOutput. 7. Set the value property of the Parameter to the value you want to pass as a parameter to the stored procedure.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-19

Updating Databases with ADO.NET

Try It Out!
See DataUpdate Working3.sln To see how to use stored procedures and SQL supported transactions, youll modify the previous example to use a stored procedure with transactions to update the Products and Categories tables. 1. Create a stored procedure for updating the Product and Categories tables. Open the SQL Enterprise Manager and navigate to the Northwind database. Right-click on the Stored Procedure and choose New Stored Procedure as shown in Figure 10.

Figure 10. Creating a new stored procedure.

2. Call your procedure spUpdateProductsAndCategories.

17-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Transactions
CREATE PROCEDURE spUpdateProductsAndCategories

3. Give your procedure parameters for the values you want to set.
@ProductID int, @ProductName varChar(40), @SupplierID int, @QuantityPerUnit varChar(20), @UnitPrice money, @UnitsInStock int, @UnitsOnOrder int, @ReorderLevel int, @Discontinued int, @CategoryName varChar(5000), @CategoryID int

4. Begin the stored procedure by opening a transaction.


as begin transaction

5. Update the products table.


Update products set productName = @ProductName, SupplierID = @supplierID, QuantityPerUnit = @QuantityPerUnit, UnitPrice = @UnitPrice, UnitsInStock = @UnitsInStock, UnitsOnOrder = @UnitsOnOrder, ReorderLevel = @ReorderLevel, Discontinued = @Discontinued where productID = @productID

6. Check for an error. If there is an error, go to an error handler that will roll back the transaction. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-21

Updating Databases with ADO.NET


if @@error <> 0 goto ErrorHandler

7. If there is no error, update the Categories table.


Update Categories set CategoryName = @CategoryName where CategoryID = @CategoryID

8. Check for an error. If there is an error, go to an error handler that will roll back the transaction. If there is no error, commit the transaction and exit the stored procedure.
if @@Error <> 0 goto ErrorHandler commit transaction return

9. Write the error handler that will roll back the transaction.
ErrorHandler: rollback transaction return

10. Test the syntax of the stored procedure by clicking the Check Syntax button. A dialog box showing that the syntax check was successful should appear, as shown in Figure 11. If not, correct the error and try again.

17-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Transactions

Figure 11. Checking the syntax.

11. Click OK to close the syntax check dialog box and then click OK to close the New Stored Procedure dialog box. With your stored procedure in place, you are ready to modify your code. Absolutely nothing in your code changes, except for how you update the database. 12. Delete the UpdateDB method. 13. Modify the btnUpdate_Click method. Leave the code that creates the connection string and Connection object and the Command object. Leave the code that assigns the connection to the Command object, but change the command text.
command.CommandText= "spUpdateProductsAndCategories";

You have set the command text to the name of the stored procedure.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-23

Updating Databases with ADO.NET


14. Previously you did not have to set the CommandType, because the default is text, and that is what you wanted. Now, however, you must set this property to indicate that you are not passing in a command but invoking a stored procedure.
command.CommandType = CommandType.StoredProcedure;

15. Create an instance of SqlParameter.


System.Data.SqlClient.SqlParameter param;

16. Assign to that param instance the parameter returned by calling the Add method on the Parameters collection of the Command object. Pass two arguments into the Add method: the name of the first parameter (@ProductID) and the enumerated type of that parameter (SqlDbType.Int).
param = command.Parameters.Add("@ProductID",SqlDbType.Int);

17. Set the Direction and Value properties of the param object.
param.Direction = ParameterDirection.Input; param.Value = txtProductID.Text.Trim();

18. Create a second parameter by calling command.Parameters.Add, and pass in the name and type of the second parameter. Assign the reference returned to the local param instance. Assign the Direction and Value properties.
param = command.Parameters.Add( "@ProductName",SqlDbType.VarChar,40); param.Direction = ParameterDirection.Input; param.Value = txtProductName.Text.Trim();

17-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Transactions
NOTE You may be wondering why it is okay to assign this second parameter to the same instance (params). Was the first parameter lost? Remember that params is just a reference to the parameter that was added to the Parameters collection. The original is still in the collection; you are just using this local variable to refer to the new one so that you can set properties on it.

19. Do the same for the remaining parameters.


param = command.Parameters.Add( "@SupplierID",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.Value = txtSupplierID.Text.Trim();

param = command.Parameters.Add( "@QuantityPerUnit",SqlDbType.VarChar,20); param.Direction = ParameterDirection.Input; param.Value = txtQuantityPerUnit.Text.Trim();

param = command.Parameters.Add( "@UnitPrice",SqlDbType.Money); param.Direction = ParameterDirection.Input; param.Value = txtUnitPrice.Text.Trim();

param = command.Parameters.Add( "@UnitsInStock",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.Value =

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-25

Updating Databases with ADO.NET


txtUnitsInStock.Text.Trim();

param = command.Parameters.Add( "@UnitsOnOrder",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.Value = txtUnitsOnOrder.Text.Trim();

param = command.Parameters.Add( "@ReorderLevel",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.Value = txtReorderLevel.Text.Trim();

param = command.Parameters.Add( "@Discontinued",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.Value = cbDiscontinued.SelectedIndex == 1 ? 0 : 1;

param = command.Parameters.Add( "@CategoryName",SqlDbType.VarChar,15); param.Direction = ParameterDirection.Input; param.Value = txtCategory.Text.Trim();

20. You need to pass the category ID to the stored procedure. You do that by getting the selected row and extracting the category ID. You can then use that to create your final parameter.

17-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Transactions
DataRow targetRow =

dataTable.Rows[lbProducts.SelectedIndex]; int catID = (int) targetRow["CategoryID"]; param = command.Parameters.Add("@CategoryID",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.Value = catID;

21. With all the parameters in place, use ExecuteNonQuery to invoke the stored procedure. Then update the user interface and refill the list box with the newly updated data.
command.ExecuteNonQuery();

// inform the user lbMsg.Text = "Updated."; Application.DoEvents();

FillLB(); } // end btnUpdate_click

22. Run the application. You have delegated responsibility for the transaction support to the database, and the stored procedure will ensure that either both tables are updated or neither table is updated.

Using Connection Transactions


The Connection object provides an alternative to database transaction support. This frees you from the limitations and specifics of the particular database you are using, which makes it easier to change back-end databases later. To convert to connection-based transactions, youll invoke the BeginTransaction method of the Connection object. This returns a reference to a SqlTransaction object. You can then assign that object to the Command objects Transaction property. You will then create a try block, and within that block youll call the various stored procedures to update the records. If the calls to ExecuteNonQuery throw an exception, youll roll back the transaction. Otherwise, once all your calls to

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-27

Updating Databases with ADO.NET


stored procedures complete without an exception, you will commit the transaction. This approach has great advantages. If nothing else, you can catch the exceptions thrown and display information about what went wrong.

Try It Out!
See DataUpdate Working4.sln To see how to use connection-based transactions, youll recreate the previous example using stored procedures without transaction support, and youll add transaction support from the Connection object. 1. Create a new stored procedure named spUpdateProducts.
CREATE PROCEDURE spUpdateProducts @ProductID int, @ProductName varChar(40), @SupplierID int, @QuantityPerUnit varChar(20), @UnitPrice money, @UnitsInStock int, @UnitsOnOrder int, @ReorderLevel int, @Discontinued int as Update products set productName = @ProductName, SupplierID = @supplierID, QuantityPerUnit = @QuantityPerUnit, UnitPrice = @UnitPrice, UnitsInStock = @UnitsInStock, UnitsOnOrder = @UnitsOnOrder, ReorderLevel = @ReorderLevel, Discontinued = @Discontinued where productID = @productID

Notice that this stored procedure does not have transaction support and updates only one table. Youll use this sproc to update the Products table and youll use a simple SQL command to update the Categories table.

17-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Transactions
2. Reopen the project used in the previous example. 3. Once again, create the connection string and Connection object, and open the Connection object.
string connectionString = "server=YourServer; uid=sa; pwd=YourPW; database=Northwind"; SqlConnection connection = new SqlConnection(connectionString); connection.Open();

4. Create the Command object.


System.Data.SqlClient.SqlCommand command = new System.Data.SqlClient.SqlCommand();

5. Call BeginTransaction on the Connection object. This begins the transaction and returns an instance of SqlClient.SqlTransaction that encapsulates the transaction as an object.
SqlTransaction transaction = connection.BeginTransaction();

6. Set the Transaction property of the Command object to the Transaction object returned by BeginTransaction.
command.Transaction = transaction;

7. Set the Connection property of the Command object to the connection youre using.
command.Connection = connection;

8. The Command object is now ready to participate in the transaction that is being mediated by the Connection object. Create a try block in which you will do all the updating. Block out the try block and create the Catch block to roll back the transaction if any errors are reported. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-29

Updating Databases with ADO.NET


try { } catch (Exception ex) { MessageBox.Show( "Exception thrown when updating db: {0}", ex.Message); transaction.Rollback(); }

9. Fill in the Try block by setting the CommandText property of the Command object to the name of the stored procedure you want to invoke.
command.CommandText= "spUpdateProducts";

10. Set the CommandType property to StoredProcedure.


command.CommandType = CommandType.StoredProcedure;

11. You are ready to create the parameters, much as you did in the previous example.
param = command.Parameters.Add("@ProductID",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.Value = txtProductID.Text.Trim();

param = command.Parameters.Add( "@ProductName",SqlDbType.VarChar,40); param.Direction = ParameterDirection.Input; param.Value = txtProductName.Text.Trim();

17-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Transactions

param = command.Parameters.Add( "@SupplierID",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.Value = txtSupplierID.Text.Trim();

param = command.Parameters.Add( "@QuantityPerUnit",SqlDbType.VarChar,20); param.Direction = ParameterDirection.Input; param.Value = txtQuantityPerUnit.Text.Trim();

param = command.Parameters.Add( "@UnitPrice",SqlDbType.Money); param.Direction = ParameterDirection.Input; param.Value = txtUnitPrice.Text.Trim();

param = command.Parameters.Add( "@UnitsInStock",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.Value = txtUnitsInStock.Text.Trim();

param = command.Parameters.Add( "@UnitsOnOrder",SqlDbType.Int); param.Direction = ParameterDirection.Input;

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-31

Updating Databases with ADO.NET


param.Value = txtUnitsOnOrder.Text.Trim();

param = command.Parameters.Add( "@ReorderLevel",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.Value = txtReorderLevel.Text.Trim();

param = command.Parameters.Add( "@Discontinued",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.Value = cbDiscontinued.SelectedIndex == 1 ? 0 : 1;

Notice that you do not include the CategoryName or CategoryID fields. These are not in the stored procedure, because this stored procedure only updates the Products table and not the Categories table. 12. Execute the command (run the stored procedure).
command.ExecuteNonQuery();

If the procedure is unable to complete properly, an exception will be thrown. The exception will be caught in the catch block and the transaction will roll back. Otherwise, you are ready to go on to update the Categories table. 13. Get the target row from the DataTable by indexing into the Rows collection, using the SelectedIndex property of the list box. Use the target row to set a local variable to hold the CategoryID value.
DataRow targetRow =

dataTable.Rows[lbProducts.SelectedIndex]; nt catID = (int) targetRow["CategoryID"];

17-32

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Transactions
14. Create a command string and set that string to the CommandText property of the Command object. Remember to set the CommandType property to the enumerated value CommandType.Text because this time you are executing the query, not passing in the name of a stored procedure.
string cmd = "Update Categories set CategoryName = '" + txtCategory.Text.Trim() + "' where CategoryID = " + catID;

command.CommandText=cmd; command.CommandType = CommandType.Text;

15. Execute the Query.


command.ExecuteNonQuery();

16. If the procedure is unable to complete properly, an exception will be thrown. The exception will be caught in the catch block, and the transaction will roll back. Otherwise, you are ready to commit the transaction.
transaction.Commit();

17. Update the User interface and close the try block. You are finished modifying the btnUpdate_Click method.
lbMsg.Text = "Updated."; Application.DoEvents(); } // end try block

18. Execute the application. There is no apparent change to the performance or behavior, but now you have the security of database-independent transaction support.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-33

Updating Databases with ADO.NET

Summary
You can update the database by using a simple SQL statement, using the ExecuteNonQuery method. You can use multiple statements to update multiple tables, but this raises problems with data corruption. You use transactions to solve the problem of data corruption when updating multiple tables. Transactions can be supported by the database (using stored procedures) or by the Connection object. Transactions must be atomic, consistent, isolated, and durable (ACID). When you support transactions using the Connection object, you catch exceptions thrown when updating the data and you roll back the transaction.

17-34

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Transactions

Questions
1. What is the purpose of the ExecuteNonQuery method? 2. What is the danger in updating two tables without transactions? 3. What is the advantage of connection-based transactions over database transactions? 4. What do the letters ACID stand for? 5. Why do you place connection-based transactions in a try block?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-35

Updating Databases with ADO.NET

Answers
1. What is the purpose of the ExecuteNonQuery method?
ExecuteNonQuery is used when you need to send an SQL statement to the database, but you do not expect to get a recordset back.

2. What is the danger in updating two tables without transactions?


If the update of one table fails, the other may succeed, and the result may be that the database is corrupted.

3. What is the advantage of connection-based transactions over database transactions?


Connection-based transactions free you from the specific syntax of the database, allowing you to change databases more easily if you need to. Connection-based transactions are guaranteed to work no matter what database technology is used to store your data.

4. What do the letters ACID stand for?


The ACID acronym stands for atomic, consistent, independent, and durable. Each transaction must work or fail as a whole (atomic); it must leave the database in a consistent state when it completes; it must be independent of all the other transactions; and the results of the transaction must be permanent (durable).

5. Why do you place connection-based transactions in a try block?


If any part of the update fails, an exception will be thrown and the transaction will be rolled back.

17-36

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Transactions

Lab 17: Updating Databases with ADO.NET

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-37

Lab 17: Updating Databases with ADO.NET

Lab 17 Overview
In this lab youll learn to provide transaction support for your database updates. To complete this lab, youll need to work through two exercises: Providing Transaction Support with SQL How the Connection Object Provides Transaction Support

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

17-38

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Providing Transaction Support with SQL

Providing Transaction Support with SQL


Objective
In this exercise, youll use a stored procedure (sproc) with transaction support to update a database.

Things to Consider
Transaction support in the sproc means that you do not have to provide transaction support in your code. If you are updating more than one table, you want either both updates to occur or neither to occur. Each parameter must be marked with a direction (e.g., ParameterDirection.Input) and a value.

Step-by-Step Instructions
1. Create a stored procedure called spUpdateProductsAndCategoriesTransactions using SQL Server Enterprise Manager. To do this perform the following steps: a. Open SQL Server Enterprise Manager. b. Drill down the hierarchical tree structure in the left pane by clicking on the plus signs until the Northwind database is visible. c. Drill down from the Northwind database one more level to Stored Procedures. d. Click on Stored Procedures. There will be a list of stored procedures visible in the right pane, as shown in Figure 12.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-39

Lab 17: Updating Databases with ADO.NET

Figure 12. Stored procedures in Enterprise Manager.

e. Right-click on Stored Procedure in the left pane and select New Stored Procedure. f. A Stored Procedure Properties dialog box will appear with a large text editing window containing the following boilerplate code:

CREATE PROCEDURE [OWNER].[PROCEDURE NAME] AS

g. Replace that boilerplate code with the following code:


CREATE PROCEDURE spUpdateProductsAndCategoriesTransactions @ProductID int, @ProductName varChar(40), @UnitPrice money, @CategoryName varChar(5000), @CategoryID int as

17-40

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Providing Transaction Support with SQL


begin transaction Update products set productName = @ProductName, UnitPrice = @UnitPrice where productID = @productID

if @@error <> 0 goto ErrorHandler

Update Categories set CategoryName = @CategoryName where CategoryID = @CategoryID

if @@Error <> 0 goto ErrorHandler commit transaction return

ErrorHandler: rollback transaction return

NOTE

To save you from typing, this code is provided for you in the file spUpdateProductsAndCategoriesTransactions.sql. h. Click on the Check Syntax button to verify that there are no syntax errors. i. Click the OK button to save the new sproc. The new sproc will now be visible in the list of sprocs in the right pane.

2. Open the file named TransactionSupportStarter.sln in the TransactionSupportStarter folder. 3. Add member variables for a SQLDataAdapter, a DataSet, and a DataTable.
private SqlDataAdapter dataAdapter; private DataSet dataSet; private DataTable dataTable;

4. Read through the FillLB method that has been created for you.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-41

Lab 17: Updating Databases with ADO.NET


5. Implement the SelectedIndexChanged method for the list box.
private void lbProducts_SelectedIndexChanged( object sender, System.EventArgs e) {

6. Create a DataRow object and initialize it with the DataRow that corresponds to the selected index in the list box.
DataRow selectedRow = dataTable.Rows[lbProducts.SelectedIndex];

7. Fill the text fields from the row.


txtProductName.Text = selectedRow["ProductName"].ToString(); txtProductID.Text = selectedRow["ProductID"].ToString(); txtCategory.Text = selectedRow["CategoryName"].ToString(); txtUnitPrice.Text = selectedRow["UnitPrice"].ToString();

8. Implement the event handler for clicking on the Update button.


private void btnUpdate_Click( object sender, System.EventArgs e) {

9. Create a connection string to connect to the Northwind Database. Be sure to use the correct server name, userID, and password for your SQL Server account.
string connectionString = "server=YourServer; uid=sa; pwd=YourPassword; database=Northwind";

10. Create a connection object and initialize it with a connection string. Then open it.

17-42

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Providing Transaction Support with SQL


System.Data.SqlClient.SqlConnection connection = new System.Data.SqlClient.SqlConnection( connectionString); connection.Open();

11. Create the command object.


System.Data.SqlClient.SqlCommand command = new System.Data.SqlClient.SqlCommand();

12. Set the command connection property to the connection.


command.Connection = connection;

13. Set the command objects command text to the name of the sproc, which is spUpdateProductsAndCategoriesTransactions.
command.CommandText= "spUpdateProductsAndCategoriesTransactions";

14. Set the command type property of the command object to CommandType.StoredProcedure.
command.CommandType = CommandType.StoredProcedure;

15. Instantiate a parameter object.


System.Data.SqlClient.SqlParameter param;

16. Review how the parameters are set in the code already provided in the starter file. 17. Extract the target row into a local DataRow object.
DataRow targetRow =

dataTable.Rows[lbProducts.SelectedIndex];

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-43

Lab 17: Updating Databases with ADO.NET


18. Extract the CategoryID from the row you just obtained.
int catID = (int) targetRow["CategoryID"];

19. Create the parameter for CategoryID, set the direction, and assign the value you just obtained.
param = command.Parameters.Add("@CategoryID",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.Value = catID;

20. Execute the command with ExecuteNonQuery.


command.ExecuteNonQuery();

21. Update the label. Remember to call DoEvents.


lbMsg.Text = "Updated."; Application.DoEvents();

22. Call FillLB to refill the list box.


FillLB();

23. Compile and run the application as shown in Figure 13.

17-44

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Providing Transaction Support with SQL

Figure 13. The final results from using transaction support with SQL.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-45

Lab 17: Updating Databases with ADO.NET

How the Connection Object Provides Transaction Support


Objective
In this exercise, youll provide transaction support using the .NET Frameworks Connection object.

Things to Consider
If the connection object provides transaction support you do not need to depend on transaction support from the database. If you do not encounter an exception, you are safe to commit the transaction after all the steps are completed. If you do encounter an exception at any time after you start updating the database, youll want to roll back the transaction. You can make direct updates to the database without using a stored procedure at all.

Step-by-Step Instructions
1. First create a stored procedure called spUpdateProductsAndCategoriesWithoutTransactions using SQL Server Enterprise Manager. To do this perform the following steps: a. Open SQL Server Enterprise Manager. b. Drill down the hierarchical tree structure in the left pane by clicking on the plus signs until the Northwind database is visible. c. Drill down from the Northwind database one more level to Stored Procedures. d. Click on Stored Procedures. There will be a list of stored procedures visible in the right pane, as shown in Figure 14.

17-46

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

How the Connection Object Provides Transaction Support

Figure 14. The list of stored procedures.

e. Right-click on Stored Procedure in the left pane and select New Stored Procedure. f. A Stored Procedure Properties dialog box will appear with a large text editing window containing the following boilerplate code:

CREATE PROCEDURE [OWNER].[PROCEDURE NAME] AS

g. Replace that boilerplate code with the following code:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-47

Lab 17: Updating Databases with ADO.NET


CREATE PROCEDURE spUpdateProductsAndCategoriesWithoutTransactions @ProductID int, @ProductName varChar(40), @UnitPrice money as Update products set productName = @ProductName, UnitPrice = @UnitPrice where productID = @productID

h. When you are done, click on the Check Syntax button to verify that there are no syntax errors. i. Click the OK button to save the new sproc. The new sproc will now be visible in the list of sprocs in the right pane. To save you from typing, this stored procedure is available in the file spUpdateProductsAndCategoriesWithoutTransactions.slq.

NOTE

2. Open the file \ConnectionTransactionSupportStarter\ ConnectionTransactionSupportStarter.sln. 3. Implement the Update button event handler.
private void btnUpdate_Click(object sender, System.EventArgs e) {

4. Create the connection string. Be sure to use the correct server name, userID, and password for your SQL Server account.
string connectionString = "server=YourServer; uid=sa; pwd=YourPassword; database=Northwind";

5. Create the connection object and pass in the connection string.

17-48

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

How the Connection Object Provides Transaction Support


System.Data.SqlClient.SqlConnection connection = new System.Data.SqlClient.SqlConnection(connectionString);

6. Open the connection object.


connection.Open();

7. Create a command object.


System.Data.SqlClient.SqlCommand command = new System.Data.SqlClient.SqlCommand();

8. Create an SQLTransaction object.


SqlTransaction transaction = connection.BeginTransaction();

9. Attach the transaction to the command.


command.Transaction = transaction;

10. Set the commands connection property to the connection.


command.Connection = connection;

11. Set the command text of the command object to spUpdateProductsAndCategoriesWithoutTransactions.


command.CommandText= "spUpdateProductsAndCategoriesWithoutTransactions";

12. Set the command type of the command object to stored procedure.
command.CommandType = CommandType.StoredProcedure;

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-49

Lab 17: Updating Databases with ADO.NET


13. Instantiate an SQLParameter object.
System.Data.SqlClient.SqlParameter param;

14. The parameters have been created for you in the starter file. Review these before going to the next step. 15. Execute the update using ExecuteNonQuery.
command.ExecuteNonQuery();

16. Get the selected row.


DataRow targetRow =

dataTable.Rows[lbProducts.SelectedIndex];

17. Extract the CategoryID.


int catID = (int) targetRow["CategoryID"];

18. Create a command string with the category name and id to update the Categories table.
string cmd = "Update Categories set CategoryName = '" + txtCategory.Text.Trim() + "' where CategoryID = " + catID;

19. Set the command objects command text to the string you just created.
command.CommandText=cmd;

20. Set the CommandType to CommandType.Text.


command.CommandType = CommandType.Text;

17-50

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

How the Connection Object Provides Transaction Support


21. Execute the update by calling ExecuteNonQuery.
command.ExecuteNonQuery();

22. Commit the transaction.


transaction.Commit();

23. Update the lblMsg label and call DoEvents.


lbMsg.Text = "Updated."; Application.DoEvents();

24. Catch exceptions and display the message in a MessageBox. Also roll back the transaction.
catch (Exception ex) { MessageBox.Show( "Exception thrown when updating db: {0}", ex.Message); transaction.Rollback(); }

25. Call FillLB to fill the list box.


FillLB();

26. Compile and run the application as shown in Figure 15.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

17-51

Lab 17: Updating Databases with ADO.NET

Figure 15. The final result of using transaction support with SQL.

17-52

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Advanced ADO Data Update

Advanced ADO Data Update


Objectives
Update DataSets independently of the database. Update the database with changed records from a DataSet. Handle concurrency issues in data updating. Use the Command Builder to simplify concurrency checking.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-1

Advanced ADO Data Update

DataSet Updates
Until now, youve updated the database every time you updated any data at all. This works, but it does not take advantage of your disconnected DataSet object. The goal with a disconnected representation of the database is to allow you to work with the data for a long time before going back to the database to resynchronize the data. An alternative model to updating the database each time the user updates a record is to store all the changes in the DataSet, and then, at a specific time, or in response to a specific user request, you can update the database with all the changes at once.

Try It Out!
See DataUpdate Working5.sln To see how to update the DataSet with multiple changes and then update the DataBase from the modified DataSet, you will modify the previous example. 1. Reopen the previous example. 2. Remove the Update and Delete buttons and replace them with new buttons, btnUpdateDataSet, with the text Update DataSet, and btnUpdateDB with the text Update DB, as shown in Figure 1.

Figure 1. Modified update buttons.

18-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

DataSet Updates
3. Break up the FillLB method, separating the code to get and fill the DataTable from the code to display the list box.
private void FillLB() { // create the connection string string strConnection = "server=YourServer; uid=sa; pwd=YourPW; database=northwind";

// create the command string string strCommand = "Select * from Products";

// create the data set command object and dataset dataAdapter = new SqlDataAdapter(strCommand, strConnection);

// extract the DataTable dataTable = dataSet.Tables[0];

// Call the new ShowLB method ShowLB();

4. Create the ShowLB method to iterate over the DataTable (which you will remember is a member variable of your form) and fill the list box, row by row. This code is unchanged from how it worked previously in FillLB.
private void ShowLB() { lbProducts.Items.Clear();

foreach (DataRow dataRow in dataTable.Rows) { lbProducts.Items.Add(dataRow["ProductName"]); } }

5. Return to the designer, and double-click on btnUpdateDataSet to create the btnUpdateDataSet_Click event handler. The job of this event handler C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-3

Advanced ADO Data Update


is to record the users changes to the current record in the dataset, but not to update the database itself. The code is straightforward: you get the selected row and then set its columns based on the values in the text boxes. You then redisplay the list box (without getting the data from the database!).
private void btnUpdateDataSet_Click( object sender, System.EventArgs e) { // Get the selected row DataRow selectedRow = dataTable.Rows[lbProducts.SelectedIndex];

// Fill the columns in the DataRow from the // text boxes in the dialog box selectedRow["ProductName"] = txtProductName.Text; selectedRow["ProductID"] = txtProductID.Text; selectedRow["SupplierID"] = txtSupplierID.Text; selectedRow["CategoryName"] = txtCategory.Text; selectedRow["QuantityPerUnit"] = txtQuantityPerUnit.Text; selectedRow["UnitPrice"] = txtUnitPrice.Text; selectedRow["UnitsInStock"] = txtUnitsInStock.Text; selectedRow["UnitsOnOrder"] = txtUnitsOnOrder.Text; selectedRow["ReorderLevel"] = txtReorderLevel.Text; selectedRow["Discontinued"] = cbDiscontinued.SelectedIndex == 0 ? 1 : 0;

// show the list box from the DataSet ShowLB(); }

The DataRow object you create (selectedRow) is a reference to the DataRow held in the Rows collection of the DataTable, which in turn is a reference to the DataTable in the DataSet. So, the changes that you make to that row (by setting its columns) are changed in the DataSet. When you call ShowLB the list box is filled from the now modified DataSet. 6. Return to the Designer and double-click on btnUpdateDB to create the btnUpdateDB_Click event handler. The job of this method is to update the database with all the changes made in the DataSet. 18-4 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

DataSet Updates
This is a non-trivial exercise and youll examine this method step-by-step. The first step is to create the connection string and connection object and to open the connection, as you did previously:
string connectionString = "server=YourServer; uid=sa; pwd=YourPW; database=Northwind"; SqlConnection connection = new SqlConnection(connectionString); connection.Open();

7. Create a Command object, call BeginTransaction, and set the Commands Connection property as you did previously.
System.Data.SqlClient.SqlCommand command = new System.Data.SqlClient.SqlCommand();

SqlTransaction transaction = connection.BeginTransaction();

command.Transaction = transaction; command.Connection = connection;

8. Set the CommandText to the name of yet another stored procedure spUpdateProductsAndCategoriesNoTransactions,and set the CommandType property of the Command object to CommandType.StoredProcedure.
command.CommandText= "spUpdateProductsAndCategoriesNoTransactions"; command.CommandType = CommandType.StoredProcedure;

9. Use SQL Server Enterprise Manager to create the new stored procedure.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-5

Advanced ADO Data Update


CREATE PROCEDURE spUpdateProductsAndCategoriesNoTransactions @ProductID int, @ProductName varChar(40), @SupplierID int, @QuantityPerUnit varChar(20), @UnitPrice money, @UnitsInStock int, @UnitsOnOrder int, @ReorderLevel int, @Discontinued int, @CategoryName varChar(5000), @CategoryID int as

Update products set productName = @ProductName, SupplierID = @supplierID, QuantityPerUnit = @QuantityPerUnit, UnitPrice = @UnitPrice, UnitsInStock = @UnitsInStock, UnitsOnOrder = @UnitsOnOrder, ReorderLevel = @ReorderLevel, Discontinued = @Discontinued where productID = @productID

Update Categories set CategoryName = @CategoryName where CategoryID = @CategoryID

You can see this time that you are updating both tables in a single stored procedure. The transaction support is provided by the connection, not the datatabase. 10. Create the parameters. This is somewhat different this time, because you want to update all the rows that changed in the DataSet. Fortunately, the DataAdapter object provides extensive support for this. Heres how youll do it: a. Create a stored procedure for the update (you did this in Step 9).

18-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

DataSet Updates
b. Fill in the parameters. c. Set the Parameter objects source column to the appropriate column. d. Set the Parameters SourceVersion to the enumerated value DataRowVersion.Current. Youll see more about this parameter later in this chapter. e. Set the DataAdapters UpdateCommand property to the Command object youve created. f. Set the DataAdapters Transaction property to the transaction object youve created.

g. Call Update on the DataAdapter. Youll get back the number of rows that are updated. They are all updated at once; you do not have to create a loop to manually iterate over the changes. Only the rows that have changed are updated! h. Commit the transaction and update the User Interface. Youve already created the stored procedure, you now need to fill in the parameters. Start with the first, ProductID. As youve done before, create this by calling the Add method on the Parameters collection of the Command object. Set the direction to the enumerated value Input.
param = command.Parameters.Add("@ProductID",SqlDbType.Int); param.Direction = ParameterDirection.Input;

Rather than setting the Value property, now you set the SourceColumn value. This allows the DataAdapter to know where to get the value for each record that has changed. The DataSet keeps various values for each column, including the current value (set when you updated the DataSet) and the original value (set when you originally got the values from the database). Youll see how to use the original value later in this chapter, for now you want the current value; you set that with the enumerated value DataRowVersion.Current.
param.SourceColumn = "ProductID"; param.SourceVersion=DataRowVersion.Current;

Set the remaining parameters accordingly:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-7

Advanced ADO Data Update


param = command.Parameters.Add( "@ProductName",SqlDbType.VarChar,40); param.Direction = ParameterDirection.Input; param.SourceColumn = "ProductName"; param.SourceVersion=DataRowVersion.Current;

param = command.Parameters.Add( "@SupplierID",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.SourceColumn = "SupplierID"; param.SourceVersion=DataRowVersion.Current;

param = command.Parameters.Add( "@QuantityPerUnit",SqlDbType.VarChar,20); param.Direction = ParameterDirection.Input; param.SourceColumn = "QuantityPerUnit"; param.SourceVersion=DataRowVersion.Current;

param = command.Parameters.Add( "@UnitPrice",SqlDbType.Money); param.Direction = ParameterDirection.Input; param.SourceColumn = "UnitPrice"; param.SourceVersion=DataRowVersion.Current;

param = command.Parameters.Add( "@UnitsInStock",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.SourceColumn = "UnitsInStock"; param.SourceVersion=DataRowVersion.Current;

param = command.Parameters.Add( "@UnitsOnOrder",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.SourceColumn = "UnitsOnOrder"; param.SourceVersion=DataRowVersion.Current;

18-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

DataSet Updates
param = command.Parameters.Add( "@ReorderLevel",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.SourceColumn = "ReorderLevel"; param.SourceVersion=DataRowVersion.Current;

param = command.Parameters.Add( "@Discontinued",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.SourceColumn = "Discontinued"; param.SourceVersion=DataRowVersion.Current;

param = command.Parameters.Add( "@CategoryName",SqlDbType.VarChar,15); param.Direction = ParameterDirection.Input; param.SourceColumn = "CategoryName"; param.SourceVersion=DataRowVersion.Current;

param = command.Parameters.Add( @CategoryID",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.SourceColumn = "CategoryID"; param.SourceVersion=DataRowVersion.Current;

11. Set the UpdateCommand of the DataAdapter to the Command object, created in Step 7.
dataAdapter.UpdateCommand = command;

12. Set the Transaction property of the DataAdapters UpdateCommand to the Transaction object created in Step 7.
dataAdapter.UpdateCommand.Transaction = transaction;

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-9

Advanced ADO Data Update


13. You are ready to request that the DataAdapter update the DataSet. If all the modified data rows are successfully updated, the number of rows updated is returned and you can commit the transaction and update the user interface.
try { int rowsUpdated = dataAdapter.Update(dataSet,"Products"); transaction.Commit(); MessageBox.Show(rowsUpdated + " rows updated!" ); }

14. If there is a problem updating the database, an exception is thrown and your catch block will roll back the transaction.
catch { transaction.Rollback(); }

15. Run the application. Scroll down to Teatime Chocolate Biscuits. Change the Units in Stock to 25 and click Update Dataset. Scroll back to the Teatime Chocolate Biscuits and click on it. Notice that the Units In Stock is set to 25. Open the SQL Server table and you can see that the UnitsInStock has not been changed, as shown in Figure 2.

18-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

DataSet Updates

Figure 2. Updating the DataSet without updating the database.

16. Go on to the next record, Sir Rodneys Marmalade. Change the Units In Stock to 39 and click UpdateDataset. Again, scroll to Sir Rodneys Marmalade and click on it. You see that the Units In Stock is reported as 39, but if you check the database, the value has not been decremented, as shown in Figure 3.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-11

Advanced ADO Data Update

Figure 3. Updating a second record in the DataSet.

17. Update the database by clicking UpdateDB. The program reports that it was able to update the two modified rows, as shown in Figure 4.

Figure 4. Reporting the update.

18. Scroll back to Teatime ChocolateBiscuits, and compare the values in the DataSet with the values in the database. You should see that the DataSet now agrees with the database values, as shown in Figure 5.

18-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

DataSet Updates

Figure 5. Updating the database.

This is a far more efficient way to work with the database. Update the dataset locally, disconnected from the database, and then when youve finished modifying your records update the backing database.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-13

Advanced ADO Data Update

Concurrency
Until now youve written these example programs as if there would be only one user updating the database at a time. In most real-world applications, however, the potential exists for many users to update the database simultaneously.

Overwriting Data
The risk of overwriting data creates a significant challenge for you as the programmer. It is possible that one users updates to the database could overwrite changes made by a second user. For example, assume that the database is a column in a table whose current value is 20. Two users (Workstation1 and Workstation2) read that value, as shown in Figure 6.

Figure 6. Two users read a value.

Once theyve read the data, they both modify the data locally (in their own datasets or in-memory representation) as shown in Figure 7.

Figure 7. Values modified in each computer.

WorkStation 1 writes the modified data back to the database. The value in the database is now 25, as shown in Figure 8.

18-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Concurrency

Figure 8. Modifying the data from Workstation1.

Unfortunately, when Workstation2 writes its modified data (30), it will overwrite the data written by Workstation1.

Concurrency Checking
To prevent this kind of overwriting, you want Workstation2 to notice that the value in the database has been changed by Workstation1. The easiest way to do this is to check to make sure the value has not changed, just before writing the update. For example, to update the UnitsInStock field for a given row (where ProductID = 1) you might create a SQL statement along the lines of:
Update Products set UnitsInStock = 30 where ProductID = 1 and UnitsInStock = 20

That is, you want to update the UnitsInStock column with the new value (30) when the productID matches (the ID of the row youre updating) and when the original value is still in place (e.g., UnitsInStock = 20). If someone else changed the UnitsInStock since you read it, the where statement will fail and the row will not be updated. That is just what you want; you will then notice that the row was not updated and ask the user how to handle the conflict.

Try It Out!
See DataUpdate Working6.sln You can see how to implement this kind of concurrency testing by modifying the stored procedure and code from the previous example. 1. The code you will modify is in the btnUpdateDB_Click method. By the time this code runs, youll have read the data into the DataSet. Your update will depend on the fact that these values have not changed since you read them from the database. To test this, youll modify the where clause in your stored procedure. Create a new stored procedure called spUpdateProdcutsAndCategoriesConcurrent. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-15

Advanced ADO Data Update


CREATE PROCEDURE spUpdateProductsAndCategoriesConcurrent @ProductID int, @ProductName varChar(40), @SupplierID int, @QuantityPerUnit varChar(20), @UnitPrice money, @UnitsInStock int, @UnitsOnOrder int, @ReorderLevel int, @Discontinued int, @CategoryName varChar(5000), @CategoryID int,

@OldProductName varChar(40), @OldSupplierID int, @OldQuantityPerUnit varChar(20), @OldUnitPrice money, @OldUnitsInStock int, @OldUnitsOnOrder int, @OldReorderLevel int, @OldDiscontinued int, @OldCategoryName varChar(5000)

as Update products set productName = @ProductName, SupplierID = @supplierID, QuantityPerUnit = @QuantityPerUnit, UnitPrice = @UnitPrice, UnitsInStock = @UnitsInStock, UnitsOnOrder = @UnitsOnOrder, ReorderLevel = @ReorderLevel, Discontinued = @Discontinued where productID = @productID and productName = @OldProductName and SupplierID = @OldsupplierID and QuantityPerUnit = @OldQuantityPerUnit and

18-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Concurrency
UnitPrice = @OldUnitPrice and UnitsInStock = @OldUnitsInStock and UnitsOnOrder = @OldUnitsOnOrder and ReorderLevel = @OldReorderLevel and Discontinued = @OldDiscontinued

if @@RowCount > 0 begin Update Categories set CategoryName = @CategoryName where CategoryID = @CategoryID and CategoryName = @OldCategoryName end

The key difference in this new stored procedure is that you add parameters for the old (original) values for each field, and you check in the where clause that these values have not been modified. 2. Modify the code from the previous example to call the new stored procedure.
command.CommandText= "spUpdateProductsAndCategoriesConcurrent";

The parameters are the same as in the previous example, except that this time you must also pass in the original values for the parameters whose names begin with Old. Fortunately, the DataRow object supports this; you can get the original value by setting the SourceVersion property of the Parameter to DataRowVersion.Original. Consider the parameter for the ProductName column. The code is:
param = command.Parameters.Add( "@ProductName",SqlDbType.VarChar,40); param.Direction = ParameterDirection.Input; param.SourceColumn = "ProductName"; param.SourceVersion=DataRowVersion.Current;

This is identical to what you had in the previous example. For the OldProductName parameter you will use the same code (setting the parameter name appropriately) but rather than using the DataRowVersion.Current you C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-17

Advanced ADO Data Update


will instead use DataRowVersion.Original. That is, you are telling the Parameter object to get is value from the ProductName column, but to use the original value rather than the updated value:
param = command.Parameters.Add( "@OldProductName",SqlDbType.VarChar,40); param.Direction = ParameterDirection.Input; param.SourceColumn = "ProductName"; param.SourceVersion=DataRowVersion.Original;

You will do this for all the Old parameters:


param = command.Parameters.Add( "@OldSupplierID",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.SourceColumn = "SupplierID"; param.SourceVersion=DataRowVersion.Original;

param = command.Parameters.Add( "@OldQuantityPerUnit",SqlDbType.VarChar,20); param.Direction = ParameterDirection.Input; param.SourceColumn = "QuantityPerUnit"; param.SourceVersion=DataRowVersion.Original;

param = command.Parameters.Add( "@OldUnitPrice",SqlDbType.Money); param.Direction = ParameterDirection.Input; param.SourceColumn = "UnitPrice"; param.SourceVersion=DataRowVersion.Original;

param = command.Parameters.Add( "@OldUnitsInStock",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.SourceColumn = "UnitsInStock"; param.SourceVersion=DataRowVersion.Original;

param = command.Parameters.Add( "@OldUnitsOnOrder",SqlDbType.Int);

18-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Concurrency
param.Direction = ParameterDirection.Input; param.SourceColumn = "UnitsOnOrder"; param.SourceVersion=DataRowVersion.Original;

param = command.Parameters.Add( "@OldReorderLevel",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.SourceColumn = "ReorderLevel"; param.SourceVersion=DataRowVersion.Original;

param = command.Parameters.Add( "@OldDiscontinued",SqlDbType.Int); param.Direction = ParameterDirection.Input; param.SourceColumn = "Discontinued"; param.SourceVersion=DataRowVersion.Original;

param = command.Parameters.Add( "@OldCategoryName",SqlDbType.VarChar,15); param.Direction = ParameterDirection.Input; param.SourceColumn = "CategoryName"; param.SourceVersion=DataRowVersion.Original;

3. These are the only changes, which means you are ready to test the application. To do so, open the File Explorer and navigate to the directory in which you saved your project. Double-click on the bin directory and then the debug directory. You should find the .exe file for your program. Double click on it, your application opens. Double-click on it again to open a second version, as shown in Figure 9. The two versions can point to the same data.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-19

Advanced ADO Data Update

Figure 9. Open two instances of the program.

4. Click on Teatime Chocolate Biscuits in both versions and set the Units In Stock to 30 in one and to 40 in the second. Click Update Dataset in both to update their respective DataSets as shown in Figure 10.

Figure 10. Updating the DataSet with different values.

5. Update the DataBase from the first (left-hand) instance. The UnitsInStock value is updated to 30, and the program reports that one row was updated. 6. Update the Database from the second (right-hand) instance. The program reports a concurrency error as shown in Figure 11. This concurrency error is displayed because the where clause has failed and an exception was raised because the data could not be updated. 18-20 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Concurrency

Figure 11. Reporting the concurrency error.

Resolving the Concurrency Error


In this example, the program simply reports the error, but you can certainly imagine extending the program to offer the user more useful choices, such as being able to overwrite with the new data or reload the data currently in the database.

Command Builder
In the previous example, you went to a great deal of work to build the parameters for the stored procedure, hand filling each parameter with both the new version and the original version of the values. The .NET Frameworks provides astonishing support for this kind of concurrency checking, but only if you meet certain stringent conditions. All the rows you are updating must come from a single table. The table must have a unique key. The unique key must be returned by the query used to fill the table. The name of the table must have no spaces, periods, quotes, or other special characters.

The one constraint that is truly limiting is the first: you can only use the builtin concurrency support if you are working with rows from a single table. That said, if you do meet this criterion, the support is incredible. The Base Class Library offers a class SqlCommandBuilder that takes a DataAdpater in its constructor. The CommandBuilder has methods that can automatically generate the Command objects needed for Updating, Deleting, and Inserting new records into the database. This saves you from creating stored procedures and all the myriad parameters; you can just generate the necessary command objects and let the DataAdapter take care of managing the update!

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-21

Advanced ADO Data Update

Try It Out!
See DataUpdate Working7.sln To see the extent of this support youll modify the previous example to fill the dialog box from a single table: products. 1. Modify the User Interface to display the CategoryID rather than the Category Name. Change the label and rename the text box txtCategoryID. 2. Modify lbProducts_SelectedIndexChanged to use the new text box.
txtCategoryID.Text = selectedRow["CategoryID"].ToString();

3. Modify the event handler btnUpdateDataSet_Click to use the modified text field.
selectedRow["CategoryID"] = txtCategoryID.Text;

4. Modify the FillLB method to select every column from the Products table and fill the DataSet with a single table: Products.
string strConnection = "server=YourServer; uid=sa; pwd=YourPW; database=northwind";

// select every column from Products string strCommand = "Select * from Products";

dataAdapter = new SqlDataAdapter(strCommand, strConnection);

dataSet = new DataSet();

dataAdapter.Fill(dataSet,"Products");

5. Create an instance of SqlCommandBuilder and pass in the DataAdapter.

18-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Concurrency
SqlCommandBuilder bldr = new SqlCommandBuilder(dataAdapter);

6. Use the CommandBuilder to generate the Update, Delete, and Insert commands, and assign these Command objects to the appropriate properties of the DataAdapter.
dataAdapter.UpdateCommand = bldr.GetUpdateCommand(); dataAdapter.DeleteCommand = bldr.GetDeleteCommand(); dataAdapter.InsertCommand = bldr.GetInsertCommand();

7. Complete the FillLB method to initialize the DataTable and call ShowLB.
dataTable = dataSet.Tables[0];

ShowLB(); } // end FillLB

8. Modify the btnUpdateDB_Click method. Rather than invoking a stored procedure, just call Update on the DataAdapter from within a try block. The DataAdapter will take care of the work for you!
private void btnUpdateDB_Click( object sender, System.EventArgs e) {

try { int rowsUpdated = dataAdapter.Update(dataSet,"Products"); MessageBox.Show(rowsUpdated + " rows updated!" ); } catch { MessageBox.Show("Concurrency error!" ); } FillLB(); }

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-23

Advanced ADO Data Update


9. Start up two instances of the program by navigating to the executable file in the bin/debug subdirectories of the project directory, as shown in Figure 12.

Figure 12. Side-by-side instances of the modified program.

10. Change the Units In Stock number to 40 in the first and to 50 in the second. Click Update Dataset in both to update their respective DataSet objects. 11. Click Update.DB on the first and receive the message that one row was updated. 12. Click Update DB on the second and receive the message that there was a concurrency error. The new version is far simpler than the version in which you filled in the parameters for the update by hand. The Command Builder is doing all the work for you. You can actually see that the Command Builder is taking the same approach as you did in the previous example, by examining the UpdateCommand that is being executed on your behalf. Add the following line to the beginning of the try block in btnUpdateDB_Click:
MessageBox.Show(dataAdapter.UpdateCommand.CommandText );

18-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Concurrency
This opens a MessageBox to display the text of the UpdateCommand. Update another record, and note the text that is displayed. The MessageBox shows that the Update Command is using 19 parameters, the first nine are used to set the new values, and the remaining ten are used to test that no original values were changed. This is shown in Figure 13.

Figure 13. The update generated by the command builder.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-25

Advanced ADO Data Update

Summary
Rather than updating the database each time the user makes a change, you can update the disconnected DataSet. You may update the database from your disconnected DataSet at any time. If more than one user is interacting with the database, you must take precautions to ensure that changes are not overwritten due to concurrency conflicts. The best way to prevent a concurrency problem is to set a where clause that checks that the data is unchanged from when it was first read. You can simplify creating the parameters for the where clause by setting the SourceVersion of the Parameter to DataRowVersion.Original. Rather than writing the concurrency checking stored procedure and parameters yourself, you can simplify your program by using a Command Builder. There are restrictions on using a Command Builder; the most important of which is that you must update only a single table and that table must have a unique key that was used to fill the table.

18-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Concurrency

Questions
1. What is the advantage of updating the DataSet rather than the database? 2. How do you loop through all the rows that have changed, to ensure they are all updated on the database? 3. Why is having more than one user at a time problematic? 4. How do you structure a query to ensure that the data has not changed from when you originally read the data? 5. How do you tell the Parameter object which version of data you want in a DataRow? 6. What is the job of the CommandBuilder? 7. What are the principal restrictions when using CommandBuilder?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-27

Advanced ADO Data Update

Answers
1. What is the advantage of updating the DataSet rather than the database?
Updating the DataSet allows you to minimize the traffic to the Database. You can update a number of records in the DataSet and then update the database all at once.

2. How do you loop through all the rows that have changed, to ensure they are all updated on the database?
The DataAdapter will manage this for you. You provide an Update command and set the SourceColumn and SourceVersion properties of the Parameter object.

3. Why is having more than one user at a time problematic?


It is possible for two users to update the same data, with one inadvertently overwriting the changes made by the other.

4. How do you structure a query to ensure that the data has not changed from when you originally read the data?
You set a where clause to update the record only if the original values are still in the various columns.

5. How do you tell the Parameter object which version of data you want in a DataRow?
Set the SourceVersion property to one of the DataRowVersion enumerated constants (Current or Original).

6. What is the job of the CommandBuilder?


The CommandBuilder can generate Select, Insert, or Update commands and greatly simplify concurrency support.

7. What are the principal restrictions when using CommandBuilder?


All the rows must come from a single table and that table must have a unique key, and that key must have been used to fill the table. Further, the table name must have no spaces, periods, quotes, or other special characters.

18-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Concurrency

Lab 18: Advanced ADO Data Update

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-29

Lab 18: Advanced ADO Data Update

Lab 18 Overview
In this lab youll learn advanced techniques for updating a database using ADO.NET. To complete this lab, youll need to work through two exercises: Updating Data in DataSets Updating the Database with CommandBuilder

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

18-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Updating Data in DataSets

Updating Data in DataSets


Objective
In this exercise, youll update columns in a dataset and then batch update the backing database.

Things to Consider
You can update the dataset repeatedly without updating the database. When you do update the database you can call a transaction and pass in each value that has been updated. Your job is to identify the parameters, the DataSet will take care of finding all the affected rows. The DataRow has a number of versions, the Current version is the value as updated in the DataSet.

Step-by-Step Instructions
1. Create a stored procedure called spDBUpdateNoTransactions using SQL Server Enterprise Manager. To do this perform the following steps: a. Open SQL Server Enterprise Manager. b. Drill down the hierarchical tree structure in the left pane by clicking on the plus signs until the Northwind database is visible. c. Drill down from the Northwind database one more level to Stored Procedures. d. Click on Stored Procedures. There will be a list of stored procedures visible in the right pane, as shown in Figure 14.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-31

Lab 18: Advanced ADO Data Update

Figure 14. Stored Procedures in Enterprise Manager.

e. Right-click on Stored Procedure in the left pane and select New Stored Procedure. f. A Stored Procedure Properties dialog box appears with a large text editing window containing the following boilerplate code:

CREATE PROCEDURE [OWNER].[PROCEDURE NAME] AS

g. Replace that boilerplate code with the following code:

18-32

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Updating Data in DataSets


CREATE PROCEDURE spDBUpdateNoTransactions @ProductID int, @ProductName varChar(40), @UnitPrice money, @CategoryName varChar(5000), @CategoryID int as Update products set productName = @ProductName, UnitPrice = @UnitPrice where productID = @productID

Update Categories set CategoryName = @CategoryName where CategoryID = @CategoryID

h. When you are done, click the Check Syntax button to verify that there are no syntax errors. i. Click the OK button to save the new sproc. The new sproc will now be visible in the list of sprocs in the right pane.

2. Open DataSetUpdatesStarter.sln in the DataSetUpdatesStarter folder. 3. Notice the DataAdapter, DataSet, and DataTable member variables.
private SqlDataAdapter dataAdapter; private DataSet dataSet; private DataTable dataTable;

4. Review the FillLB method provided in the starter file as well as the ShowLB method and the lbProducts_SelectedIndexChanged method. 5. Implement the event handler for the Update DataSet click event.
private void btnUpdateDataSet_Click( object sender, System.EventArgs e) {

6. Get the selected row.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-33

Lab 18: Advanced ADO Data Update


DataRow selectedRow = dataTable.Rows[lbProducts.SelectedIndex];

7. Fill the columns in the DataRow from the text boxes in the dialog box.
selectedRow["ProductName"] = txtProductName.Text; selectedRow["ProductID"] = txtProductID.Text; selectedRow["CategoryName"] = txtCategory.Text; selectedRow["UnitPrice"] = txtUnitPrice.Text;

8. Show the list box from the DataSet by calling ShowLB.


ShowLB();

9. Implement the UpdateDB button event handler.


private void btnUpdateDB_Click(object sender, System.EventArgs e) {

10. Create the connection string. Remember to use the correct server name, userID, and password for your SQL Server account.
string connectionString = "server=YourServer; uid=sa; pwd=YourPassword; database=Northwind";

11. Create the connection object.


System.Data.SqlClient.SqlConnection connection = new System.Data.SqlClient.SqlConnection( connectionString);

12. Open the connection.


connection.Open();

18-34

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Updating Data in DataSets


13. Create a SqlCommand object.
System.Data.SqlClient.SqlCommand command = new System.Data.SqlClient.SqlCommand();

14. Create a SqlTransaction object and initialize it by calling BeginTransaction on the connection object.
SqlTransaction transaction = connection.BeginTransaction();

15. Set the commands transaction and connection objects.


command.Transaction = transaction; command.Connection = connection;

16. Set the command text for the command to the sproc spDBUpdateNoTransactions.
command.CommandText= "spDBUpdateNoTransactions";

17. Set the CommandType of the command object to Stored Procedure.


command.CommandType = CommandType.StoredProcedure;

18. Create a SqlParameter object.


System.Data.SqlClient.SqlParameter param;

19. Review the settings of the parameters in the starter file. 20. Set the UpdateCommand property of the DataAdapter to the command and set the Transaction property of the UpdateCommand to the transaction.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-35

Lab 18: Advanced ADO Data Update


dataAdapter.UpdateCommand = command; dataAdapter.UpdateCommand.Transaction = transaction;

21. Create a try block. Within the try block, call Update on the DataAdapter and pass in the DataSet object and the table name (Products). Use a MessageBox to display the number of rows updated.
try { int rowsUpdated = dataAdapter.Update(dataSet,"Products"); transaction.Commit(); MessageBox.Show(rowsUpdated + " rows updated!" ); }

22. Create a catch block to catch any exceptions and use a MessageBox to display the exceptions message.
catch (System.Exception ex) { MessageBox.Show(ex.Message); transaction.Rollback(); }

23. Call FillLB to refill the list box.


FillLB();

The final result should look like Figure 15.

18-36

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Updating Data in DataSets

Figure 15. Running the program.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-37

Lab 18: Advanced ADO Data Update

Updating the Database with CommandBuilder


Objective
In this exercise, youll update the database using the Frameworks CommandBuilder class.

Things to Consider
You may use a CommandBuilder only if all the rows you are updating must come from a single table. That table must have a unique key and the unique key must be returned by the query used to fill the table. Note that the name of the table you use must have no spaces, periods, quotes, or other special characters. The CommandBuilder provides a very simple way to manage concurrency. No stored procedure is neededthe DataSet will know which data to update.

Step-by-Step Instructions
1. Open CommandBuilderStarter.sln in the CommandBuilderStarter folder. 2. Create an SqlCommandBuilder instance.
SqlCommandBuilder bldr = new SqlCommandBuilder(dataAdapter);

3. Set DataAdapters UpdateCommand object by calling GetUpdateCommand on DataAdapter.


dataAdapter.UpdateCommand = bldr.GetUpdateCommand();

18-38

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Updating the Database with CommandBuilder


4. Set DataAdapters DeleteCommand object by calling GetDeleteCommand on DataAdapter.
dataAdapter.DeleteCommand = bldr.GetDeleteCommand();

5. Set DataAdapters InsertCommand object by calling GetInsertCommand on DataAdapter.


dataAdapter.InsertCommand = bldr.GetInsertCommand();

6. Fill member variable dataTable with the table you created above.
dataTable = dataSet.Tables[0];

7. Call ShowLB to show the list box.


ShowLB();

8. Implement the ShowLB method.


private void ShowLB() {

9. Empty the list box.


lbProducts.Items.Clear();

10. Iterate over the Rows collection to fill the list box.
foreach (DataRow dataRow in dataTable.Rows) { lbProducts.Items.Add(dataRow["ProductName"]); }

11. Implement the SelectedIndexChanged event handler. C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-39

Lab 18: Advanced ADO Data Update


private void lbProducts_SelectedIndexChanged( object sender, System.EventArgs e) {

12. Extract the selected row.


DataRow selectedRow = dataTable.Rows[lbProducts.SelectedIndex];

13. Fill the four text boxes.


txtProductName.Text = selectedRow["ProductName"].ToString(); txtProductID.Text = selectedRow["ProductID"].ToString(); txtCategoryID.Text = selectedRow["CategoryID"].ToString(); txtUnitPrice.Text = selectedRow["UnitPrice"].ToString();

14. Implement the event handler for the Update DataSet button.
private void btnUpdateDataSet_Click( object sender, System.EventArgs e) {

15. Declare a local DataRow and fill with a reference to the selected row.
DataRow selectedRow = dataTable.Rows[lbProducts.SelectedIndex];

16. Set the Selected Rows values from the text boxes.
selectedRow["ProductName"] = txtProductName.Text; selectedRow["ProductID"] = txtProductID.Text; selectedRow["CategoryID"] = txtCategoryID.Text; selectedRow["UnitPrice"] = txtUnitPrice.Text;

18-40

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Updating the Database with CommandBuilder


17. Update the list box by calling ShowLB.
ShowLB();

18. Implement the event handler for the UpdateDB button.


private void btnUpdateDB_Click( object sender, System.EventArgs e) {

19. Create a try block.


try {

20. Display a MessageBox with the Command Text from the UpdateCommand object held by the DataAdapter.
MessageBox.Show(dataAdapter.UpdateCommand.CommandText );

21. Call the Update method on the DataAdapter (passing in the dataSet, and the string "Products"). Assign the value returned to a local variable.
int rowsUpdated = dataAdapter.Update(dataSet,"Products");

22. Display that value in a MessageBox as the number of rows updated.


MessageBox.Show(rowsUpdated + " rows updated!" );

23. Implement a catch block to display a MessageBox with the message "Concurrency error!".

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

18-41

Lab 18: Advanced ADO Data Update


catch { MessageBox.Show("Concurrency error!" ); }

24. Call FillLB to refill the list box.


FillLB();

When you run the program, it should look like Figure 16.

Figure 16. Running the program.

18-42

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Programming Web Forms

Programming Web Forms


Objectives
Use Web Forms to create Web applications. Handle events in Web forms. Understand how state is managed. Explore the various ASP.NET controls. Use data bound controls to display data from a database. Examine rich controls.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

19-1

Programming Web Forms

ASP.NET
The .NET platform refers to the suite of technologies for creating Web applications as ASP.NET. The part of ASP.NET we care about here, however, is the Web Forms technology that allows Rapid Application Development of Web programs. The Web Forms design pattern is extremely similar to the Windows Forms design pattern. Once again you are presented with a form on which you drag and drop controls, adding code-behind to support event handling. The key difference is that when you run the Web application, it is served by your Web server (IIS) and viewed remotely using a browser, as illustrated in Figure 1.

Figure 1. Web hosting.

Web Forms
Web Forms dynamically generate Web pages on the server and send them to the client. What is viewed on the client browser is simply HTML. This allows the Web Forms technology to support any browser. At the time the page is generated, the host knows what browser the client is using and has an opportunity to take advantage of the capabilities of up-level browsers (e.g., IE 6) but this is invisible to you as the author of the page.

Creating Web Forms


Web forms are just HTML with a C# source code file behind them. You can create Web forms in a simple editor (such as Notepad), but most of the time 19-2 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

ASP.NET
you wont want to. Visual Studio .NET provides extensive support for building Web pages, including a WYSIWYG (What You See Is What You Get) designer to support drag-and-drop manipulation of controls. A Web form consists of two files: the HTML file (.aspx) and the code-behind file (.aspx.cs). The code-behind file has C# code that is compiled. (Of course, you can write code-behind in Visual Basic .NET or any other .NET language, but why on Earth would you want to?)

Event Driven
A key distinction between ASP.NET and its precursor technology is that ASP.NET is event-driven. That is, there are many events that you handle in an ASP.NET page, and you write your event handlers in the code behind, much as you do with Windows forms.

The Life Cycle Model


The model for ASP.NET is this: the server draws a page and sends it to the user. The user reads the page and interacts with it. Some of the interactions post the page back to the server. When the page is posted back, it is updated and then sent back to the browser.

PostBack
Not all controls postback, but many do including: Button Calendar DataGrid DataList ImageButton LinkButton

Many other controls have non-postback events. These events are stored and handled during the next postback.

State
The Web is inherently stateless. That is, each time you request a page, a normal Web server simply dispenses the page and then breaks the connection. There is no state maintained that allows the server to understand the concept of

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

19-3

Programming Web Forms


a session. ASP.NET imposes a session on top of this stateless environment through the use of cookiesa small file kept on the users machine. ASP.NET provides extensive support for state. Control state (the state of the controls on the page) is no longer maintained by hand; it is maintained for you through view-state. View state is implemented as a hidden encrypted field on the HTML page. You simply provide the control and the page maintains the state of the control. Session state is maintained on the server and can be written to a database to support large scale Web Farms. NOTE Web Forms created with Visual Studio .NET have files in two different directories on your local computer. The bulk of the files are in a subdirectory under the localhost virtual directory. On most machines, the default physical location for localhost corresponds to the directory c:\inetpub\wwwroot. In addition, there is also a solution file, with an extension of sln, located in the default projects directory. This directory will vary from machine to machine, and is specified in Visual Studio .NET under Tools|Options|Environment|Projects and Solutions.

In order to utilize the sample projects included with this chapter, you will need to perform the following steps: 1. The projects included as part of this courseware are contained within two directories: From inetpub and From Visual Studio Projects. Copy these two directories to a location on your local machine. They do not necessarily have to be copied to the existing inetpub or projects directories. 2. Create a virtual directory for each project that you want to open in Visual Studio .NET. 3. Click Start|Settings|Control Panel|Administrative Tools|Computer Management. 4. In the left pane, drill down by clicking on the plus signs next to Services and Applications, Internet Information Services, Internet Information Services, and Default Web Site. 5. Right-click on Default Web Site and select New|Virtual Directory. 6. Follow the wizard to create a new virtual directory for the project you want to work on. The alias name can be any name you wish, but to avoid confusion name the alias the same as the directory name. 7. For the Directory, click the Browse button to browse to the correct subdirectory under the From inetpub directory. For Access Permissions, use the defaults. 19-4 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

ASP.NET
8. Edit the sln file so that it points to the correct URL incorporating the virtual directory you just created. The sln file is contained in the From Visual Studio Projects directory. 9. Open the sln file in a text editor such as Notepad. 10. The second line will have the name of the project followed by a URL. 11. Edit the URL (in quotes in the sln file) so that it uses the virtual directory you just created. 12. Note that the final node of the URL is a file with a csproj extension. 13. Save and close the sln file. 14. Now you can open the project in Visual Studio .NET either by doubleclicking the sln file in Windows Explorer or opening Visual Studio .NET by clicking the Open Project button on the Start page, and navigating to the sln file.

Try It Out!
See HelloWeb.sln To see how easy it is to build a Web Forms application and to see the support provided by Visual Studio .NET, youll build a simple Web application. 1. Create a new application in Visual Studio .NET by clicking on New Project. 2. In the Templates window click on ASP.NET Web Application. The Location will be a folder under http://localhost, but you are free to create subdirectories to hold your project. 3. Name the project HelloWeb and click OK. Visual Studio .NET will create a Web page and virtual directory for you. It will also save the project file in a subdirectory, typically under My Documents/Visual Studio Projects. 4. The form has two Layout options: Grid, which allows you to precisely place objects (and uses absolute positioning) or Flow, which simply flows one object after another. To change the Layout, right-click on the form, choose Properties, and set the Page Layout property. For now, leave it as GridLayout. 5. Drag a label onto the form. Stretch the label and set its ID to lblOutput and its text to Hello Web. While you are at it, click on the + in front of the Font attribute, and set Bold to true and Size to Larger. 6. Drag a button onto the form. Set its ID to btnChange and its Text to Change!, as shown in Figure 2.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

19-5

Programming Web Forms

Figure 2. Creating the Web form.

7. Start the application. The message prints and clicking the button posts the page back to the server, though it is redrawn unchanged. Youll fix that in a moment. 8. Click the HTML tab and look at the HTML youve created so far. Notice that you are using absolute positioning. 9. Double-click on the button to open an event handler. You should find yourself in the btnChange_Click event handler method. Add the following code:
lblOutput.Text = "Goodbye Web";

19-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

ASP.NET
10. Notice that when you type lblOutput Visual Studio .NET offers you help in finding the method or property you want. 11. Start the application. This time when you click the button the page is redrawn with a new message: Hey Presto! Now youre a Web programmer. 12. Go back to the designer, and drag a list box onto the form. Set the ID for the list to lbNames. 13. Double-click on the form to open the default event handler: Page_Load. 14. In the Page_Load method, add this code:
lbNames.Items.Add("Jesse"); lbNames.Items.Add("Joe"); lbNames.Items.Add("Jim"); lbNames.Items.Add("Jack");

15. Start the application. Notice that the four names fill the list box. 16. Click Change. The list box now has eight names, so what happened? When you clicked change, the page was posted to the server. The new HTML was sent down for the text for the label. The page was then reloaded. Unfortunately, on page load you add items to the list box. You dont want to do that, because the view_state is already maintaining the state of the list box and you dont need to add them again. You want to differentiate between the first time the page is loaded (in which case you do want to add these names) and when the page is being reloaded because of a postback. 17. Modify the code in Page_Load as follows:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

19-7

Programming Web Forms


private void Page_Load( object sender, System.EventArgs e) { if ( ! IsPostBack ) { lbNames.Items.Add("Jesse"); lbNames.Items.Add("Joe"); lbNames.Items.Add("Jim"); lbNames.Items.Add("Jack"); } }

Here you are testing whether the IsPostBack property is true. If not (and this is the first time the page is being loaded) then you will add the four names. 18. Rerun the application. Click Change and the four names are shown with no additional names added. You can trust the view state to maintain the state of your controls through a postback. Try clicking on one name (highlighting it) and then click Change. Notice that after the postback the state of the control is maintained. You do not need to write code to do this, ASP.NET does it for you through the use of View_State. 19. Examine the source code sent to the browser by clicking View|Source in the browser, as shown in Figure 3.

19-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

ASP.NET

Figure 3. Viewing the HTML.

There are two things to notice when examining the HTML. First, what is sent to the browser is just HTML; no source code. This is key; all the source code is on the server. Second, there is a hidden text field named _VIEWSTATE that has all sorts of gibberish in it. 20. Keep the source window open, but go back to your HTML. Notice that the list box is marked as an object of type ASP:ListBox:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

19-9

Programming Web Forms


<asp:ListBox id="lbNames" style="Z-INDEX: 103; LEFT: 115px; POSITION: absolute; TOP: 132px" runat="server"></asp:ListBox>

21. Examine the HTML sent. The ASP:ListBox has been transformed into a simple Select HTML tag. In addition, the items you added using object syntax,
lbNames.Items.Add("Jesse"); lbNames.Items.Add("Joe"); lbNames.Items.Add("Jim"); lbNames.Items.Add("Jack");

have been transformed into <option> tags.


<option value="Jesse">Jesse</option> <option selected="selected" value="Joe">Joe</option> <option value="Jim">Jim</option> <option value="Jack">Jack</option>

The power of ASP.NET is that you design and code using objects, but what is sent to the browser is just HTML.

ASP.NET Controls
ASP.NET programming supports five types of controls. HTML Controls HTML Server Controls Web (ASP) Server Controls Validation Controls User Controls/Custom Controls

HTML Controls
You can continue to program using simple HTML controls just as you might with ASP or any other Web technology.

19-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

ASP.NET

HTML Server Controls


You can transform a simple HTML control into a programmable server-side control just by adding the attribute to a traditional HTML control.
runat = "Server"

This allows you to program events for the HTML control that will be implemented at the server.

Web (ASP) Server Controls


ASP.NET provides a new set of controls that implement all the functionality of HTML controls, but do so in a more uniform object model. These controls are prefixed with the ASP tag, and offer what many programmers consider a superior programming model. This chapter uses Web Server controls rather than HTML Server controls. The browser never sees your Web Server controls. What is sent to the browser is always traditional HTML. You saw this earlier when the Web Server List control was converted to a traditional HTML Select control. Web controls include the following: Label: displays text Text box: provides single or multi-line text entry Button LinkButton (like a button with a link to a URL) ImageButton (like an image with a link to a URL) CheckBox: provides yes/no semantics RadioButton: provides mutually exclusive choices ListBox: provides single or multiselect choices from a list CheckBoxList: combines check boxes with a list box RadioButtonList: for building lists of radio buttons DropDownList: for choosing one from a list

In addition, ASP.NET provides a suite of rich new controls including: Calendar: for date manipulation Ad rotator: for displaying advertising

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

19-11

Programming Web Forms

Validation Controls
ASP.NET offers a series of controls for implementing data validation on your Web pages. These allow you to ensure that fields are not left blank, or that values are within a specified range or meet a given regular expression pattern. Validation controls can run on the server or the client, and can be set to decide where to run based on the capabilities of the browser.

User Controls/Custom Controls


If the controls provided by .NET are not sufficient to meet your needs, you are free to write your own. You can combine a set of controls into a small reusable HTML page (User Controls) or you can derive entirely new controls from the Controls base class.

Data Bound Controls


It is possible to bind a control to a data source and have ASP.NET do much of the processing for you. ASP.NET offers a number of rich and powerful controls for displaying and manipulating data, typically binding controls to DataSets and DataTables.

Try It Out!
See WebControls.sln To see how to work with Web Controls and especially how to bind data to a Web control, youll build a simple demonstration Web page. 1. Create a new ASP.NET application or open the WebControlsStarter project. 2. If you are creating your own application from scratch, drag a ListBox, three Buttons, nine Labels, nine Textboxes, and one DropDownList onto your form, as shown in Figure 4.

19-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

ASP.NET

Figure 4. WebControlsStarter.

3. If you are writing this from scratch, set the IDs as shown in Table 1.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

19-13

Programming Web Forms


Control ListBox Button Button Button TextBox TextBox TextBox TextBox TextBox TextBox TextBox TextBox DropDownList lbProducts btnUpdate btnDelete btnNew txtProductName txtCategoryID txtUnitPrice txtUnitsOnOrder txtQuantityPerUnit txtUnitsInStock ReorderLevel SupplierID cbDiscontinued Update Delete New ID Text

Table 1 Controls with their IDs and Text.

The Labels are not given special IDs (i.e., they remain Label1, Label2, etc.), but you can see which text box aligns with each label based on the name of the text box. In addition, there is a text box above txtQuantityPerUnit which is named txtProductID, but which has no label. 4. Run the application. It doesnt do much except display the labels and text boxes and buttons. Notice that the drop-down list is empty. Youll fix all of this in the next steps. 5. Notice that this form is quite similar to the Windows Form application you built in a previous chapter. The code is very similar as well. To get started, open the code behind and add this line:
using System.Data.SqlClient;

6. The first programming task is to fill in the contents of the list box. Youll do that in the Page_Load method. Once again you must check that you are not in a postback page. You only want to go to the database the first time you load the page.

19-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

ASP.NET
private void Page_Load( object sender, System.EventArgs e) { //*** new if (!IsPostBack) {

} }

7. Within the test for PostBack, create a connection string to connect to the database.
string strConnection = "server=YourServer; uid=sa; pwd=YourPW; database=northwind";

8. Create the command string to retrieve records from the Products table.
string strCommand = "Select * from Products";

9. Instantiate a DataAdapter, passing in the command and connection strings.


SqlDataAdapter dataAdapter = new SqlDataAdapter(strCommand, strConnection);

10. Create the DataSet and fill it from the DataAdapter.


DataSet dataSet = new DataSet(); dataAdapter.Fill(dataSet,"Products");

11. Get the DataTable from the DataSet.


DataTable dataTable = dataSet.Tables[0];

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

19-15

Programming Web Forms


12. You will need this DataTable when a choice is made in the list box. You must store it away in session state, because when the page is reposted, it will no longer exist (remember, the DataSet is only created the first time the page is loaded, and then it is lostthere is no state in a Web page).
Session["dataTable"] = dataTable;

To store an object in Session state, you must add it to the Session object. You do so by using indexer syntax. If the object at the index you provide exists, it is updated, otherwise it is created. In this case, youve created a Session variable dataTable just by naming it in the index operator and assigning the dataTable object to the Session object at that string. 13. Call FillLB to fill the list box. Youll write this method in Step 15.
FillLB();

14. Fill the DropDown list by adding two strings, true and false. Close the method:
cbDiscontinued.Items.Add("True"); cbDiscontinued.Items.Add("False"); } }

15. Implement the FillLB method. Start by retrieving the DataTable from Session state.
private void FillLB() { DataTable dataTable = (DataTable) Session["dataTable"];

16. Clear the list box and fill it, by iterating over the Rows in the DataTable. For each row you extract from the Rows collection, index on the ProductName column, and call ToString on the results to get a string that you can add to the Items collection of the list box.

19-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

ASP.NET
lbProducts.Items.Clear();

// for each row in the table, fill the list foreach (DataRow dataRow in dataTable.Rows) { lbProducts.Items.Add( dataRow["ProductName"].ToString()); } }

17. Return to the designer and double-click on the list box. This opens the default event handler, which is called each time the user changes the selection.
private void lbProducts_SelectedIndexChanged( object sender, System.EventArgs e) {

18. When the user changes the selection you want to fill the text boxes with the data from the appropriate DataRow. To do so, get the DataTable from session state, and then get the appropriate row based on the new SelectedIndex property of the list box lbProducts.
DataTable dataTable = (DataTable) Session["dataTable"];

DataRow selectedRow = dataTable.Rows[lbProducts.SelectedIndex];

19. Use the name of the column as an index into the row; extract the value and assign it to the appropriate text box.
txtProductName.Text = selectedRow["ProductName"].ToString();

20. Do the same for all the other text boxes.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

19-17

Programming Web Forms


txtProductID.Text = selectedRow["ProductID"].ToString(); txtSupplierID.Text = selectedRow["SupplierID"].ToString(); txtCategoryID.Text = selectedRow["CategoryID"].ToString(); txtQuantityPerUnit.Text = selectedRow["QuantityPerUnit"].ToString(); txtUnitPrice.Text = selectedRow["UnitPrice"].ToString(); txtUnitsInStock.Text = selectedRow["UnitsInStock"].ToString(); txtUnitsOnOrder.Text = selectedRow["UnitsOnOrder"].ToString(); txtReorderLevel.Text = selectedRow["ReorderLevel"].ToString();

21. Set the value for the drop-down list, setting the selected index in the list to 1 if the value is false, or 0 if it is true.
cbDiscontinued.SelectedIndex = selectedRow["Discontinued"].ToString() == "False" ? 1 : 0;

Implementing Update, etc.


You wont bother implementing update, delete, and so forth, because the issues here are identical to those covered in other chapters. The important fact here is this: the fact that you are implementing this as a Web application rather than a Windows application does not change how you interact with the database. All the issues of updating apply, and you must be especially careful about concurrency, because Web applications typically are written for multiple users.

19-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

ASP.NET

Binding Data
Many of the controls allow you to bind data to them directly rather than filling them by hand. In the previous example, you filled the ListBox control in the FillLB method:
foreach (DataRow dataRow in dataTable.Rows) { lbProducts.Items.Add( dataRow["ProductName"].ToString()); }

See WebControls.sln

If you prefer, you can just as easily bind the control to the DataTable.

Try It Out!
1. Reopen the previous example. 2. Eliminate the call to FillLB in PageLoad.
// FillLB();

3. Set the DataSource property of the ListBox to the DefaultView of the DataTable.
lbProducts.DataSource = dataTable.DefaultView;

4. Set the DataTextField of the ListBox.


lbProducts.DataTextField = "ProductName";

5. Call DataBind to bind the data from the table to the list box.
lbProducts.DataBind();

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

19-19

Programming Web Forms


6. Run the application; the list box is filled just as it was when you filled it manually.

Other Rich Controls


See WebControls.sln ASP.NET provides a variety of powerful rich data controls. One of the most interesting is the Calendar control. You can get started with this just by dragging it onto your form.

Try It Out!
1. Reopen the previous project. 2. Drag a Calendar onto the form. 3. Run the application. This adds a pretty powerful calendar widget to your form, as illustrated in Figure 5.

Figure 5. The Calendar control.

4. The Calendar supports a number of events. One useful event is SelectionChanged. To use this event, create three labels, and place them one below the other, each to the right of the calendar. 5. Name the three labels lblToday, lblSelected, and lblCount. 6. Set the text of all three to blank. 7. Size the first label so its wide enough to hold a long message.

19-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

ASP.NET
8. Shift click on the other labels and use Format|Align|Lefts to align the labels, and Format|MakeSameSize|Both to make all the labels the same size, as shown in Figure 6.

Figure 6. Setting the labels.

9. Click on the calendar control in your designer. 10. Click on the lightning bolt to see the events. 11. Navigate to SelectionChanged and fill in OnSelectionChanged. 12. Click enter; you should be taken to the event handler 13. Add code to get the current date and set the text of lblToday.
lblToday.Text = "Today's Date is " + cal.TodaysDate.ToShortDateString();

14. Test to see if the date has been selected. To do so, test the selected date against the constant DateTime.MinValue. If the selected date is not the MinValue date, then a date has been selected, and you can set the lblSelected label.
if (cal.SelectedDate != DateTime.MinValue) lblSelected.Text = "The date selected is " + cal.SelectedDate.ToShortDateString();

15. Get the Count property of the SelectedDates collection from the Calendar, and convert it to a string that you can assign to the Text property of lblCount.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

19-21

Programming Web Forms


lblCount.Text = "Count of Days Selected: + cal.SelectedDates.Count.ToString(); "

16. Run the application and click on a Date. The labels are populated, as shown in Figure 7.

Figure 7. Populating the labels.

17. Return to the designer and click on the calendar. Change the SelectionMode property from Day to DayWeekMonth and rerun the application. You may have to reposition the labels because the calendar will resize. You can now select an entire week, as shown in Figure 8.

Figure 8. Selecting a week.

18. Before you leave this example, play with the properties. You can set colors and fonts and otherwise make your calendar look more professional and appealing (or ugly and absurd) as shown in Figure 9.

19-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

ASP.NET

Figure 9. Using color and adjusting fonts.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

19-23

Programming Web Forms

Summary
ASP.NET is the successor technology to ASP. Web forms provide a Rapid Application Development (RAD) for Web application development. ASP.NET is an event-driven environment. Many events cause the page to be posted back to the server where your code-behind page runs on the server. You can test for postback with the IsPostBack property of the form. The Web is inherently stateless. WebForms support state for controls with the hidden ViewState field. ASP.NET supports session state. ASP.NET supports five types of controls: HTML, HTML Server, Web, Validation, and User/Custom. Whatever controls you use, HTML is sent to the browser.

19-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

ASP.NET

Questions
1. What are the key differences between ASP and ASP.NET? 2. Are WebForms the same as WindowsForms? 3. How do you use the IsPostBack property of Form? 4. How do you maintain the state of your controls in ASP.NET? 5. How do you add an object to Session state?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

19-25

Programming Web Forms

Answers
1. What are the key differences between ASP and ASP.NET?
ASP.NET is an event-driven technology. In addition, the HTML is separated from the code, with the code in a compiled codebehind page.

2. Are WebForms the same as WindowsForms?


While these two technologies are very similar, they are distinct. The controls available in the WebForms may look very much like the controls in WindowsForms but they have different abilities, and are transformed to simple HTML when sent to the browser.

3. How do you use the IsPostBack property of Form?


The IsPostBack property is used to test whether this is the first time the form has been loaded (false) or whether the form is being reloaded due to a postback event (true).

4. How do you maintain the state of your controls in ASP.NET?


Control state is maintained for you by the ASP.NET framework in the VIEWSTATE hidden text control.

5. How do you add an object to Session state?


You add an object to Session state by using the index operator and passing in a string representing the name of the Session variable.

19-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

ASP.NET

Lab 19: Programming Web Forms

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

19-27

Lab 19: Programming Web Forms

Lab 19 Overview
In this lab youll learn to work with Web forms. To complete this lab, youll need to work through three exercises: Lab Setup Creating a Web Application Creating Web Controls

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

19-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Lab Setup

Lab Setup
Web forms created with Visual Studio .NET have files in two different directories on your local computer. The bulk of the files are in a subdirectory under the localhost virtual directory. On most machines, the default physical location for localhost corresponds to the directory c:\inetpub\wwwroot. In addition, there is also a solution file, with an extension of sln, located in the default projects directory. This directory will vary from machine to machine, and is specified in Visual Studio .NET under Tools|Options|Environment|Projects and Solutions. In order to utilize the sample projects included with this lab, you will need to perform the following steps: 1. The projects included as part of this lab are contained within two directories: From inetpub and From Visual Studio Projects. Copy these two directories to a location on your local machine. They do not necessarily have to be copied to the existing inetpub or projects directories. 2. Create a virtual directory for each project that you wish to open in Visual Studio .NET. To do this: a. Click Start|Settings|Control Panel|Administrative Tools|Computer Management. b. In the left pane, drill down by clicking on the plus signs next to Services and Applications, Internet Information Services, Internet Information Services, and Default Web Site. c. Right-click on Default Web Site and select New|Virtual Directory. d. Follow the wizard to create a new virtual directory for the project you want to work on. The alias name can be any name you wish, but to avoid confusion, it makes sense to name the alias the same as the directory name. So for example, if the project you will be working on is in the subdirectory WebHelloWorldCompleted, then that would also be a good alias name. e. For the Directory, click the Browse button to browse to the correct subdirectory under the From inetpub directory. For Access Permissions, use the defaults. 3. Edit the sln file so that it points to the correct URL incorporating the virtual directory you just created. The sln file is contained in the From Visual Studio Projects directory.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

19-29

Lab 19: Programming Web Forms


a. Open the sln file in a text editor such as Notepad. b. The second line will have the name of the project followed by a URL that will look something like the following.
"http://localhost/ASPNET/Labs/WebHelloWorldCompleted/ WebHelloWorldCompleted.csproj"

c. Edit the URL (in quotes in the sln file) so that it uses the virtual directory you just created. In this example, the URL would be edited to look like the following.
"http://localhost/WebHelloWorldCompleted/ WebHelloWorldCompleted.csproj"

d. Note that the final node of the URL is a file with a csproj extension. e. Save and close the sln file. 4. Now you can open the project in Visual Studio .NET either by doubleclicking the sln file in Windows Explorer or opening Visual Studio .NET, clicking the Open Project button on the Start page, and navigating to the sln file.

19-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating a Web Application

Creating a Web Application


Objective
In this exercise, youll recreate the Hello World Web application using Visual Studio.

Things to Consider
Visual Studio is writing much of the infrastructure for you, but the fundamentals are unchanged. Your form will be separated from your code-behind file. The codebehind will house the event handlers. You need to decide whether to use the grid layout, which will use explicit coordinates to place your controls. If not, you can use the flow layout, which will just add the controls one by one; you may then decide to put the controls into a table to place them as you like.

Step-by-Step Instructions
1. Create a new Visual C# ASP.NET Web application in Visual Studio named WebHelloWorld. 2. Drag a label onto the form, and set the text to Hello World From ASP.NET. 3. Set its name to lblOutput. 4. Stretch the label to fit the words. 5. Drag a button onto the form. Name it btnChange and set its text to Change!. 6. Double-click on the button to open the event handler. 7. Enter the code to change the text.
lblOutput.Text = "Goodbye world!";

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

19-31

Lab 19: Programming Web Forms


8. Press CTRL+F5 to start the application. Your Web application should look like Figure 10.

Figure 10. Running the application.

9. Click the Change button to change the text. Your application should look like Figure 11.

19-32

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating a Web Application

Figure 11. The application after clicking the Change button.

10. Close the browser.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

19-33

Lab 19: Programming Web Forms

Creating Web Controls


Objective
In this exercise, youll recreate a form tied to the database to examine a subset of the data known about the products table.

Things to Consider
If your page is not posted back, youll want to fill the list box. Double-clicking on the list box will generate the SelectedIndexChanged method handler. When you set the drop-down list control, you must set the appropriate index value.

Step-by-Step Instructions
1. Open the WebControlsStarter project in Visual Studio .NET by following the steps in the Lab Setup section of this lab. This starter project has all the controls already in place. 2. In the Page_Load event handler, test that you are not responding to a post back.
if (!IsPostBack) {

3. Set up the connection string to the Northwind database. Be certain to use the server name, userID, and password for your SQL Server account.
string strConnection = "server=YourServer; uid=sa; pwd=YourPassword; database=northwind";

4. Set up a command string to select all the fields from the Products table.

19-34

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Web Controls


string strCommand = "Select * from Products";

5. Instantiate a SQL Server specific DataAdapter with the command and connection strings.
SqlDataAdapter dataAdapter = new SqlDataAdapter(strCommand, strConnection);

6. Instantiate a DataSet.
DataSet dataSet = new DataSet();

7. Fill the DataSet using the DataAdapter.


dataAdapter.Fill(dataSet,"Products");

8. Extract the table you just created.


DataTable dataTable = dataSet.Tables[0];

9. Preserve the DataTable in session state.


Session["dataTable"] = dataTable;

10. Call FillLB (a method youll write in Step 12).


FillLB();

11. Add the values true and false to the drop-down list control.
cbDiscontinued.Items.Add("True"); cbDiscontinued.Items.Add("False");

12. Implement the FillLB method.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

19-35

Lab 19: Programming Web Forms


private void FillLB() {

13. Extract the DataTable from Session state to a local object.


DataTable dataTable = (DataTable) Session["dataTable"];

14. Clear the contents of the list box.


lbProducts.Items.Clear();

15. Iterate over the rows in the DataTable, filling the list box.
foreach (DataRow dataRow in dataTable.Rows) {

lbProducts.Items.Add(dataRow["ProductName"].ToString()); }

16. Implement the SelectedIndexChanged method.


private void lbProducts_SelectedIndexChanged( object sender, System.EventArgs e) {

17. Extract the DataTable from session state and save to a local object.
DataTable dataTable = (DataTable) Session["dataTable"];

18. Extract the Selected row from the DataTable and store in a local DataRow object.
DataRow selectedRow = dataTable.Rows[lbProducts.SelectedIndex];

19. Fill the text fields from the selected row. 19-36 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Web Controls


txtProductName.Text = selectedRow["ProductName"].ToString(); txtProductID.Text = selectedRow["ProductID"].ToString(); txtSupplierID.Text = selectedRow["SupplierID"].ToString(); txtCategoryID.Text = selectedRow["CategoryID"].ToString(); txtQuantityPerUnit.Text = selectedRow["QuantityPerUnit"].ToString(); txtUnitPrice.Text = selectedRow["UnitPrice"].ToString(); txtUnitsInStock.Text = selectedRow["UnitsInStock"].ToString(); txtUnitsOnOrder.Text = selectedRow["UnitsOnOrder"].ToString(); txtReorderLevel.Text = selectedRow["ReorderLevel"].ToString();

20. Set the drop-down list control to true or false.


cbDiscontinued.SelectedIndex = selectedRow["Discontinued"].ToString() == "False" ? 1 : 0;

21. Build and run the application. Scroll to Teatime Chocolate Biscuits and click on it. The fields will be filled in as shown in figure Figure 12.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

19-37

Lab 19: Programming Web Forms

Figure 12. Running the application.

19-38

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Attributes and Reflection

Attributes and Reflection


Objectives
Understand the difference between custom and intrinsic attributes. Understand what an attribute target is and how targets are described. Create custom attributes and designate their target elements. Use reflection to view the metadata youve added to your program with attributes. Use reflection to discover the types and methods of classes in assemblies. Use reflection to dynamically invoke a method in an assembly.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

20-1

Attributes and Reflection

Attributes
.NET applications consist of code, data, and metadata. The metadata is information about your program that is stored along with your program. This makes your program fully self-contained and (ideally) self-documenting.

Key Term
Attribute An object that represents metadata.

Attributes come in two flavors: intrinsic and custom. Intrinsic attributes are part of the C.L.R. (Common Language Runtime), while custom attributes are created by you to use in your own code. Most programmers will use only intrinsic attributes. You associate an attribute with an element in your program. That element is said to be the target of the attribute. The designer of the attribute determines what the legal targets for that attribute are. Potential targets are: Assembly Class Constructor Delegate Enum Event Field Interface Method Module Parameter Property ReturnValue Struct

The All target is the same as listing all of the above. The ClassMembers attribute is the same as listing all the bold elements above.

20-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Attributes
Most intrinsic attributes are used when interoperating with COM (a topic covered in another chapter), though some are used when remoting or serializing objects (also covered in other chapters). Some attributes are used in helping to organize large projects.

Custom Attributes
You can create your own attributes, and doing so is one of the best ways to understand what attributes are and how they are used. In the next example, you will create your own attribute to assist in commenting your code. Using an attribute allows you to access the comment at run time, through a process called reflection, which is discussed later in this chapter.

Try It Out!
Imagine your development team wants to keep track of code reviews. Each time you finish a code review youd like that noted in the code itself, along with the date of the review and the name of the reviewer. By having this information in the code, you can see it in context, but you can also extract the information to a database to make searching easier. You will write another application that will go through your code and find the reviews using reflection (youll learn about reflection shortly). See Attributes.sln In this example youll write a simple console application to demonstrate how to create custom attributes. To get started, create an Employee class, so that you can assign attributes to the methods and properties of the class. 1. Start by creating a new console application. 2. Create a class Employee and give it a few private member variables.
class Employee { private string name; private string empID; private int age; private int salLevel = 1;

3. Add a constructor and a SalaryLevel property.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

20-3

Attributes and Reflection


public Employee(string name, string empID, int age) { this.name = name; this.empID = empID; this.age = age; } public int SalaryLevel { get { return salLevel; } set { salLevel = value; } }

4. You can create an Employee and get or set the Employees salary. Add one more method, BumpSalary that takes an integer and increments the salary by that amount.
public void BumpSalary(int bump) { salLevel += bump; }

5. Create a Tester class. Implement a Run method to instantiate an Employee object and bump the salary.

20-4

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Attributes
class Tester { static void Main() { Tester t = new Tester(); t.Run(); }

public void Run() { Employee emp1 = new Employee("Fred","101",35); Console.WriteLine( "emp1 sal before bump: {0}", emp1.SalaryLevel); emp1.BumpSalary(4); Console.WriteLine( "emp1 sal after bump: {0}", emp1.SalaryLevel); } } // end Run // end Tester class

The output is shown in Figure 1.

Figure 1. Testing the Employee class.

Creating the Attribute


You are ready to create the Attribute. It is important to realize that attributes are classes. All attribute classes derive (ultimately) from System.Attribute. When you create an attribute class you must identify the target. To do so use a (surprise!) attribute. The attribute you use is the AttributeUsage attribute. The AttributeUsage attribute is metadata about your metadata, or meta-metadata.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

20-5

Attributes and Reflection

Try It Out!
See Attributes.sln Return to the earlier example and add a new Attribute class. 1. Add a new class CodeReviewAttribute and derive from System.Attribute.
class CodeReviewAttribute : System.Attribute {

2. Set the targets for your new class, using the AttributeUsage attribute. To set the targets, use the AttributeTargets enumeration. You may or multiple values together. (When you or two values, you combine them using the OR operator). Set the AllowMultiple attribute to true. That allows you to assign more than one CodeReviewAttribute to any given target:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Interface | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple=true)] class CodeReviewAttribute : System.Attribute {

3. Give the new attribute class three private members.


private string reviewDate; private string reviewerName; private string reviewComments;

4. Create three properties. The Date and Name properties will be read-only and the Comment property will be read/write.

20-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Attributes
public string date { get { return reviewDate; } } public string name { get { return reviewerName; } } public string comment { get { return reviewComments; } set { reviewComments = value; } }

5. Create a constructor that allows you to set two of the attributes.


public CodeReviewAttribute( string date, string name) { reviewDate = date; reviewerName = name; }

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

20-7

Attributes and Reflection

Positional and Named Attributes


Attributes that are set in the constructor are called Positional attributes. Attributes that are set through a property are called Named attributes. In the previous example, the date and name are positional. The date must be passed as the first argument to the constructor and the name must be set as the second. The comment itself, however, is a positional parameter. You can see how this works, by adding a CodeReviewAttribute to the Employee class. Since AttributeTargets.Class is one of the targets of the CodeReviewAttribute it is legal to add a comment to the class itself:
[CodeReviewAttribute("08/01/2005", "Jesse Liberty", comment="Do we have a conflict with this name?")] class Employee {

Notice that the date and name are just passed in positionally, but the comment attribute must be identified by name.

AllowMultiple
The AllowMultiple attribute that you added to the AttributeUsage attribute determines if any given target can be the recipient of more than one CodeReviewAttribute. Since you set this to true, you may add additional CodeReviewAttributes to the class:
[CodeReviewAttribute("08/01/2005", "Jesse Liberty", comment="Do we have a conflict with this name?")] [CodeReviewAttribute("08/02/2005", "James Supervisor", comment="Let's find another name for this class")] class Employee {

20-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Attributes

Try It Out!
See Attributes.sln Return to the earlier example and add Attributes to the Employee class.

1. Add attributes to the class as discussed earlier.


[CodeReviewAttribute("08/01/2005", "Jesse Liberty", comment="Do we have a conflict with this name?")] [CodeReviewAttribute("08/02/2005", "James Supervisor", comment="Let's find another name for this class")] class Employee {

2. Add a CodeReviewAttribute to the salLevel property.


[CodeReviewAttribute("08/02/2005", "Jesse Liberty", comment = "does a default make sense for this?")] private int salLevel = 1;

3. Add an attribute to the constructor.


[CodeReviewAttribute("08/02/2005", "James Supervisor", comment = "Let's change the empID to int")] public Employee(string name, string empID, int age) { this.name = name; this.empID = empID; this.age = age; }

4. Add an attribute to the method.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

20-9

Attributes and Reflection


[CodeReviewAttribute("08/02/2005", "James Supervisor", comment = "Do we need this method if there is a Salary property?")] public void BumpSalary(int bump) { salLevel += bump; }

5. Add an attribute to a property.


[CodeReviewAttribute("08/02/2005", "James Supervisor", comment = "Should this be read only?")] public int SalaryLevel { get { return salLevel; } set { salLevel = value; } }

6. Build the application and run it. The results are shown in Figure 2. Notice that it is totally unchanged! The attributes are in the code, but there is no change to the output.

Figure 2. Running the application with attributes.

7. Perhaps you should be skeptical that the attributes are really there? Open ILDasm (the Intermediate Language DisASseMbler) as shown in Figure 3. To open your program in the disassembler, click on File|Open and navigate to your project, then navigate into the Bin and Debug directories and click on the executable program. 20-10 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Attributes

Figure 3. Opening ILDasm.

8. Double-click on the Employee class to open it. Double-click on the first entry under the class to open the meta information about the class, as shown in Figure 4. You see that the two class comments are there, as expected (circled and highlighted in the Figure 4).

Figure 4. Meta information about the class.

Return to the disassembler, and double-click on BumpSalary. Once again, you see the expected attribute for that method, as shown in Figure 5.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

20-11

Attributes and Reflection

Figure 5. The attributes for BumpSalary.

The attributes are in the class, but what good are they if you cant get to them, and they have no impact on the running of the program? This is what reflection is for.

20-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Reflection

Reflection
Reflection is the process of a program looking at itself. The goal of reflection is to obtain objects within the running program that describe either the currently running program, or by extension, any other program. With reflection you cannot only examine the methods and structure of the program, but you can also get hold of the metadata. Reflection is tightly coupled with attributes, so it makes attributes useful. Reflection is used for viewing metadata as well as type discovery, late binding, and dynamic invocation.

View MetaData
This section will look at using reflection to view metadata. Later sections will examine type discovery, late binding, and dynamic invocation. Reflection allows you to examine the metadata embedded with your program, including metadata created by custom attributes. This section examines the metadata from the previous example. Youll then go on, in subsequent examples, to discover the types in an assembly and then to bind to those types and invoke methods on them.

Try It Out!
See Attribute.sln In the next example, youll reflect on the attributes you created in the previous example. 1. Reopen the previous example. 2. At the top of the program, add the namespace youll need for reflection.
using System.Reflection;

3. In the Run method, add an object of type MemberInfo. Initialize this object by calling the typeOf operator. TypeOf returns an object of type System.Type. MemberInfo is the base class of Type and allows you to obtain information for all members of a class.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

20-13

Attributes and Reflection


MemberInfo inf = typeof(Employee);

4. Call the GetCustomAttributes method on the MemberInfo object. This returns an array of the custom attributes. You specify, in the parameter, whether you want the type to search up its inheritance chain; in this case you do not want the attributes of the base class (and in this case there is no base class) so you specify false. The attributes are returned as an array of objects.
object[] attributes = inf.GetCustomAttributes(false);

5. Iterate over the array of attributes. Cast each to a CodeReviewAttribute (this is safe, you have only one kind of attribute in this example) and then use that CodeReviewAttribute to print the value of the date, name, and comment.
foreach (object attribute in attributes) { CodeReviewAttribute attr = (CodeReviewAttribute) attribute; Console.WriteLine( "\nThis class was reviewed on {0} by {1}. " + "\nComments: {2}", attr.date, attr.name, attr.comment); }

6. Use the typeof keyword again; this time store the results in a Type object. Type derives from MemberInfo and provides methods to get an array of MemberInfo objects about each member in the type.
Type employeeType = typeof(Employee); MemberInfo[] mbrInfoArray = employeeType.GetMembers();

7. Iterate over the collection of MemberInfo objects and get the attributes for each member of the class.
foreach (MemberInfo mbrInfo in mbrInfoArray) { attributes = mbrInfo.GetCustomAttributes( false);

20-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Reflection
8. Iterate over the collection of attributes returned for each member of the class, and display the attribute information.
foreach (object attribute in attributes) { CodeReviewAttribute attr = (CodeReviewAttribute) attribute; Console.WriteLine( "\nThis code was reviewed on {0} by {1}. " + "\nComments: {2}", attr.date, attr.name, attr.comment); }

9. Compile and run the program. This time the attributes are reflected and displayed to the console, as shown in Figure 6.

Figure 6. Displaying the attributes.

Type Discovery
Using reflection, you can examine the types in an assembly and instantiate them. This can be very useful in creating custom scripts.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

20-15

Attributes and Reflection

Try It Out!
See discovery.sln To see how Type Discovery works, youll create a simple console application to discover the types in the Mscorlib.dll provided with .NET. 1. Create a new console application and name it Discovery. 2. Create a class Discovery and a Run method. 3. In the Run method, create an instance of the Assembly class. An assembly is the basic unit of compilation; representing a DLL or an EXE file. Initialize the Assembly with the static Load method, passing in the name of the DLL you want to examine.
class Discovery { static void Main(string[] args) { Discovery d = new Discovery(); d.Run(); }

public void Run() { Assembly asm = Assembly.Load("Mscorlib.dll");

4. Call GetTypes on the Assembly object, getting back an array of Type objects representing all the types in the assembly.
Type[] types = asm.GetTypes();

5. Iterate over the types. You can pass the type to WriteLine. The ToString() method will be called implicitly and the Type will display its name. Use the Length property of the array to display how many types were found.
foreach (Type type in types) { Console.WriteLine("Type is {0}", type); } Console.WriteLine("\n{0} types found.",types.Length);

20-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Reflection
6. Compile and run the program. The results can be quite long. An excerpt is shown in Figure 7.

Figure 7. An excerpt of the types found.

Late Binding and Dynamic Invocation


Late binding to a method allows you to creating dynamically instantiated objects. That is, you can bind to objects that you discover through type discovery, and then once bound, you can use those objects as if they were compiled into your program (though you will pay a performance penalty). Once youve discovered an object, and bound to it, you can use reflection to invoke methods at run time. You can pass parameters to methods of a late bound object, and you can get back return values. Again, the only problem is that the performance of late bound objects is far slower than with compiled objects.

Try It Out!
See lateBinding.sln To see how late binding and dynamic invocation works, youll create a simple console application to dynamically invoke the Pow method from the System.Math dll. To be perfectly clear, there is no good reason to invoke this method dynamically. You know the method exists, you can just instantiate an object and invoke the method. Youll do so here just to demonstrate how late binding and dynamic invocation works. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

20-17

Attributes and Reflection


1. Create a new console application and name it lateBinding. 2. Create a Tester class, and within it a Run method. In the Run method, instantiate a Type object and assign to it the result of calling the static method GetType, passing in the name of the type you are interested in using.
class Tester { static void Main(string[] args) { Tester t = new Tester(); t.Run(); } public void Run() { Type mathType = Type.GetType("System.Math");

3. Instantiate an instance of the type youve asked for by calling the static method CreateInstance on the Activator class, passing in the Type object you just obtained.
Object obj = Activator.CreateInstance(mathType);

4. obj now represents the math type. You want to call a method on that type. To do so, you need to call GetMethod and pass in an array of Type objects that describe the types of the parameters. To do so, youll create the array and then fill the members with the Type objects for a Double (Pow takes two doubles).
Type[] parameterTypes = new Type[2]; parameterTypes[0] = Type.GetType("System.Double"); parameterTypes[1] = Type.GetType("System.Double");

What is going on here is that you pass the string System.Double to the static method GetType of the Type class. What you get back is a Type object for the System.Double class. You add that to your array of Type objects. 5. You are now ready to call GettMethod to get the Pow method. You pass in a string representing the name of the method and the array of Type objects 20-18 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Reflection
describing the parameters for the method. Remember, methods can be overloaded by varying the number or type of the parameters; this allows the system to return the one method that matches the signature: name and parameters.
MethodInfo powInfo = mathType.GetMethod("Pow",parameterTypes);

6. You can now invoke the method. You must call Invoke on the MethodInfo object you got back in Step 5, passing in the Object you got back from CreateInstance in Step 3, along with an array of objects representing the parameters.
object[] parameters = new object[2]; parameters[0] = 5; parameters[1] = 3; object returnValue = powInfo.Invoke(obj,parameters);

7. The value returned by Pow is an object that you can cast to a Double if you need to manipulate the return value. In this case, youll just pass that returned object to WriteLine which will call ToString on the object and display its value:
Console.WriteLine( "5 to the 3rd power is {0}",returnValue);

8. Compile and run the program. The result is shown in Figure 8. Youve invoked the Pow instance method from the System.Math dll without ever instantiating an object. This is dynamic invocation; it may not be pretty, but it is a powerful technique that can be used in dynamic scripting.

Figure 8. Invoking the method dynamically.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

20-19

Attributes and Reflection

Summary
The .NET Framework provides a number of intrinsic attributes that you might use when working with COM or network I/O. You are free to create your own custom attributes. When you create a custom attribute, you must designate the target for that attribute (e.g., assembly, class, method, etc.). You describe the target of your attribute with an attribute! Attributes are created as classes derived from System.Attribute. Attributes can be positional or named. Positional attributes are implemented in the constructor, named attributes are implemented with properties. Reflection allows you to inspect the metadata in your program at run time. Reflection also allows you to discover the types in an assembly and the members of a class, both at run time. Reflection allows you to dynamically invoke methods at run time.

20-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Reflection

Questions
1. What does the ClassMembers target indicate? 2. How can you see the attributes embedded in your program? 3. What is the difference between a named and a positional attribute? 4. What does the AllowMultiple attribute do? 5. What does type discovery mean? 6. What does dynamic invocation mean?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

20-21

Attributes and Reflection

Answers
1. What does the ClassMembers target indicate?
The ClassMembers target indicates that the attribute can be applied to any of the targets that indicate members of the class (e.g., method, property, field, etc.).

2. How can you see the attributes embedded in your program?


You can inspect the source code, you can use ILDasm to inspect the IL code, or you can use reflection to inspect the metadata.

3. What is the difference between a named and a positional attribute?


A named attribute is entered with the name of the attribute followed by an equal sign and then the value (comment = hello). With a positional attribute you simply enter the value, but you must enter positional attributes first, and in the order designated in the constructor.

4. What does the AllowMultiple attribute do?


The AllowMultiple attribute is used in the attribute describing the custom attribute, and it allows multiple instances of your custom attribute to be applied to any given target.

5. What does type discovery mean?


Type discovery is the process of finding out what classes are defined in an assembly.

6. What does dynamic invocation mean?


Dynamic invocation is the process of calling a method at run time on an object that was not defined in your program, but that is accessed through reflection.

20-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Reflection

Lab 20: Attributes and Reflection

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

20-23

Lab 20: Attributes and Reflection

Lab 20 Overview
In this lab youll learn about using reflection for Discovery and Late Binding. To complete this lab, youll need to work through two exercises: Using Reflection for Discovery Using Reflection for Late Binding

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

20-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Using Reflection for Discovery

Using Reflection for Discovery


Objective
In this exercise, youll use the Reflection methods to discover the methods of the Array class.

Things to Consider
A Type object can be used to gather information about any known type. The Type object has a GetMembers method that returns an array of MemberInfo objects, each of which has information about a particular member of the type.

Step-by-Step Instructions
1. Create a new Windows Console Application named Discovery. 2. At the top of the file, add a using statement for System.Reflection.
using System.Reflection;

3. Change the namespace to DiscoveryStarter.


namespace DiscoveryStarter {

4. Implement the class DiscoveryStarter.


class DiscoveryStarter {

5. Implement Main to run the program.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

20-25

Lab 20: Attributes and Reflection


static void Main(string[] args) { DiscoveryStarter d = new DiscoveryStarter(); d.Run(); }

6. Implement the Run method.


public void Run() {

7. Create a Type object and fill it with the Type object returned by the Static method GetType. Pass in the string "System.Array".
Type theType = Type.GetType("System.Array");

8. Display the Type you received.


Console.WriteLine("Got this type: {0}", theType);

9. Call GetMembers on the Type object and assign the resulting array to a local array of type MemberInfo.
MemberInfo[] memberInfoArray = theType.GetMembers();

10. Iterate over the array, and display each MemberInfo object and also the MemberType property of that object.
foreach (MemberInfo mi in memberInfoArray) { Console.WriteLine("{0} is a member of {1}", mi, mi.MemberType); }

20-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Using Reflection for Discovery


11. End the Run method, the DiscoveryStarter class, and the namespace.
} } } // // // end Run method end class DiscoveryStarter end namespace

12. Run the application. Your results should look like Figure 9.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

20-27

Lab 20: Attributes and Reflection

Figure 9. Running the Discovery application.

20-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Using Reflection for Late Binding

Using Reflection for Late Binding


Objective
In this exercise, youll late bind to an existing method in the Math class, through reflection.

Things to Consider
You can get a Type object by calling the static GetType method on the Type class. You can get info about a particular method by calling GetMethod on the Type object. When you ask for a method, you must pass in an array describing the parameter types.

Step-by-Step Instructions
1. Create a new console application named LateBinding. 2. Add a using statement to the top of the file for System.Reflection.
using System.Reflection;

3. Change the namespace to LateBindingStarter.


namespace LateBindingStarter {

4. Implement the class Tester.


class Tester {

5. Implement Main to run the program. C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

20-29

Lab 20: Attributes and Reflection


static void Main(string[] args) { Tester t = new Tester(); t.Run(); }

6. Implement the Run method.


public void Run() {

7. Create a Type object for the math type and initialize it by calling the static GetType method, passing in the string "System.Math".
Type mathType = Type.GetType("System.Math");

8. Create an instance of the Math type by calling the static method CreateInstance of the Activator class.
Object obj = Activator.CreateInstance(mathType);

9. Create an array to hold the parameter types. In this case there is only one parameter.
Type[] parameterTypes = new Type[1];

10. Fill the array with the Double type you retrieve by passing the string "System.Double" to the static GetType method of the Type class.
parameterTypes[0] = Type.GetType("System.Double");

11. Call the GetMethod instance method of the Type object you created in Step 7. Pass in the string "Sqrt" to get the square root method; also pass in your array of parameter types. Assign the resulting MethodInfo object to a local instance.

20-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Using Reflection for Late Binding


MethodInfo methodInfo = mathType.GetMethod("Sqrt",parameterTypes);

12. Create an array to hold the parameter.


object[] parameters = new object[1];

13. Assign the value 16 to the first member of the array of parameters.
parameters[0] = 16;

14. Create an object to hold the value returned by calling the instance member Invoke on the MethodInfo object. Pass in the object you created in Step 8, as well as the array of parameters.
object returnValue = methodInfo.Invoke(obj,parameters);

15. Use the object you got back in Step 14 to display the square root of the parameter you passed in.
Console.WriteLine("The square root of 16 is {0}",returnValue);

16. End the Run method, the Tester class, and the namespace.
} } } // // // end Run method end class Tester end namespace

17. Run the application. Your results should look like Figure 10.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

20-31

Lab 20: Attributes and Reflection

Figure 10. Running the late binding application.

20-32

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Threads

Threads
Objectives
Understand how threads support time slicing and apparently simultaneous operations. Create multiple threads running the same or different methods. Use sleep to delay a thread or yield control of the processor. Use Abort to request that a thread kill itself. Instruct a thread to stop processing until other threads complete. Synchronize two threads using a lock. Synchronize threads using the Interlocked class.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-1

Threads

Threads in .NET
As a computer user you tend to think of an application or a program. When you start up your favorite word processor or game, you are actually starting a process. Each program runs in its own process. As a programmer, you know that within a process, you may want to have more than one thing happening at a time. A thread acts as a lightweight process; it allows more than one thing to appear to happen at the same time without the same overhead of a full process. Of course, on most computers there is only one chip, and so only one thing can actually happen at a time, but the computer is able to switch among a few things so rapidly that it appears that multiple things are happening all at once, as illustrated in Figure 1. In this illustration you have four tasks running at the same time. The chip divides its time, here illustrated as ten time slices per second, though that is just for illustration purposes. Notice that each task gets a time slice and then the chip moves to the next task. Also notice that not every task receives the same percentage of time; some tasks get two slices per second, some get more (or less).

Figure 1. Time slicing.

21-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Threads in .NET
It turns out that adding threads to your program can actually slow it down! This is because time slicing has overhead; the processor must store away its current state before it switches context to the next task. Threading can greatly increase the performance of your application, however, if the multitasking allows the chip to be busy with other tasks, while you would otherwise be waiting for something slow, like a file read or (even slower!) an Internet access. Most programmers will never create their own explicit threads, because the .NET framework provides higher-level classes for managing most situations where you would otherwise need to create a thread. That said, if you do want to create your own threads, you certainly can do so and the .NET framework provides extensive support for managing your threads.

Creating Threads
The easiest way to create a thread is to instantiate a Thread object. The Thread constructor requires a delegate of type ThreadStart. The ThreadStart delegate is given a reference to the method you want to run in the thread. The delegate describes the signature and return value of the method it will encapsulate. Thread methods must return void and take no parameters.

Try It Out!
See Threads1.sln It is easy to create a thread in your program as illustrated in this first simple example. 1. Create a new console application and name it Threads1. 2. Create a Run method. 3. In the Run method, declare a Thread object and initialize it with a ThreadStart delegate wrapping the CountUp method (that you will write shortly) and start the thread.
public void Run() { Thread threadOne = new Thread(new ThreadStart(CountUp)); threadOne.Start(); }

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-3

Threads
4. Create the CountUp method to count from 0 to 10,000 and display the results on the console.
public void CountUp() { for (int i = 0; i < 10000; i++) Console.WriteLine("Count up: {0}", i); }

5. Start the application. A command window opens and the thread counts up to 9,999, as shown in Figure 2.

Figure 2. Running a single thread.

Creating Multiple Threads


While this shows how easy it is to create a thread, it doesnt exactly show off the power of multithreading. You could have accomplished precisely the same effect without a thread. To show the effect of multithreading youll need a second thread. You also want a way to identify which thread is doing what work. The Thread class has a static property CurrentThread that returns the currently running thread. This allows you access to the thread itself from within the running method. The Thread class also has an instance property Name that allows you to set or query the name of the thread; a string you provide to uniquely identify a given thread.

21-4

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Threads in .NET

Try It Out!
See Threads1.sln In the next example youll create multiple threads and display which thread is writing to the console at any given moment. 1. Reopen Threads1, the application you just created. 2. Modify the Run method to create a second thread, and initialize it with a delegate to the same CountUp method.
public void Run() { Thread threadOne = new Thread(new ThreadStart(CountUp)); Thread threadTwo = new Thread(new ThreadStart(CountUp));

3. Assign names to the threads and then start them.


threadOne.Name = "Thread One"; threadTwo.Name = "Thread Two"; threadOne.Start(); threadTwo.Start(); }

4. Modify the CountUp method to display the name of the thread. Do so by displaying the Name property of the current thread. Obtain the current thread using the static property CurrentThread.
public void CountUp() { for (int i = 0; i < 10000; i++) Console.WriteLine("{0},Count up: {1}", Thread.CurrentThread.Name, i); }

5. Compile and run the program. Youll see the threads switch off. One thread runs briefly, then the other, switching back and forth until both have run their course, as shown in Figure 3. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-5

Threads

Figure 3. Threads switch off.

Notice that the local integer variable i is independent in Thread Two from the same variable in Thread One. The threads maintain their own instances of these variables. You do not have to assign the same method to two threads, of course. You may assign any method to the delegate as long as it matches the signature for the delegate.

Multiple Tasks
The various threads can do different things; they can call different methods that will (appear to) run simultaneously.

Try It Out!
See Threads1.sln In the next variation on your example, youll create a thread with a second method, CountDown. 1. Reopen the Threads1 project and add a second method, CountDown.

21-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Threads in .NET
public void CountDown() { for (int i = 10000; i > 0; i--) Console.WriteLine("{0},\t\tCount down: {1}", Thread.CurrentThread.Name,i); }

2. Modify the Run method to create a third thread, and have the third thread invoke the CountDown method.
public void Run() { Thread threadOne = new Thread(new ThreadStart(CountUp)); Thread threadTwo = new Thread(new ThreadStart(CountUp)); Thread threadThree = new Thread(new ThreadStart(CountDown)); threadOne.Name = "Thread One"; threadTwo.Name = "Thread Two"; threadThree.Name = "Thread Three"; threadOne.Start(); threadTwo.Start(); threadThree.Start(); }

3. Compile and run the program. All three threads start, and take turns running. The third thread counts down from 10,000 while the others count up. All three run independently of one another, as shown in Figure 4.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-7

Threads

Figure 4. Running three threads.

Arrays of Threads
You can manage your threads in arrays, rather than creating each one individually. You do this by simply creating an array of Thread objects and initializing the array with newly instantiated threads.
Thread[] threads = { new Thread( new ThreadStart(CountUp) ), new Thread( new ThreadStart(CountUp) ), new Thread( new ThreadStart(CountDown) ), };

Rather than individually naming each by hand, you can use a counter to set the name in a loop. You can iterate over the array of threads with a foreach loop.
int counter = 1; foreach (Thread thread in threads) { thread.Start(); thread.Name="Thread Number " + counter.ToString(); Console.WriteLine("Started thread {0}", thread.Name); counter++; }

21-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Threads in .NET

Sleep
In the previous examples, you allowed the chip to switch among your threads however frequently it wanted to. There are times, however, when you will want to suspend a thread momentarily; perhaps to allow some time to pass, or perhaps to allow another thread time to run. To do so, use the static Sleep method. You pass in an integer representing the number of milliseconds you want the thread to sleep (or you can pass in a TimeSpan object, if that is easier in your particular program). When a thread encounters the Sleep call, the thread is suspended for the designated time and other threads are allowed to run.

Try It Out!
See Threads2.sln To see sleep at work (and creating threads in arrays) create a new simple test application. 1. Reopen the project you were working on in the previous example. 2. Modify Run to create an array of threads, and to start them in a foreach loop.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-9

Threads
public void Run() { Thread[] threads = { new Thread( new ThreadStart(CountUp) ), new Thread( new ThreadStart(CountUp) ), new Thread( new ThreadStart(CountDown) ), };

int counter = 1; foreach (Thread thread in threads) { thread.Start(); thread.Name="Thread Number " + counter.ToString(); Console.WriteLine("Started thread {0}", thread.Name); counter++; }

3. Modify the CountUp and CountDown methods to count to and from 10 (rather than 10,000) to simplify the display. Add a Sleep call for 1 millisecond to each method. This yields control of the processor and causes the next thread to run.

21-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Threads in .NET
public void CountUp() { for (int i = 0; i < 10; i++) { Console.WriteLine("{0}: Count up: {1}", Thread.CurrentThread.Name,i); Thread.Sleep(1); } }

public void CountDown() { for (int j= 10; j > 0; j--) { Console.WriteLine("{0}: Count down: {1}", Thread.CurrentThread.Name,j); Thread.Sleep(1); } }

4. Build the application and run it. As shown in Figure 5, each thread displays its value and then yields to the next thread. The result is that the threads run interleaved, one line per thread until they are all completed.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-11

Threads

Figure 5. Yielding with Sleep.

Try increasing the Sleep from 1 to 100. The display should stutter down the page. Try increasing it to 500 and then to 1,000. You begin to see the delaying effects of Sleep.

Joining Threads
At times, youll want your current thread to wait until another thread completes working before your current thread continues. For example, suppose you wanted to modify the previous example to display the words, All the threads are done when all three threads complete. You cant just put a Console.WriteLine statement after you launch the three threads, because the threads run independently and your Run() method will continue right to the output as soon as it launches the other threads.

21-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Threads in .NET

Try It Out!
See Threads3.sln Try modifying the previous version to print All threads completed after the threads are launched, 1. Reopen the previous project. 2. Modify the Run method and add a WriteLine statement as the last line in the method.
public void Run() { Thread[] threads = { new Thread( new ThreadStart(CountUp) ), new Thread( new ThreadStart(CountUp) ), new Thread( new ThreadStart(CountDown) ), };

int counter = 1; foreach (Thread thread in threads) { thread.Start(); thread.Name="Thread Number " + counter.ToString(); Console.WriteLine("Started thread {0}", thread.Name); counter++; } Console.WriteLine("All threads completed."); }

3. Build and run the application. The results are shown in Figure 6.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-13

Threads

Figure 6. Displaying All threads completed.

Notice that the threads are started and then the All threads completed message is displayed immediately! It turns out that the Run method is itself in a thread. This is the global thread that all programs run. You do not have to create that thread, it is created for you and it is not named, though you are free to name it. 4. Add a line to the top of Run to name the current thread.
public void Run() { Thread.CurrentThread.Name = "Global thread";

5. Modify the WriteLine at the bottom of Run to display the name of the thread. 21-14 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Threads in .NET
Console.WriteLine("{0}: {1}", Thread.CurrentThread.Name, "All threads completed.");

6. Run the application again. As you can see in Figure 7, Run is running in a thread. Threads 1, 2, and 3 are spawned from within the global thread and are considered child threads of the global thread.

Figure 7. Seeing the global thread.

The reason the display did not work as intended is that you must pause the global thread, while the child threads run. You do so by joining the global thread to its children. This instructs the global thread to wait until they complete before it runs any further. You must explicitly join the global thread to each thread you want it to wait for.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-15

Threads
7. Add a line of code to Run to join each thread just before you display the display of the message All threads completed. Iterate over the threads array, and for each thread, call the Join method. This joins the current thread (in this case, the global thread) to the thread on which you call Join. The current thread will now wait for that thread to complete before proceeding:
foreach(Thread thread in threads) thread.Join();

8. Build and run the application. As shown in Figure 8, the global thread now waits for the others to complete before printing the All threads completed message.

Figure 8. Joining threads.

21-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Threads in .NET
9. You can make the demise of the threads more explicit by displaying the end of the thread in each of the CountDown and CountUp methods. After the loop completes, but just before the method ends, display that the thread is ending:
public void CountUp() { for (int i = 0; i < 10; i++) { Console.WriteLine("{0} Count up: {1}", Thread.CurrentThread.Name,i); Thread.Sleep(1); } Console.WriteLine("{0} Exiting", Thread.CurrentThread.Name); }

public void CountDown() { for (int i= 10; i > 0; i--) { Console.WriteLine("{0} Count down: {1}", Thread.CurrentThread.Name,i); Thread.Sleep(1); } Console.WriteLine("{0} Exiting", Thread.CurrentThread.Name); }

10. Take the Sleep call out of CountDown to eliminate its yielding the processor each time it runs a single line. 11. Build and run the application once more. The results are shown in Figure 9. You can see that thread3 runs its entire course and then exits. Threads 1 and 2 continue to take turns and then exit when they are done. Only when all the joined threads have exited does the original thread resume and print its happy message.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-17

Threads

Figure 9. Exiting threads.

Killing Threads
Normally, threads die of old age. At times, however, due to conditions that arise while your program is running, you may need to kill a thread. You dont actually kill a thread so much as ask the thread to commit suicide. To do so, you call the threads Abort method. When you do this, an exception of type ThreadAbortedException is thrown, and the thread catches the exception and cleans up after itself.

21-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Threads in .NET

Try It Out!
See Threads4.sln Try modifying the previous version to kill a couple of threads and handle the thread exception that is thrown. 1. Reopen the previous example. 2. Create four threads in your initial array.
public void Run() { Thread[] threads = { new Thread(new ThreadStart(CountUp)), new Thread(new ThreadStart(CountDown)), new Thread(new ThreadStart(CountUp)), new Thread(new ThreadStart(CountDown)) };

int counter = 1; foreach(Thread thread in threads) { thread.Start(); thread.Name = "Thread Number " + counter.ToString(); Console.WriteLine("Started thread {0}", thread.Name); counter++; Thread.Sleep(1); }

3. Immediately Abort the second and third threads. Then join the threads and wait for them all to complete.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-19

Threads

threads[2].Abort();

foreach(Thread thread in threads) thread.Join();

Console.WriteLine("All threads completed"); } // end Run

4. Modify CountUp to iterate over its value in a try block.


public void CountUp() { try { for (int i = 0; i < 500; i++) Console.WriteLine( "{0},Count up: {1}", Thread.CurrentThread.Name, i); }

5. Create a catch for the ThreadAbortException.


catch(ThreadAbortException) { Console.WriteLine( "Thread {0} caught ThreadAbortException. Cleaning up.", Thread.CurrentThread.Name); }

In this case you are doing nothing but displaying a message when the thread is destroyed, but you can imagine cleaning up allocated resources when the exception is caught. 6. Add a finally block as well, so that your thread can exit cleanly whether it was aborted or not.

21-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Threads in .NET
Finally { Console.WriteLine( "{0} Exiting...", Thread.CurrentThread.Name); }

7. Do the same for the CountDown method.


public void CountDown() { try { for (int i = 500; i >= 0; i--) Console.WriteLine( "{0},Count Down: {1}", Thread.CurrentThread.Name, i); } catch(ThreadAbortException) { Console.WriteLine( "Thread {0} caught ThreadAbortException. Cleaning up.", Thread.CurrentThread.Name); } finally { Console.WriteLine( "{0} Exiting...", Thread.CurrentThread.Name); } }

8. Compile and run the application. The various threads start and then the second and third thread are stopped by a ThreadAbortException, as shown in Figure 10. You may need to use CTRL+S to freeze the scrolling display so that you can see the threads exit.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-21

Threads

Figure 10. Aborting threads.

21-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Synchronization

Synchronization
When you introduce multithreading to your application you expose yourself to a number of difficult and advanced issues. Various threads may access the same objects simultaneously and this can cause profound synchronization problems. How do you control access to a resource so that only one thread can access it at the same time? To understand how this is done, consider how you control access to any limited precious resource, such as a restroom on an airplane. There may be only one or two restrooms for three or four hundred people. It is not uncommon for there to be more requests for the resource than can be accommodated at any one time, but it is imperative that access to the bathroom be synchronized; you do not want one passenger interrupting another.

Locking
The solution is to lock the door when the resource is in use. When the passenger is finished with the resource, the bathroom is unlocked. You perform a similar operation on objects you must synchronize among various threads. When the first thread wants the object it locks the door. A little light goes on above the object saying occupied and the object cannot be accessed until the first thread is finished, washes its hands, and opens the lock. Consider the example of a record in a database (or an object in memory for that matter). The record has the value 20 (to keep things simple). You access the value from two threads: thread1 and thread2. Thread1 updates the value to 30 and thread2 updates the value to 40. Thread1 then writes the value back, and then thread2 writes its value back, overwriting the work of thread1. This is very similar to the problem of two processes interacting with a disconnected database record. You see this all the time in threads when you must update a value held in memory. Thread1 accesses the value, then thread2 does. Thread 1 updates the value. Thread2 should then be working with the updated value, but because it got the value before it was updated by thread1, thread2 is now working with corrupted data. To solve this, .NET provides a number of locking mechanisms.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-23

Threads

Try It Out!
See Synchronization1. sln To see the problem youre trying to solve, imagine that you have a shared resource such as a database record. In this simple example, youll just add to a class variable. 1. Reopen the previous example. 2. Add a class field to act as the resource shared by various threads.
private int synchValue = 0;

3. Delete the CountDown method; you wont need it. 4. Modify the CountUp method to modify the synchValue field. To do so, assign the value in synchValue to a local variable, temp.
public void CountUp() { try { for (int i = 0; i < 20; i++) { int temp = synchValue;

This is not unlike fetching a value from a database or other repository. Now that you have a copy of the value, increment it.
temp++;

5. Sleep for two milliseconds to allow other threads to access the processor. This mimics the fact that your method might run long enough that it could be interrupted.
Thread.Sleep(2);

6. When you get access to the processor again, assign the newly incremented temp back to the member field, and then display the value.

21-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Synchronization
synchValue = temp; Console.WriteLine("{0} Count up: {1}", Thread.CurrentThread.Name,synchValue);

7. These are the only changes. Compile and run the program. The results are shown in Figure 11. You can see that rather than counting up cleanly, the various threads are duplicating the values.

Figure 11. Unsynchronized threads.

See Synchronization .sln

What is happening is that thread1 gets the value 1 and then goes to sleep. Thread2 gets the value, then thread1 increments the value (from 1 to 2) and thread2 increments it (also from 1 to 2). They then display the new values: 2 and 2. As thread3 and thread4 come online, they do the same thing. You get four threads all incrementing the original value in each cycle. This is not good. To fix this problem, youll use a lock. 8. Modify the CountUp method. The keyword lock takes a reference to an object to lock. In this case, youll lock the current object (this) while you make the changes. Once you have completed your work on the resource, youll release the lock. The implementation is that you create a scope for the lock by using braces. When your code exits the block described by the braces, the lock is released:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-25

Threads
public void CountUp() { try { for (int i = 0; i < 20; i++) { // *** new *** lock(this)

{ int temp = synchValue; temp++; Thread.Sleep(2); synchValue = temp; } Console.WriteLine("{0} Count up: {1}", Thread.CurrentThread.Name,synchValue); } }

9. Compile and run the application, as shown in Figure 12.

21-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Synchronization

Figure 12. Using Lock(this).

Now the values are incremented properly, with no overlap. That said, the application begins to stutter a bit. This is no surprise, since some of the threads must wait to get a lock on the object. The resource (in this case synchValue) is locked from the opening brace until the closing brace. Notice that within the block you call Sleep, the effect of which is to release control of the processor (just as if the thread were interrupted). When the next thread tries to lock the object it is blocked from doing so; your first object has the lock. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-27

Threads
You can make this explicit with a few WriteLine comments. 10. Before you lock the object, write to the console that you are about to do so.
Console.WriteLine("{0} trying to lock the object...", Thread.CurrentThread.Name);

11. Once you get the lock, indicate that you have it by displaying a message to the console.
lock(this) { Console.WriteLine("{0} locked the object...", Thread.CurrentThread.Name);

12. When you are done with the lock, indicate that youre done with the lock by displaying a message to the console.
synchValue = temp; Console.WriteLine("{0} unlocking the object...", Thread.CurrentThread.Name); }

13. Modify the Run method to run only a single thread and comment out the calls to Abort.

21-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Synchronization
public void Run() { Thread[] threads = { new Thread( new ThreadStart(CountUp) ), new Thread( new ThreadStart(CountUp) ), // // }; new Thread( new ThreadStart(CountUp) ), new Thread( new ThreadStart(CountUp) ),

int counter = 1; foreach (Thread thread in threads) { thread.Start(); thread.Name="Thread Number " + counter.ToString(); Console.WriteLine( "Started thread {0}", thread.Name); counter++; Thread.Sleep(2); }

// //

threads[1].Abort(); threads[2].Abort(); foreach(Thread thread in threads) thread.Join();

Console.WriteLine("All threads completed."); }

14. Compile and run the program. The output is shown in Figure 13.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-29

Threads

Figure 13. Waiting for the lock.

Examine the seventh line carefully (highlighted in Figure 13, where the arrow is drawn). Thread Number 1 is trying to lock the object. It locks the object and then Thread Number 2 tries to lock the object, but it must wait for the object to be unlocked by Thread Number 1. Thread Number 1 then unlocks the object and Thread Number 2 can lock it. This is synchronization in a nutshell. Two lines later you see Thread Number 1 waiting to lock the object until Thread Number 2 unlocks it. They go back and forth, waiting their turn until the object is unlocked.

Interlocked Increment
Because incrementing a resource under lock is such a common requirement, the .NET framework provides an Interlocked class that offers an Increment static method. You pass in a reference to an integer (note the ref keyword) and the value is incremented under lock. This greatly simplifies your code.

21-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Synchronization

Try It Out!
See Synchronization2. sln To see how the Increment method works, youll make a small revision to the previous example.

1. Reopen the previous example. 2. Update the CountUp method, delete the lock(this) keyword and block the current threads execution. 3. Call the Increment method of the Interlocked class, passing in a reference to the resource.
try { for (int i = 0; i < 20; i++) { Interlocked.Increment(ref synchValue); int temp = synchValue; temp++; Thread.Sleep(2); synchValue = temp; Console.WriteLine("{0} Count up: {1}", Thread.CurrentThread.Name,synchValue); } }

4. Compile and run the program, as shown in Figure 14. The results are the same as in the previous example using lock(this), but the code is far simpler and easier to maintain.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-31

Threads

Figure 14. Using Interlocked.Increment.

Race Conditions
You do need to be very careful when using thread synchronization. A typical problem is a race condition. A race condition exists when the success of one thread depends on the order of execution of another thread. For example: 21-32 Thread A opens a file Thread B writes to the file C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Synchronization
This creates a race condition. Thread B might try to write to the file before Thread A opens it. This is particularly difficult to debug, because most of the time Thread A opens the file in time; but now and then it might be delayed for one reason or another, and then Thread B will fail.

Deadly Embrace
One particularly nasty version of a race condition is when two threads depend on each other, and they get locked waiting for one another. For example: Thread A locks the Employee object and waits for a lock on a database row. Thread B locks the database row (unfortunately the one needed by Thread A) and waits to get a lock on the Employee object.

Thread A cant get its lock on the database row until Thread B lets the lock go. Thread B cant let go of the lock on the Database row until it gets a lock on the Employee, but that Employee is locked by thread A. Thread A cant let go of its Employee object until it completes its Database update. This is known as a Deadly Embrace or as Deadlock. Typically, this hangs your program until it either times out (if you were smart enough to put in a time-out mechanism) or you reboot the computer. When you are locking resources you want to be very careful to avoid the dreaded Deadly Embrace.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-33

Threads

Summary
Threads are lightweight processes. Threads allow the appearance of simultaneous processing, but can actually slow your program if not used carefully. Threads are helpful when your program would otherwise wait for a slow operation such as reading from a database or file. When you create a thread, pass in a delegate to the method you want to start in the thread. Call Sleep on a thread to pause the thread or to release control of the processor. Join a thread to block the current thread until the joined thread completes. Request that a thread kill itself by calling Abort. Synchronize access to a resource by calling lock and passing in a reference to the resource. The Interlocked class offers a static method Interlocked that takes a reference to an integer and increments that integer in a thread safe manner.

21-34

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Synchronization

Questions
1. How can multithreading reduce the performance of your application? 2. How can multithreading enhance the performance of your application? 3. How do you create a new thread? 4. How do you block the current thread until a given thread completes? 5. How does a thread respond to a call to Abort? 6. How do you release the lock on an object?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-35

Threads

Answers
1. How can multithreading reduce the performance of your application?
If all the threads are process intensive, the overhead in switching among the threads can actually reduce performance.

2. How can multithreading enhance the performance of your application?


If one or more of the threads must wait for a slow operation such as reading from a database or from a file, then multiprocessing allows the chip to remain busy while waiting for the other operation to complete.

3. How do you create a new thread?


You launch a new thread by instantiating a Thread object and passing in a delegate that wraps the method you want the thread to run.

4. How do you block the current thread until a given thread completes?
To block the current thread until ThreadA completes, call Join() on ThreadA.

5. How does a thread respond to a call to Abort?


The thread catches the ThreadAbortException object. This gives the thread an opportunity to clean up before exiting.

6. How do you release the lock on an object?


When you create the lock you create a block using braces. When the block exits the lock is released.

21-36

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Synchronization

Lab 21: Threads

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-37

Lab 21: Threads

Lab 21 Overview
In this lab youll learn to work with multithreaded applications and to implement synchronization mechanisms. To complete this lab, youll need to work through two exercises: Creating Multiple Threads Working with Synchronization

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

21-38

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Multiple Threads

Creating Multiple Threads


Objective
In this exercise, youll create a multithreaded application and youll abort one thread before it completes.

Things to Consider
Threads are created with the ThreadStart delegate. You can run more than one thread at a time. You can give a thread a name by assigning to the Name property of the thread.

Step-by-Step Instructions
1. Open ThreadingStarter.sln in the ThreadingStarter folder. 2. Add a using statement for the Threading namespace.
using System.Threading;

3. Implement the Run method.


public void Run() {

4. Create an array of threads. Initialize with two Threads, that will both call the CountUp method that you will create in Step 14.
Thread[] threads = { new Thread(new ThreadStart(CountUp)), new Thread(new ThreadStart(CountUp)), };

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-39

Lab 21: Threads


5. Create a local counter for naming the threads.
int counter = 1;

6. Start the thread.


thread.Start();

7. Set the threads name using the counter.


thread.Name = "Thread Number " + counter.ToString();

8. Display to the console that youve started the thread.


Console.WriteLine("Started thread {0}", thread.Name);

9. Increment the counter.


counter++;

10. Call the static Sleep method on the Thread class to cause the current thread to sleep for 10 milliseconds.
Thread.Sleep(10);

11. Ask the first thread to abort.


threads[0].Abort();

12. Iterate over the threads and join each.


foreach(Thread thread in threads) thread.Join();

13. Display a message that the threads are started. 21-40 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating Multiple Threads


Console.WriteLine("All threads Started");

14. Implement the CountUp method you referred to in Step 4.


public void CountUp() {

15. Use a constant for the value to count to. A reasonable number is 200, but you can use any number you like.
const int MagicNumber = 200;

16. Create a try block so that you can catch an abort exception.
try {

17. Create a for loop to do the work. Iterate once from 0 up to the constant value you declared in Step 15.
for (int i = 1; i < MagicNumber; i++) { Console.WriteLine( "{0}, i: {1}", Thread.CurrentThread.Name, i); }

18. Create a catch block to catch a ThreadAbortException. In that block, write a message to the console that you are aborting.
catch(ThreadAbortException) { Console.WriteLine( "Thread {0} caught ThreadAbortException. Cleaning up.", Thread.CurrentThread.Name); } // end catch

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-41

Lab 21: Threads


19. Implement a finally block. In that block, write a message to the Console indicating that you are exiting.
finally { Console.WriteLine( "{0} Exiting...", Thread.CurrentThread.Name); } // End finally

20. Run the application. Your results should look like Figure 15.

Figure 15. Running the threading application.

21-42

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Working with Synchronization

Working with Synchronization


Objective
In this exercise, youll use Interlocked Increment to synchronize two threads that increment a shared class member.

Things to Consider
The threads are independently accessing the shared member, so you need to provide some kind of locking. Interlocked increment does all the locking for you. You do not need to manage the locking yourself.

Step-by-Step Instructions
1. Open SynchronizationStarter.sln in the SynchronizationStarter folder. 2. Add the Threading namespace.
using System.Threading;

3. Give the class a member variable sharedObject of type int, and initialize it to the value 0 (zero).
private int sharedObject = 0;

4. Implement the CountUp method.


public void CountUp() {

5. Create a constant for the for loop.


const int MagicNumber = 20;

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-43

Lab 21: Threads


6. Create the for loop, counting from 0 up to the MagicNumber-1.
for (int i = 0; i < MagicNumber; i++) {

7. Increment the sharedObject using the static Increment method of the Interlocked class. Remember that you must pass the object in by reference.
Interlocked.Increment(ref sharedObject);

8. Display the value.


Console.WriteLine("{0} Count up: {1}", Thread.CurrentThread.Name,sharedObject);

9. Build and run the application. Your results should look like Figure 16.

21-44

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Working with Synchronization

Figure 16. Running the synchronized application.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

21-45

Lab 21: Threads

21-46

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Streams

Streams
Objectives
Use the DirectoryInfo object to explore directory and file information. Use the Stream class to read and write files. Use Buffered streams to optimize file reading and writing. Use Asynchronous I/O to enhance performance.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

22-1

Streams

Stream Fundamentals
When you move data to and from a file, or to and from the Internet, your data must be streamed. When data is streamed, it is serialized; that is one packet is sent after another much like bubbles on a stream. In .NET files and directories, and streams are all abstracted as classes. You create a stream, associate the stream with a file, and then give your object to the stream object to have it sent to the file. Streams may be buffered or they may be unbuffered. Buffered streams are mediated by the Framework to optimize transmission performance.

Directory Information
Streams are implemented by classes within the IO namespace. This namespace also provides support for Directory and DirectoryInfo classes that you can use to explore and manipulate directories on your drive.

Try It Out!
See Streams1.sln To see how DirectoryInfo objects work, youll create a simple test application to print information about the directories in the WinNT directory. 1. Create a new console application and name it Streams1. 2. Add a using statement for the IO namespace.
using System.IO;

3. Create a Run method. Within the Run method create a string variable to hold the directory name.

22-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Stream Fundamentals
class Tester { static void Main(string[] args) { Tester t = new Tester(); t.Run(); } public void Run() { // initial directory string dir = @"C:\WinNT";

Notice that you can use a verbatim string (beginning with the at symbol @), which allows you to avoid escaping the backslash. That is, with the verbatim string you can write C:\WinNT rather than C:\\WinNT. 4. Instantiate a DirectoryInfo object, passing in the name of the directory you are interested in.
DirectoryInfo dirInfo = new DirectoryInfo(dir);

5. You want to explore your directory and all its sub-directories, so you need a method that can be called recursively. Create a new method, ExploreDirectory. This new method will take two parameters: a DirectoryInfo for the directory you want to explore and a string for the path. Since you are invoking it for the first time, the path string will be blank. ExploreDirectory will return the total number of directories found, and you can then print that value.
// completed. print the statistics Console.WriteLine("\n\n{0} directories found.\n", dirsFound);

} // end Run

6. Create the dirsFound member variable to keep track of the number of directories found. Initialize it to 0:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

22-3

Streams
class Tester { long dirsFound = 0;

7. Write the ExploreDirectory method.


private int ExploreDirectory( DirectoryInfo dirInfo, string path) {

8. Display the full path to the current directory by displaying the path, followed by a slash, followed by the current directorys name. You get the path from the parameter and the current directorys name from the DirectoryInfo object passed in as a parameter. You can also display the last time the directory was accessed, by using the LastAccessTime property of the DirectoryInfo object:
Console.WriteLine("{0}\\{1} [{2}]\n", path, dirInfo.Name, dirInfo.LastAccessTime);

9. Update the path with the name of the current directory. Youll use this new path when you recurse into the subdirectories.
path+=("\\" + dirInfo.Name);

10. Get all the subdirectories for the current directory, by calling GetDirectories. This method returns an array of DirectoryInfo objects:
DirectoryInfo[] directories = dirInfo.GetDirectories();

11. Iterate over all the subdirectories and recurse into the current directory, passing in the subdirectory and the current path.
foreach (DirectoryInfo newDir in directories) { ExploreDirectory(newDir,path); }

22-4

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Stream Fundamentals
12. Return the counter that now has the running total of directories found.
dirsFound++; } // end ExploreDirectory

13. Compile and run this application. You should see all the subdirectories under WinNT scrolling by. To simplify the output, weve created a test directory and subdirectories, as shown in Figure 1.

Figure 1. The test subdirectory tree.

The output of the program applied against these subdirectories is shown in Figure 2.

Figure 2. The test subdirectories.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

22-5

Streams

Streams and Files


The Stream class can be used to read a file from the disk, or it can be used to write a file to disk. When you open a stream for reading, you can call the Read method, and pass in a buffer of bytes that will be read from the file and that you may then either write to another file or display to the console.

Try It Out!
See Streams2.sln To see how streams work, youll read the text from a file and write it out to a new file. 1. Create a new console application named Streams2. 2. Within the Run method, create an input stream and initialize that stream by calling the OpenRead static method on the File class. That method takes the name and path of a file:
class Tester { static void Main(string[] args) { Tester t = new Tester(); t.Run(); }

public void Run() { Stream inputStream = File.OpenRead( @"C:\test\story\Odyssey.txt");

3. Notice that the Stream object is named inputStream, but it is just a vanilla stream object. Similarly, you can create an output stream by calling OpenWrite on the File class:
Stream outputStream = File.OpenWrite( @"C:\test\story\Odyssey.bak");

4. Define a buffer of bytes and size it using a constant. 22-6 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Stream Fundamentals
const int SizeBuff = 255; byte[] buffer = new Byte[SizeBuff];

5. Call the Read method on the first stream; pass in the buffer and the offset at which you want to begin reading (0). Also pass in the size of the buffer youve provided. You will get back an integer value indicating the number of bytes successfully read from the file.
int bytesRead; bytesRead = inputStream.Read(buffer,0,SizeBuff);

6. Create a loop to continue reading as long as you get any bytes back from the call to the Read method. Each time you fill the buffer, write it to the output buffer, using the Write method. The Write method also takes three parameters; the buffer, the offset, and the number of bytes you are writing.
while( bytesRead { outputStream.Write(buffer,0,bytesRead); > 0 )

7. Write the number of bytes youve read to the console and then read another buffer-full.
Console.WriteLine("Bytes read: {0}", bytesRead); bytesRead = inputStream.Read(buffer,0,SizeBuff); } // end while

8. Once the while loop is completed, youve read the entire file. You can now close both the input and the output stream.
inputStream.Close(); outputStream.Close(); } // end Run

9. To test this, weve created a file in the text\story subdirectory named Odyssey.txt. This contains the beginning of Homers Odyssey, an excerpt from the public domain version of the file available through the Guttenberg Project. An excerpt is shown in Figure 3.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

22-7

Streams

Figure 3. The Odyssey file.

10. Compile and run the program. You will see that buffers full of 255 bytes of text will be read until the final buffer-full is read, as shown in Figure 4. At that point the file has been copied and the streams are closed.

22-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Stream Fundamentals

Figure 4. Reading the buffers full.

11. Examine the directory and youll find a backup version has been created, as shown in Figure 5.

Figure 5. The backup file in the directory

12. Examine the files side-by-side, youll find that they are identical as shown in Figure 6.

Figure 6. The copy is identical.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

22-9

Streams

Buffered Streams
In the previous examples, each time you read from the file you created your own buffer to read to. It may turn out, however, that the reading can be done much more efficiently with a different sized buffer. By using buffered streams, you allow the operating system to read from the disk (and write to the disk) using the optimal sized buffer. You then read from that buffered stream to and from your own buffer. With a large buffer, this can significantly enhance the efficiency of your program. A BufferedStream object is created around an existing stream object. The BufferedStream just provides the additional efficiency.

Try It Out!
See Streams3. sln To see how to use buffered streams, youll create a simple application to read a file from disk and to write a copy. 1. Create a new application named Streams3. 2. Create a Run method, and create two stream objects, one to read a file and the other to write a file.
class Tester { static void Main(string[] args) { Tester t = new Tester(); t.Run(); } public void Run() { // open a stream for reading Stream inputStream = File.OpenRead( @"C:\test\story\Odyssey.txt");

// open a stream for writing Stream outputStream = File.OpenWrite( @"C:\test\story\Odyssey.bak");

22-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Stream Fundamentals
3. Create a BufferedStream object. Name it buffIn and pass the input stream to the constructor. Do the same for a buffered output stream:
BufferedStream buffIn = new BufferedStream(inputStream); BufferedStream buffOut = new BufferedStream(outputStream);

4. Create a local buffer of bytes.


const int SizeBuff = 255; byte[] buffer = new Byte[SizeBuff]; int bytesRead;

5. Fill the buffer from the BufferedStream, by calling the Read method. Pass in the buffer, the offset (0), and the size of the buffer. The Read method will return the number of bytes read. As long as you are getting bytes, keep reading and write the bytes out to the output buffer:
while ( (bytesRead = buffIn.Read(buffer,0,SizeBuff)) > 0 ) { buffOut.Write(buffer,0,bytesRead); }

6. Remember to flush the output buffer when you are done and to write any remaining bytes to the file.
buffOut.Flush();

7. Close the BufferedStream objects. The original stream objects will be closed for you by the BufferedStream objects.
buffIn.Close(); buffOut.Close();

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

22-11

Streams
Once again youve read the Odyssey.txt file, and created a backup. The results are identical to the previous example, but with a large file, this version of the program should run noticeably faster.

Text Files
The stream class provides special support for text files. A text file has no binary information in it (no database records, no images, etc.), only text. If you have such a file, it is easier, and more efficient to use the text file Stream objects StreamReader and StreamWriter. To create a StreamReader you start by creating a FileInfo object. The constructor for a FileInfo object takes the name and path of the file you want to open. You then call OpenText on the FileInfo object, which returns a StreamReader object. You can instantiate a StreamWriter by passing a path and file name to the constructor, along with a second parameter indicating whether or not to append to a file if it already exists.

Try It Out!
See Streams4.sln To see how StreamReader and StreamWriter work, youll create a new console application to read and write the Odyssey text file. 1. Create a new console application named Streams4. 2. Within the Run method, create a FileInfo object for the Odyssey.txt file.
class Tester { static void Main(string[] args) { Tester t = new Tester(); t.Run(); }

public void Run() { FileInfo theSourceFile = new FileInfo( @"C:\test\story\Odyssey.txt");

22-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Stream Fundamentals
3. Create a StreamReader object and initialize it by calling OpenText on the FileInfo object.
StreamReader reader = theSourceFile.OpenText();

4. Create a new StreamWriter object.


StreamWriter writer = new StreamWriter( @"C:\test\story\Odyssey.bak",false);

5. Create a local string variable named text to hold the text read from the StreamReader object.
string text;

6. Create a do while loop to read through the file. Each time through the loop youll assign to the string variable the result of calling ReadLine on the StreamReader. This will return one line of text that you can then write to the file (using WriteLine) or to the console. Continue to do this as long as the text string is not null.
do { text = reader.ReadLine(); writer.WriteLine(text); Console.WriteLine(text); } while (text != null);

7. Once you have read through the entire file, the text string will be null and you can close the StreamReader and StreamWriter objects.
reader.Close(); writer.Close();

The effect on the disk is the same as in the previous examples (a copy is made), but this time the output to the console is the text of the file as well, as shown in Figure 7.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

22-13

Streams

Figure 7. Using the TextReader object.

22-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Asynchronous I/O

Asynchronous I/O
The previous examples read from the file synchronously; that is, the program waited while the file was read. Reading from a file can be a slow process, however, and it is tempting to use threading to create a multitasking application that can be busy with other work while the reading is completed. Threading can be complex, however, and the designers of .NET realized that asynchronous reading and writing would be so common that there would be great benefit in building in support for asynchronous I/O. With asynchronous I/O you instruct the stream to read from the file and then you continue with your work. When the buffer is read, a method you designate is called back indicating that the data is ready to be handled. You can then stop what you are doing just long enough to manipulate the data, and then continue with your other work while the stream fetches the next buffer-full. The asynchronous I/O classes support the creation of highly-efficient programs without the difficulties of managing threads directly. Heres how it works. As illustrated in Figure 8, you create an Asynchronous Input Stream and call BeginRead, passing in a buffer to fill and the name of the method you want called when the read is completed (e.g., OnCompletedRead). The stream begins reading, while you go on with other work.

Figure 8. Asynchronous I/O.

When the stream has read the data, it calls your callback method (OnCompletedRead) passing in a token. In that method you call the Streams EndRead method passing back the token and you get back the data. You can then kick off another read and resume work.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

22-15

Streams

Try It Out!
See AsynchStreams.sln To see how AsynchStreams allows you painless multitasking, youll create a console application and read from a file asynchronously. 1. Create a new console application named AsynchStreams. 2. Create a class, AsynchTest and give it a private Stream object named inStream.
public class AsynchTest { private Stream inStream;

3. To call a callback method you will need a delegate of type AsyncCallback. This delegate type is defined by the .NET Framework. Youll call your instance of this delegate type callBackMethod.
private AsyncCallback callBackMethod;

4. Create a private buffer of bytes and establish a constant value for the buffer size.
private byte[] buffer; const int BufferSize = 256;

5. In the constructor you will open the input stream file by the static OpenRead method of the file class. You will also initialize your buffer and your delegate.

22-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Asynchronous I/O
// constructor AsynchTest() { // open the input stream inStream = File.OpenRead( @"C:\test\story\Odyssey.txt");

// allocate a buffer buffer = new byte[BufferSize];

// assign the call back callBackMethod = new AsyncCallback(this.OnCompletedRead); }

6. In the Run method you call BeginRead on the input stream object, passing in the buffer you want to fill, the offset, the size of the buffer, and the delegate for your callback method. You can also pass in a state object to hold the current state of your object, but youll pass in null because you do not need to track the state of your object.
void Run() { inStream.BeginRead( buffer, 0, buffer.Length, callBackMethod, null); // holds the results // offset // (BufferSize) // call back delegate // local state object

7. The BeginRead method will operate asynchronously; you can go on to do other work. Youll simulate other work by counting to 500,000 displaying a value every 1000 increments.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

22-17

Streams
// do some work while data is read for (long i = 0; i < 500000; i++) { if (i%1000 == 0) { Console.WriteLine("i: {0}", i); } } } // end if // end for // end run

8. Implement the method to be called back. The delegate requires that your method take a single parameter: a reference to an object implementing IAsyncResult.
void OnCompletedRead(IAsyncResult asyncResult) {

9. When this method is called, you will call the EndRead method on your input stream, passing in the IAsyncResult object you received as a parameter: you get back a value indicating how many bytes were read.
int bytesRead = inStream.EndRead(asyncResult);

10. The actual bytes were put into the buffer you passed in to the BeginRead method. If you got back a non-zero amount from EndRead you are ready to create a string from the buffer full of data and write that string to the console. You will then fire off BeginRead again passing in the same parameters as last time.

22-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Asynchronous I/O
if (bytesRead > 0) { String s = Encoding.ASCII.GetString( buffer, 0, bytesRead);

Console.WriteLine(s);

inStream.BeginRead( buffer, 0, buffer.Length, callBackMethod, null); }

This requests that the asynchronous I/O stream read another buffer-full and notify you when it is ready. 11. Compile and run the program. As shown in Figure 9, the program begins counting while the asynchronous I/O stream fetches the first buffer-full. When the buffer-full is ready you can display it to the console, and then go back to work as shown in Figure 9.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

22-19

Streams

Figure 9. Reading the data asynchronously.

This continues, oscillating back and forth between your other work and displaying the read data until all the data is read as shown in Figure 10.

22-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Asynchronous I/O

Figure 10. Asynchronous I/O and other work.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

22-21

Streams

Summary
When you move data to or from a file or across the network you must stream the data. When data is streamed it is serialized: put into a series of byte packages, one after the other. The .NET Framework provides extensive support for streaming. The DirectoryInfo object can be used to find information about directories and their subdirectories. The File class has a number of methods, many static, for manipulating files. Buffered Streams allow the operating system to decide the optimal buffer size for streaming. You wrap a BufferedStream object around a normal Stream object. The StreamReader and StreamWriter classes provide optimized support for working with Text streams. Rather than writing your own multitasking thread support for asynchronous I/O, you may use the overlapped I/O classes provided by the Framework Class Library. Overlapped I/O works by the use of callback methods, implemented with delegates.

22-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Asynchronous I/O

Questions
1. How do you find the subdirectories within a directory? 2. What are the parameters passed to the Read method of Stream?

3. What is the advantage of using a Buffered Stream? 4. What is the advantage of using asynchronous I/O? 5. How is your callback method designated, that is, how does the stream know which method to call?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

22-23

Streams

Answers
1. How do you find the subdirectories within a directory?
Call GetDirectories on a DirectoryInfo object. This method returns an array of DirectoryInfo objects representing the subdirectories.

2. What are the parameters passed to the Read method of Stream?


The Read method takes three parameters. The first is an array of bytes to be filled. The second is an offset into that array, generally set to 0. The third parameter is the size of the buffer youve passed.

3. What is the advantage of using a Buffered Stream?


The Buffered Stream allows the operating system to decide on the optimal size for the buffer. You continue to pass in a buffer of whatever size you choose, but the data for your buffer is retrieved from the BufferedStreams buffer, rather than from the disk. This can greatly reduce the number of disk reads, and disk reads are very slow.

4. What is the advantage of using asynchronous I/O?


Asynchronous I/O allows your program to continue processing, while the system is busy retrieving data from the disk. This is the heart of multitasking. The Framework provides extensive support for overlapped I/O, allowing you to create multithreaded programs without having to manage the threads yourself.

5. How is your callback method designated, that is, how does the stream know which method to call?
You designate the callback method with a delegate. The delegates signature is specified by the Framework. You initialize the delegate in your constuctor, passing in the name of the method to call.

22-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Asynchronous I/O

Lab 22: Streams

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

22-25

Lab 22: Streams

Lab 22 Overview
In this lab youll learn to work with text streams and with asynchronous streams. To complete this lab, youll need to work through two exercises: Reading Text from a File Using Overlapped I/O to Read a File

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

22-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Reading Text from a File

Reading Text from a File


Objective
In this exercise, youll use a text stream to read text from a file.

Things to Consider
You obtain a StreamReader by calling OpenText on a FileInfo object. You can obtain a StreamWriter class by passing in to the StreamWriter constructor the name of the file you want to create. StreamReader and StreamWriter are optimized for text and support the ReadLine and WriteLine methods, respectively.

Step-by-Step Instructions
1. Create a new Windows console application named Streams. 2. Add a using statement for System.IO.
using System.IO;

3. Change the namespace to StreamsStarter.


namespace StreamsStarter {

4. Create a class Tester and establish the Main method to call the Run method.
class Tester {

5. Implement the Main method.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

22-27

Lab 22: Streams


static void Main(string[] args) { Tester t = new Tester(); t.Run(); }

6. Implement the Run method.


public void Run() {

7. Open a FileInfo instance on the supplied file Odyssey.txt. Be sure to put the full path to the Odyssey.txt file.
FileInfo theSourceFile = new FileInfo( @"C:\test\story\Odyssey.txt");

8. Create a StreamReader instance for that file.


StreamReader reader = theSourceFile.OpenText();

9. Create a StreamWriter instance for a new file. Put the new file in the same directory but call it Odyssey.bak.
StreamWriter writer = new StreamWriter( @"C:\test\story\Odyssey.bak",false);

10. Create a text variable to hold each line of text as it is retrieved from the file.
string text;

11. Walk through the file and read every line, writing both to the console and to the file.

22-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Reading Text from a File


Do { text = reader.ReadLine(); writer.WriteLine(text); Console.WriteLine(text); } while (text != null);

12. Close the reader and writer and exit.


reader.Close(); writer.Close();

13. End the Run method, the Tester class, and the namespace.
} } } // // // end Run method end class Tester end namespace

14. Build and run the Streams application. Your results should be similar to Figure 11.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

22-29

Lab 22: Streams

Figure 11. Running the Streams application.

22-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Using Overlapped I/O to Read a File

Using Overlapped I/O to Read a File


Objective
In this exercise, youll use overlapped I/O to read a file from disk.

Things to Consider
Overlapped IO provides multithread support without you having to manage the threads. You can continue other work, while the stream is reading from the file. Your callback method will be passed a token of type IAsyncResult use that to call EndRead to get the data.

Step-by-Step Instructions
1. Open OverlappedIOStarter.sln in the OverlappedIOStarter folder. 2. Add using statements for System.IO, System.Threading, and System.Text.
using System.IO; using System.Threading; using System.Text;

3. Create a private member of type Stream named inputStream.


private Stream inputStream;

4. Declare a private delegate of type AsyncCallBack named callBackMethod.


private AsyncCallback callBackMethod;

5. Declare a private array of bytes named buffer to hold the read data.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

22-31

Lab 22: Streams


private byte[] buffer;

6. Declare a constant to hold the size of the buffer.


const int BufferSize = 256;

7. Implement the constructor.


AsynchTest() {

8. Open the input stream by calling the static OpenRead method on the File class, passing in the full path of the supplied file Odyssey.txt.
inputStream = File.OpenRead( @"C:\test\story\Odyssey.txt");

9. Allocate a buffer to hold the text.


buffer = new byte[BufferSize];

10. Assign the callback method to the OnStarterRead method of this class.
callBackMethod = new AsyncCallback(this.OnStarterRead);

11. Implement the Run method.


void Run() {

12. Call BeginRead on the input stream object. Pass in the buffer. Use 0 for the offset. Pass in the length of the buffer and the delegate encapsulating the callback method. For the final parameter, the local state object, pass in null.

22-32

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Using Overlapped I/O to Read a File


inputStream.BeginRead( buffer, 0, buffer.Length, callBackMethod, null); // buffer for results // offset // (BufferSize) // call back delegate // local state object

13. Implement the callback method, OnStarterRead.


void OnStarterRead(IAsyncResult asyncResult) {

14. Call EndRead and get back an integer value; store bytesRead to a local integer variable.
int bytesRead = inputStream.EndRead(asyncResult);

15. Build and run the application. You should see the counting interleaved with the text read from the file, as shown in Figure 12.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

22-33

Lab 22: Streams

Figure 12. Overlapped I/O implemented.

22-34

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Network Streams

Network Streams
Objectives
Read files across the Web using standard URLs. Read files across a local area network. Implement asynchronous I/O across a network. Control the serialization of your object. Implement IDeserializationCallBack to control which parts of your object will be serialized.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-1

Network Streams

Long Distance I/O


In the previous lesson you learned about using streams to read data from a disk file and write data out to a new file. Because .NET abstracts this process out to a stream, it is easy to reapply the same approach to reading and writing files across the Web or across a local area network. The idiom is the same: create a stream and read from or write to that stream. The only tricky part is connecting that stream to a file on another machine or to a URL.

Web I/O
One of the truly powerful and elegant aspects of I/O in .NET is that you can read from the network or the Web just as easily as you can read from a file. To read from the Web you will use a WebRequest object to request a URI. A URI is a Uniform Resource Identifier. The most common type of URI is a URL Uniform Resource Locator): the familiar Web address (http://www.LibertyAssociates.com). The WebRequest object creates a WebResponse object that encapsulates the URI you provide. You call GetResponseStream on the WebResponse object and get back a stream that you can write to or read from. The steps to creating a stream for a Web site are as follows: 1. Call WebRequest.Create(). This static method returns a WebRequest object. Youll cast this to the derived class HttpWebRequest. If you pass the Create method a valid URL, you get back an HttpWebRequest, which is a class derived from WebRequest. 2. Call webRequest.GetResponse(). This instance method returns an HttpWebResponse object. 3. Create a StreamReader object. In the constructor, pass in the value returned by the instance method webResponse.GetResponseStream(). Note that GetResponseStream returns a Stream object. The constructor for StreamReader takes a Stream object and an Encoding object that represents, in this case, ASCII encoding. 4. Call streamReader.ReadToEnd(). This instance method returns a string with the entire stream representing the Web page.

23-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Long Distance I/O

Try It Out!
See WebStream.sln To see how to write a Web page to a stream, youll create a simple console application to read a Web page. 1. Create a simple console application named WebStream. 2. In the Run method, create the HttpWebRequest object by calling the static method Create on the WebRequest object. Pass in the URL for the Web page you want to retrieve.
HttpWebRequest webRequest = (HttpWebRequest) WebRequest.Create ("http://www.libertyassociates.com");

3. Call GetResponse on the WebRequest object and get back an HttpWebResponse object.
HttpWebResponse webResponse = (HttpWebResponse) webRequest.GetResponse();

4. Generate the StreamReader object.


StreamReader streamReader = new StreamReader( webResponse.GetResponseStream(), Encoding.ASCII);

5. Call ReadToEnd() on the StreamReader. If no exception is thrown, this will fill the string with the contents of the Web page. Display this page to the console:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-3

Network Streams
Try { string outputString; outputString = streamReader.ReadToEnd(); Console.WriteLine(outputString); } catch { Console.WriteLine("Exception reading from web page"); }

6. Close the StreamReader.


streamReader.Close();

7. Compile and run the program. The Web page is requested and displayed, as shown in Figure 1.

Figure 1. Reading a Web page.

23-4

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Long Distance I/O

Networking I/O
Just as you read from the Web, you can read and write across a local area network or over the Internet since it all just streams. Network I/O streams depend on sockets. A socket is an object that represents an end point. Sockets work with various protocols (such as UDP and TCP/IP) but this course focuses on the most popular protocol: TCP/IP.

Ports
It is possible that more than one application on a single computer will talk to various clients at the same time. Each application must, therefore, have its own unique ID. An application ID is called a port. If your I/P address is analogous to a phone number, then a port is analogous to an extension. When a client connects to a server over TCP/IP, it connects to a specific I/P address: Yahoo: 216.114.108.245 CNN: 207.24.71.20

The client must also connect to a specific port. All Web browsers connect to port 80 by default, and Web browsers generally make their services available on port 80. By international convention ports are divided into the following ranges: 0-1023 Well-known ports e.g., 80=web browser 1024-49151 Registered Ports 49152-65535 Dynamic or private ports

For a list of registered and well-known ports check: http://www.isi.edu/in-notes/iana/assignments/port-numbers To create a client/server application you will: 1. Create a socket. 2. Call Start() on the socket. This tells the socket to begin accepting connections. 3. Call Accept() on the socket. This tells the socket to begin responding to client requests.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-5

Network Streams
In the .NET Framework, sockets are encapsulated by the Socket class. The TcpListener class builds on the Socket class to provide high-level TCP/IP services. The steps are: 1. Create a TcpListener with a port ID. 2. Tell it to start. 3. Call Accept(). This method returns a socket for the client.

Try It Out!
See NetworkIO Server1.sln and NetworkIO Client1.sln To see how to work with sockets, youll create a simple server application and a simple client application. This application will allow you to stream the contents of the Oddysey.txt file across the network to a client application that you will write as well. 1. Create a console application and name it NetworkIOServer. 2. Add using statements for the namespaces youll require.
using System; using System.Net.Sockets; using System.IO;

3. In the Run method, create an instance of TcpListener. Pass the port you want to listen to on to the constructor (in this case port 65000). Call Start on that object.

23-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Long Distance I/O


public class NetworkIOServer {

public static void Main() { NetworkIOServer app = new NetworkIOServer(); app.Run(); }

private void Run() { // create a new TcpListener and start it up // listening on port 65000 TcpListener tcpListener = new TcpListener(65000); tcpListener.Start();

4. In this simplified program youll keep listening until you accept a connection to a client, and then youll keep listening while there is a file to send. When the file is sent, youll break out of the listening loop and shut down. Start by creating a forever loop. You can do this with:
for(;;)

or if you prefer:
while (true)

Within the forever loop, instantiate a socket by calling AcceptSocket on the TcpListener object.
for (;;) {

Socket clientSocket = tcpListener.AcceptSocket();

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-7

Network Streams
5. Test the socket to see if it is connected. It will return false until a client connects.
if (clientSocket.Connected) {

6. When the Connected property returns true, update the console and call a helper method to send the file to the client. Update the console again, and then close the socket and break out of the loop.
if (clientSocket.Connected) { Console.WriteLine("Client connected");

// call the helper method to send the file StreamFile(clientSocket);

Console.WriteLine( "Disconnecting from client...");

// clean up and go home clientSocket.Close(); Console.WriteLine("Exiting..."); break; } } // end if clientSocket.Connected // end for(;;)

7. Create the helper method StreamFile. You can make this a private method and it will take the socket you created in Run as a parameter.
private void StreamFile( Socket clientSocket ) {

8. In the StreamFile method youll create a NetworkStream object, passing in the socket you received as a parameter.

23-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Long Distance I/O


NetworkStream networkStream = new NetworkStream(clientSocket);

9. Create an instance of StreamWriter, passing in the NetworkStream as a parameter to the constructor.


StreamWriter streamWriter = new System.IO.StreamWriter(networkStream);

10. Instantiate a StreamReader object for the file.


StreamReader streamReader = new System.IO.StreamReader( @"C:\test\story\odyssey.txt");

11. Read the file line-by-line. Write the file out to the StreamWriter and flush the StreamWriter each time you do to ensure that the entire line is sent to the client.
do { strStream = streamReader.ReadLine();

if( strStream != null ) { Console.WriteLine( "Sending {0}", strStream); streamWriter.WriteLine(strStream); streamWriter.Flush(); } } while( strStream != null );

12. Close the StreamReader, the NetworkStream, and the StreamWriter.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-9

Network Streams
// tidy up streamReader.Close(); networkStream.Close(); streamWriter.Close(); } // end StreamFile method

13. Build the server. 14. Open a new instance of Visual Studio .NET to create the client. 15. Create a new console application and name it NetworkIOClient1. 16. Add the following namespaces:
using System; using System.Net.Sockets; using System.IO;

17. In the Run method, create a TcpClient object to represent the TCP/IP connection.
public class Client { static public void Main( ) { Client c = new Client(); c.Run(); }

public void Run() { // create a TcpClient to talk to the server TcpClient serverSocket;

18. In a try block (so that you can catch exceptions thrown if the connection fails), create a new instance of the TcpClient, passing in the host name and the port.

23-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Long Distance I/O


Try { serverSocket = new TcpClient("localHost", 65000); }

19. Create a catch block to handle any exceptions thrown.


catch { Console.WriteLine( "Failed to connect to server at {0}:65000", "localhost"); return; }

20. If you get past the connection you are ready to read the file. Create a NetworkStream and a StreamReader, much as you did in the server.
NetworkStream networkStream = serverSocket.GetStream();

StreamReader streamReader = new StreamReader(networkStream);

21. In a second try block read line-by-line from the stream. As you get lines, display them to the Console.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-11

Network Streams
Try { string strOut;

// read the data from the host and display it do { strOut = streamReader.ReadLine();

if( strOut != null ) { Console.WriteLine(strOut); } } while( strOut != null ); } catch { Console.WriteLine( "Exception reading from Server"); }

22. Close the NetworkStream.


networkStream.Close(); } // end Run()

23. Compile the client and then run it. You should see an exception, as shown in Figure 2.

Figure 2. The server not found exception.

23-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Long Distance I/O


24. Stop the client and run the server. Then run the client. You should see the server connect to the client, and then the file will be streamed over the network to the client as shown in Figure 3.

Figure 3. The server and client.

You can run these programs on the same machine, or across a local area network, or across the Internet.

Asynchronous Network I/O


The previous example works great if you are only going to manage one request. Busy network servers, however, can be expected to receive multiple requests from many clients. You need a way to combine asynchronous reads and writes with network I/O. Not surprisingly, the .NET Framework provides extensive support for this. The idiom for creating an asynchronous server is to: Get a connect. Instantiate a handler for the client. Pass the socket to the client handler. Get client handler to manage the request, while the server code listens for the next client.

Client Handler
The job of the client handler is to create a copy of the socket and use that to open a NetworkStream object. The client handler then uses overlapped I/O to read and write to the NetworkStream, similar to using overlapped (asynchronous) I/O to read and write from a file.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-13

Network Streams

See Asynch NetworkIO Server.sln and NetworkIO Client2.sln

Try It Out!
To get your feet wet with asynchronous network I/O youll build a simple server and a client. The client will send a string to the server. The server will read the string using overlapped I/O and then echo it back to the client, using overlapped I/O. 1. Create a new console application and name it AsynchNetworkIOServer. While some of the code is similar to the previous example, the changes are extensive and youre better off starting clean. 2. Create a class AsynchNetworkIOServer. In the Run method create a TcpListener on port 65000 and start it up:
public class AsynchNetworkIOServer {

public static void Main() { AsynchNetworkIOServer app = new AsynchNetworkIOServer(); app.Run(); }

private void Run() { // create a new TcpListener and start it up // listening on port 65000 TcpListener tcpListener = new TcpListener(65000); tcpListener.Start();

3. In the forever loop youll create a Socket class, and check if it is connected (as you did previously). Once connected youll create an instance of a ClientHandler, passing in the socket. ClientHandler is a class youll create in Step 4. Once youve instantiated the ClientHandler, call StartRead.
for (;;) { // if a client connects, accept the connection // and return a new socket named socketForClient // while tcpListener keeps listening

23-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Long Distance I/O


Socket socketForClient = tcpListener.AcceptSocket(); if (socketForClient.Connected) { Console.WriteLine("Client connected"); ClientHandler handler = new ClientHandler(socketForClient); handler.StartRead(); } }

Compare this to the way you handled the forever loop in the previous example. In this example you do not break the loop after calling the handler. More important, you dont pass the socket to your own member method, but to a new object you instantiate of type ClientHandler. The goal in this example is for the Run method to be able to dispatch the client request quickly (by handing it to the ClientHandler object) and go back to retrieving new client requests. So, Run becomes a dispatcher, with individual instances of ClientHandler actually handling each client request. 4. Declare the ClientHandler class as a nested class within the AsynchNetworkIOServer class. Give the client handler class private members to hold the buffer, Socket, NetworkStream object, and a pair of call back delegates:
class ClientHandler { private byte[] private Socket private NetworkStream private AsyncCallback private AsyncCallback buff; socket; networkStream; cbRead; cbWrite;

5. Create a constructor that takes a socket and initializes the NetworkStream and the two delegates.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-15

Network Streams
public ClientHandler( Socket socketForClient ) { socket = socketForClient; buff = new byte[256]; networkStream = new NetworkStream(socketForClient);

cbRead = new AsyncCallback(this.OnReadComplete);

cbWrite = new AsyncCallback(this.OnWriteComplete); }

6. Implement the StartRead method called by AsynchNetworkIOServer .Run. The job of StartRead is to call the BeginRead (overlapped I/O) method on the NetworkStream, passing in the buffer, the offset, the length of the buffer, and most important: the delegate to call when a read is completed.
public void StartRead() { networkStream.BeginRead( buff, 0, buff.Length, cbRead, null ); } // buffer to fill // offset // length of the buffer // delegate to call // state object

7. Implement the method encapsulated by the cbRead delegate: OnReadComplete. This method receives an object implementing IAsyncResult. You will call EndRead on the NetworkStream, passing in this IAsyncResult object and you will get back a value indicating how many bytes have been read.

23-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Long Distance I/O


private void OnReadComplete( IAsyncResult ar ) { int bytesRead = networkStream.EndRead(ar);

8. If you have a positive number in bytesRead then you know your buffer has been filled. At this point you would do something with the data youve retrieved. In this case, youll simply display it to the console and echo it back to the client. Extract the value from the buffer and convert it to a string by calling the static GetString method on System.Text.Encoding.ASCII. Pass that string to the Console.Write method and then call BeginWrite to write the same string back to the client using overlapped I/O.
if( bytesRead > 0 ) { string s = System.Text.Encoding.ASCII.GetString( buff, 0, bytesRead); Console.Write( "Received {0} bytes from client: {1}", bytesRead, s ); networkStream.BeginWrite( buff, 0, bytesRead, cbWrite, null); }

9. If the value of bytesRead is not greater than zero then the connection has been dropped (or the client has stopped sending data). Close the NetworkStream and the socket, and set them both to null:
else { Console.WriteLine( "Read connection dropped"); networkStream.Close(); socket.Close(); networkStream = null; socket = null; } } // end else // end OnReadComplete

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-17

Network Streams
10. Write the OnWriteComplete method to implement the second delegates method. This method will be called when the write request completes. Once again you are passed an IAsyncResult and you finalize the write by calling EndWrite on the NetworkStream. Update the status to the Console and call BeginRead to start the next asynchronous read.
private void OnWriteComplete( IAsyncResult ar ) { networkStream.EndWrite(ar); Console.WriteLine( "Write complete"); networkStream.BeginRead( buff, 0, buff.Length, cbRead, null); } } } // end OnWriteComplete // end ClientHandler // end AsynchNetworkIOServer

11. Create a new application and name it NetworkIOClient2. 12. Add the necessary using statements to the top of the cs file.
using System; using System.IO; using System.Net.Sockets; using System.Threading; using System.Runtime.Serialization.Formatters.Binary;

13. Create the AsynchClient class and give it a private member of type NetworkStream. In the constructor set the server name and instantiate a TcpClient object with the server name and port 65000. Assign to the member variable the result of calling GetStream on the TcpClient.

23-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Long Distance I/O


public class AsynchClient { private NetworkStream streamToServer;

AsynchClient() { string serverName = "localhost"; Console.WriteLine("Connecting to {0}", serverName); TcpClient tcpSocket = new TcpClient(serverName, 65000); streamToServer = tcpSocket.GetStream(); }

14. Create a run method. In Run create a string named message. Write the message to the Console:
static public int Main() {

AsynchClient client = new AsynchClient(); return client.Run(); }

private int Run() { string message = "This is a string sent from the client!"; Console.WriteLine( "Sending {0} to server.", message);

15. Create a StreamWriter object and pass to its constructor the member variable streamToServer. Call WriteLine on that StreamWriter and pass in the message, flush the buffer.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-19

Network Streams
StreamWriter writer = new StreamWriter(streamToServer); writer.WriteLine(message); writer.Flush();

16. Create a StreamReader object to retrieve the response from the server (this will be the string echoed back to the client).
StreamReader reader = new StreamReader(streamToServer);

17. Retrieve the string from the server by calling ReadLine and displaying the string on the console.
string strResponse = reader.ReadLine(); Console.WriteLine("Received: {0}", strResponse);

18. Close the NetworkStream and exit the program.


streamToServer.Close(); return 0; } // end Run

19. Build and run the server, then build and run the client. As shown in Figure 4, the client comes up and reports that it is connecting to the server. The server reflects that the client has connected. The client sends This is a string sent from the client!, which is received on the server. The server then writes it back to the client, and you see it displayed on the client. The client then exits and the server reports that the Read connection was dropped.

23-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Long Distance I/O

Figure 4. The asynchronous network I/O.

Combining File and Network Asynchronous I/O


To build a useful asynchronous application, you may need to combine local asynchronous reading and writing with network overlapped I/O. In the next example youll send a file name from the client to the server, and then youll write the contents of the file from the server to the client. To do this, you will use three delegates on the server and three callback methods. OnReadComplete will be called when the system has read a buffer from the client. OnFileReadComplete will be called when the server has read a buffer full of data from the file. Finally, OnWriteComplete will be called when the server has finished writing a buffer full of data to the client. The program will work like this: 1. The server starts. 2. The client starts. 3. The server creates a TcpListener to listen to the port, and when a client connects a ClientHandler is created to handle the clients request Once the server creates the ClientHandler it calls StartRead and goes back to listen for additional clients. 4. The StartRead method of ClientHandler starts an asynchronous read from the client. When the read completes the OnReadComplete method is called.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-21

Network Streams
5. In OnReadComplete youll call EndRead on the NetworkStream. If bytes were received youll treat the string received as a file name. Youll open that file and read from it asynchronously, passing in a delegate to OnFileReadComplete. 6. When a buffer from the file is filled, OnFileReadComplete is called. This method calls EndRead on the FileStream to finish the read and then it passes that buffer to BeginWrite on the NetworkStream. This writes the string to the client asynchronously. 7. When the write completes OnWriteComplete is called. OnWriteComplete updates the console and then calls BeginRead on the FileStream. In this way, the program ping-pongs back and forth between reading the file asynchronously and writing it out to the client. This is tremendously efficient; you can read the file asynchronously and you can write it asynchronously. When the file is completed close all the streams and youre done.

Try It Out!
See NetworkIO Server3.sln and NetworkIOClient3. sln Youll see how this works by implementing a new server and client. The server will implement asynchronous files and network I/O.

1. Create a new server console application named NetworkIOServer3. 2. Add the required namespaces.
using System; using System.Net.Sockets; using System.Text; using System.IO;

3. Create a Run method that opens the TcpListener and waits for a connection. Delegate handling the connection to the ClientHandler object (as seen in previous examples):
public class AsynchNetworkIOServer { public static void Main() { AsynchNetworkIOServer app = new AsynchNetworkIOServer();

23-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Long Distance I/O


app.Run(); }

private void Run() { TcpListener tcpListener = new TcpListener(65000); tcpListener.Start();

for (;;) { Socket socketForClient = tcpListener.AcceptSocket(); if (socketForClient.Connected) { Console.WriteLine("Client connected"); ClientHandler handler = new ClientHandler(socketForClient); handler.StartRead(); } } } // end if // end for ever loop // end run method

4. Create the nested class ClientHandler. Add private fields as in previous examples; this time add a third delegate for OnFileReadComplete and add a stream for the file.
class ClientHandler { private const int private byte[] private Socket private NetworkStream private Stream private AsyncCallback private AsyncCallback private AsyncCallback BufferSize = 256; buff; socket; networkStream; fileStream; cbRead; cbWrite; cbFileRead;

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-23

Network Streams
5. In the constructor you will create the NetworkStream by passing in the socket you receive as a parameter and initializing the three delegates.
public ClientHandler( Socket socketForClient ) { socket = socketForClient;

buff = new byte[256];

networkStream = new NetworkStream(socketForClient);

cbFileRead = new AsyncCallback(this.OnFileReadComplete);

cbRead = new AsyncCallback(this.OnReadComplete);

cbWrite = new AsyncCallback(this.OnWriteComplete); }

6. Implement the StartRead method to begin the read across the network.
public void StartRead() { // read from the network // get a file name networkStream.BeginRead( buff, 0, buff.Length, cbRead, null); }

7. Implement OnReadComplete to handle the completed read callback. Read the buffer as a string and assume that it holds the name of the file the server is to read:

23-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Long Distance I/O


private void OnReadComplete( IAsyncResult ar ) { int bytesRead = networkStream.EndRead(ar);

if( bytesRead > 0 ) {

string fileName = System.Text.Encoding.ASCII.GetString( buff, 0, bytesRead);

8. Use the file name to open a FileStream object by calling the static OpenRead method on the File class.
Console.Write( "Opening file {0}", fileName);

fileStream = File.OpenRead(fileName);

9. Call BeginRead on the FileStream, passing in a buffer to hold the contents of the file, along with the delegate for the method to callback when the buffer is filled.
fileStream.BeginRead( buff, 0, buff.Length, cbFileRead, null); // holds the results // offset // BufferSize // call back delegate // local state object

10. If the number of bytes read is not greater than zero, close the NetworkStream and socket, and set both to null.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-25

Network Streams
} else { Console.WriteLine( "Read connection dropped"); networkStream.Close(); socket.Close(); networkStream = null; socket = null; } } // end else // end OnReadComplete // end if bytesRead > 0

11. Implement OnFileReadComplete. This method is called when the file has been read into the buffer. End the read with a call to the EndRead method of the FileStream instance. If you did get bytes from the file, write them to the NetworkStream. Pass in the delegate for the method to call when the write completes (OnWriteComplete).
void OnFileReadComplete(IAsyncResult asyncResult) { int bytesRead = fileStream.EndRead(asyncResult);

// if you read some file if (bytesRead > 0) { // write it out to the client networkStream.BeginWrite( buff, 0, bytesRead, cbWrite, null); } }

12. Implement OnWriteComplete. End the Write by calling EndWrite on the NetworkStream. Having written to the client, call BeginRead on the fileStream, starting the next round of reading and writing.

23-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Long Distance I/O


private void OnWriteComplete( IAsyncResult ar ) { networkStream.EndWrite(ar); Console.WriteLine( "Write complete"); fileStream.BeginRead( buff, 0, buff.Length, cbFileRead, null); } } } // end OnWriteComplete // end ClientHandler // end AsynchNetworkIOServer // holds the results // offset // (BufferSize) // *** call back delegate // local state object

13. Build the server and start it. Build the client and run it. As shown in Figure 5, the server starts up and waits for a client. The client connects to the server and that is reflected in the server window. The server opens the file requested by the client. The server then sends buffers-full until the file is completely read and displayed on the client.

Figure 5. The asynchronous network file server.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-27

Network Streams

Serialization
When an object is saved to disk or sent over the Internet it must be serialized that is written out as a stream of bytes, one after the other. When you serialize an object, instruct each member field to serialize itself. Intrinsic types (such as integer, double, etc.) know how to serialize themselves, as illustrated in Figure 6.

Figure 6. Streaming data.

Member fields that are not primitive types must stream themselves, as illustrated in Figure 7. This works because all user-defined types are either composed of primitive types or of other user-defined types. Each user-defined type delegates responsibility for streaming to all its fields; ultimately every user-defined type reduces to primitive types, and primitive types know how to stream themselves.

23-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Serialization

Figure 7. User-defined types stream themselves.

Marking Types for Serialization


It turns out that by default user-defined types are not serializable; you must explicitly mark them using an attribute. To mark your class serializable use the following attribute:
[serializable]

Formatters
A formatter is used to determine the format of the serialization. The .NET Framework provide two formatters: SOAP and Binary. Youll typically use the SOAP formatter for serializing across the Web, and the Binary formatter for serializing to disk. You are also free to write your own formatter, but this is a nontrivial task.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-29

Network Streams

Try It Out!
See Serialization0.sln To see how serialization works, youll build a simple console application with a class to serialize. 1. To get started, create a new console application and call it Serialization. 2. Create a class named Products. This class will create an array of products such that the nth entry is equal to n time the value at n-1. So, the fifth entry is equal to five times the value at the fourth entry. This class needs three private members: a starting value (1), an ending number, and an array to hold the products.
class Products { private int startNumber = 1; private int endNumber; private int[] theProducts;

3. Add a constructor to set the start number and end number. Within the constructor youll call the two methods ComputeProducts and DisplayProducts. The former fills the array, the latter displays their contents:
public static void Main() { Products p = new Products(1,12); }

public Products(int start, int end) { startNumber = start; endNumber = end; ComputeProducts(); DisplayProducts(); }

4. ComputeProducts creates the array and fills it with values.

23-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Serialization
private void ComputeProducts() { int count = endNumber - startNumber + 1; theProducts = new int[count]; theProducts[0] = startNumber; for (int i=1,j=startNumber + 1;i<count;i++,j++) { theProducts[i] = j * theProducts[i-1];

} }

5. DisplayProducts just iterates over the array, displaying each value.


private void DisplayProducts() { foreach(int i in theProducts) { Console.WriteLine("{0}, ",i); } }

6. Compile and run the program. The results are shown in Figure 8. You can see that the fifth value (120) is the fourth value (24) times five.

Figure 8. Running the Products test program.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-31

Network Streams
7. To serialize the class, add the Serialization namespaces.
using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary;

8. Add the Serializable attribute to your class. Note, that this is not a custom attribute, its an intrinsic attribute provided by the .NET Framework.
[Serializable] class Products {

9. Modify the Main method to Serialize the class after it is created. This requires a Serialize method that you will create in Step 11.
public static void Main() { Console.WriteLine("Creating first one with new..."); Products p = new Products(1,10);

p.Serialize();

10. You can imagine that another program will deserialize the class youve just serialized to disk. To simulate that, youll now create a second instance of the Products class by deserializing that same file. This requires writing the Deserialize method, which you will do in Step 15.
Console.WriteLine( "Creating second one with deserialize..."); Products p2 = Products.DeSerialize(); p2.DisplayProducts();

// end Main()

11. Write the Serialize method. The job of this method is to write the object to a file.

23-32

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Serialization
public void Serialize() { Console.Write("Serializing...");

12. Open a FileStream object and pass in the name of the output file, along with a flag indicating that you want to create a new file.
FileStream fileStream = new FileStream("DoProduct1.out",FileMode.Create);

13. You want this file written using the Binary formatter (as opposed to the SOAP formatter or a custom formatter youd write yourself.) Instantiate a BinaryFormatter, and call Serialize on it, passing in the FileStream you just created and a reference to the object you want to serialize.
BinaryFormatter binaryFormatter = new BinaryFormatter();

binaryFormatter.Serialize(fileStream,this);

14. Youve serialized the object, you may now close the FileStream.
Console.WriteLine("...completed"); fileStream.Close(); } // end Serialize

15. Write the Deserialize method to retrieve the object from the disk. Start by creating a FileStream object to read the file you just saved. Pass in the name of the file and a flag indicating that you want to open an existing file.
public static Products DeSerialize() { FileStream fileStream = new FileStream("DoProduct1.out",FileMode.Open);

16. The file was saved using a BinaryFormatter, so youll need another BinaryFormatter to read it. Call DeSerialize on the BinaryFormatter and cast the result to an object of type Products. Close the FileStream. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-33

Network Streams

public static Products DeSerialize() { FileStream fileStream = new FileStream("DoProduct1.out",FileMode.Open); BinaryFormatter binaryFormatter = new BinaryFormatter(); Products p = (Products) binaryFormatter.Deserialize(fileStream); fileStream.Close(); return p; }

17. Compile and run the program. As shown in Figure 9, you can see that the first instance was created and run, then serialized. The second instance is created by deserializing the object from disk, and when you call DisplayProducts on the second instance it displays properly.

Figure 9. Deserializing the object from disk.

23-34

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Serialization

Dont Serialize Everything


In some classes, you may decide that there is data you do not want to serialize. Typically, this would happen when the data will take a lot of room on the disk and when you can easily reproduce the data at run time. You can mark specific members of your class not to be serialized with the [NonSerialized] attribute. Mark the array with this attribute to signal to the compiler not to serialize this field:
[NonSerialized] private long[] theProducts;

Implementing IDeserializationCallBack
This presents a bit of a problem, however. If you do not serialize the array, how will you restore the array? The Framework supports an interface IDeserializationCallBack for this very purpose. To have an opportunity to restore your file you will implement this interface. This interface describes only a single method: OnDeserialization. You implement OnDeserialization, and in that method you restore the array, in this case by recomputing the numbers.

The Time/Space Continuum


It turns out that you are trading disk space for performancethat is, by not serializing the array your file will be smaller, but your reconstruction will take a bit longer. This tradeoff makes little sense (or little difference) in this trivial example, but can be a terrific optimization with a large object.

Try It Out!
See Serialization2.sln To see how you can control what gets serialized, modify the previous example to avoid serializing the array. 1. Reopen the previous example. 2. Search and replace int and make it long. 3. Change the parameters to the constructor from 1,10 to 1,20; this will create an array twice as large.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-35

Network Streams
public static void Main() { Console.WriteLine("Creating first one with new..."); Products app = new Products(1,20);

4. Run the program. Navigate to the file produced (DoProduct.out) and rightclick on it. The file is 361 bytes (see Figure 10).

Figure 10. Examining the output file.

You can imagine that with a far larger array, this file would be much bigger. At some point, it might be worth a bit of effort to cut down the size of this file. 5. Mark the class to implement IDeserializationCallBack.
[Serializable] class Products : IDeserializationCallback {

23-36

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Serialization
6. Mark the array not to be serialized.
private long startNumber = 1; private long endNumber; [NonSerialized] private long[] theProducts;

7. Implement the OnDeserialization method. In this case, you can just call ComputeProducts.
public virtual void OnDeserialization(object sender) { Console.WriteLine("Recomputing..."); ComputeProducts(); }

8. Compile and run the program. As shown in Figure 11, the output is identical, though you may notice a brief stutter while the numbers are recomputed.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-37

Network Streams

Figure 11. Implementing IdeserializationCallBack.

9. Right-click on the file produced. As shown in Figure 12, the file size has been cut significantly. By not storing the array of values youve saved 183 bytes.

23-38

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Serialization

Figure 12. Reduced file size.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-39

Network Streams

Summary
Because the Stream object is properly abstracted, reading and writing to the network is not very different from reading and writing to a file. Read from the Web by using a WebRequest object to create a WebResponse object, and then call GetResponseStream on the WebResponse instance. Read from a file across the network just as you would read from a local file, except that you use sockets to make the connection to the remote machine. Identify the application to which you will connect by using a port. If you have an application that will provide I/O support, youll want to use the overlapped I/O support provided by the Framework to allow your application to scale to handle more than one request at a time. When an object is written to a stream, it must be serialized. Intrinsic types know how to serialize themselves. User-defined types delegate responsibility for serialization to each of their members. You can control which fields are serialized, but if you do you will want to implement IdeserializationCallBack.

23-40

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Serialization

Questions
1. What is a URI? 2. What is a port? 3. What is a socket? 4. What is the advantage of asynchronous network I/O? 5. How do user-defined types handle requests for serialization? 6. How do you mark a class for serialization? 7. How do you mark a field to avoid serialization?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-41

Network Streams

Answers
1. What is a URI?
A URI is a Uniform Resource Identifier. The most common type of URI is a URLthe typical Web address.

2. What is a port?
A port uniquely identifies access to a particular application running at a given IP address.

3. What is a socket?
A socket is an object that represents an end point a specific server to which your client may connect.

4. What is the advantage of asynchronous network I/O?


Servers typically receive requests from many clients simultaneously. They must handle these requests in a timely fashion; by using asynchronous network I/O the server can hand the request off to another object and continue to field requests for connections from other clients.

5. How do user-defined types handle requests for serialization?


User defined types delegate responsibility to their member fields. Those fields will either be intrinsic types or other user-defined types. Intrinsic types know how to serialize themselves; other user-defined types will continue to delegate. Ultimately, all requests will arrive at an intrinsic type that will serialize itself.

6. How do you mark a class for serialization?


You mark a class for serialization with the [serializable] attribute.

7. How do you mark a field to avoid serialization?


You mark the field with the [Nonserializable] attribute, but if you do, be sure to implement the IDeserializationCallBack interface so that you have an opportunity to recreate the field.

23-42

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Serialization

Lab 23: Network Streams

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-43

Lab 23: Network Streams

Lab 23 Overview
In this lab youll learn how to stream data across the Web and how to control the serialization of your objects. To complete this lab, youll need to work through two exercises: Accessing Data with Web Streams Controlling Serialization

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions.

23-44

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Accessing Data with Web Streams

Accessing Data with Web Streams


Objective
In this exercise, youll access a Web page using a URL and youll stream the data retrieved to the console.

Things to Consider
A URL is a unique identifier of a specific page on the World Wide Web. The .NET Framework encapsulates a request for a Web page in an HttpWebRequest object, and it encapsulates a Web page returned by a HttpWebRequest in an HttpWebResponse object. Any time you request an object across the Web, you want to use a try/catch block to handle the exceptions that may arise from missing pages or unavailable networks.

Step-by-Step Instructions
1. Open WebStreamStarter.sln in the WebStreamStarter folder. 2. Add using statements for System.Net, System.Net.Sockets, System.IO, and System.Text.
using System.Net; using System.Net.Sockets; using System.IO; using System.Text;

3. Create an HttpWebRequest object named webRequest for the page www.libertyassociates.com/pages/book_edit.htm.


HttpWebRequest webRequest = (HttpWebRequest) WebRequest.Create

("http://www.libertyassociates.com/pages/book_edit.htm");

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-45

Lab 23: Network Streams


4. Ask the Web request for an HttpWebResponse object named webResponse encapsulating that page.
HttpWebResponse webResponse = (HttpWebResponse) webRequest.GetResponse();

5. Get the StreamReader object from the response.


StreamReader streamReader = new StreamReader( webResponse.GetResponseStream(), Encoding.ASCII);

6. Assign to the outputString the text returned by calling ReadToEnd on the StreamReader object.
outputString = streamReader.ReadToEnd();

7. Write the string to the console.


Console.WriteLine(outputString);

8. Close the StreamReader object.


streamReader.Close();

9. Compile and run the program as shown in Figure 13.

23-46

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Accessing Data with Web Streams

Figure 13. The network stream results.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-47

Lab 23: Network Streams

Controlling Serialization
Objective
In this exercise, youll create a class, serialize it to disk, and reconstitute it. You will also dictate which parts of the class will be serialized by implementing IdeserializationCallback.

Things to Consider
You mark a class for serialization with the Serializable attribute. To be notified when the class is reconstituted, youll implement IDeserializationCallBack. You can mark some members of your class not to be serialized with the NonSerialized attribute.

Step-by-Step Instructions
1. Open SerializationStarter.sln in the SerializationStarter folder. 2. Add using statements for System.IO, System.Runtime.Serialization, and System.Runtime.Serialization.Formatters.Binary.
using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary;

3. Create a class named Sums. Mark it as Serializable and have it implement the IDeserializationCallback interface.
[Serializable] class Sums : IDeserializationCallback {

4. There are three private member variables. The first two are startNumber and endNumber, both of type long. 23-48 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Controlling Serialization
private long startNumber = 1; private long endNumber;

5. The third member is an array of longs named theSums. Mark this one so it isnt serialized.
[NonSerialized] private long[] theSums;

6. Implement the OnDeserialization method that takes one parameter of type object.
public virtual void OnDeserialization(object sender) {

7. Display a message to the console that you are recomputing the sums.
Console.WriteLine("Recomputing...");

8. Call ComputeSums.
ComputeSums();

9. Implement the Constructor to take two parameters. The first parameter is a long representing the start value, and the second parameter is a long representing the end value.
public Sums(long start, long end) {

10. Initialize the member variables.


startNumber = start; endNumber = end;

11. Call ComputeSums.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-49

Lab 23: Network Streams


ComputeSums();

12. Call DisplaySums.


DisplaySums();

13. Call Serialize.


Serialize();

14. Implement ComputeSums.


private void ComputeSums() {

15. Create a local variable to hold the number of sums to compute (the end number less the start number plus one).
long count = endNumber - startNumber + 1;

16. Initialize the member array with the right number of values.
theSums = new long[count];

17. Set the first value to the start number.


theSums[0] = startNumber;

18. Iterate over the array, adding the sums. Each member of the array is the sum of the index, plus the previous value in the array.

23-50

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Controlling Serialization
for (long i=1,j=startNumber + 1;i<count;i++,j++) { theSums[i] = j + theSums[i-1];

19. Implement DisplaySums.


private void DisplaySums() {

20. Iterate over the array and show the value of each member of the array.
foreach(long i in theSums) { Console.WriteLine("{0}, ",i); }

21. Implement Serialize.


private void Serialize() {

22. Display a message that you are serializing the object.


Console.Write("Serializing...");

23. Open a FileStream object for the output file.


FileStream fileStream = new FileStream("DoSum.out",FileMode.Create);

24. Instantiate a BinaryFormatter for the object.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-51

Lab 23: Network Streams


BinaryFormatter binaryFormatter = new BinaryFormatter();

25. Call Serialize on the binary formatter, passing in the file stream object and a reference to this object.
binaryFormatter.Serialize(fileStream,this);

26. Display a message that the formatting is completed.


Console.WriteLine("...completed");

27. Close the file stream.


fileStream.Close();

28. Implement DeSerialize.


public static Sums DeSerialize() {

29. Open a file stream on the output file.


FileStream fileStream = new FileStream("DoSum.out",FileMode.Open);

30. Instantiate a Binary formatter for the object.


BinaryFormatter binaryFormatter = new BinaryFormatter();

31. Deserialize the object and cast it to type Sums. Assign to a local object.
Sums theObject = (Sums) binaryFormatter.Deserialize(fileStream);

23-52

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Controlling Serialization
32. Close the file stream object.
fileStream.Close();

33. Return the object you deserialized.


return theObject;

34. Compile and run the program as shown in Figure 14.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

23-53

Lab 23: Network Streams

Figure 14. The serialization results.

23-54

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

COM Interop

COM Interop
Objectives
Create an ActiveX control in Visual Basic 6 to represent a legacy COM control. Import the ActiveX control into a .NET application. Create a COM DLL in Visual Basic 6 to represent a legacy COM DLL. Import the COM DLL using early binding. Import the COM DLL using reflection (late binding).

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

24-1

COM Interop

ActiveX
When a new technology comes along with the power and grace of .NET, it is tempting to throw away all your old code and start clean on every new project. Unfortunately, that is totally unrealistic; many companies have invested a great deal of time, money, and effort into building COM and ActiveX objects, and should reuse these tested and working components with the new technology. Anticipating this requirement, Microsoft has provided a clean and easy path for integrating existing ActiveX and COM DLL components into new .NET applications. The steps to using an existing ActiveX component with .NET are: 1. Import the ActiveX control into the .NET environment. 2. Add the ActiveX control to your Toolbox. 3. Drag it onto your form and use it like any other control. Thats all there is to it.

Try It Out!
See ActiveX.sln To demonstrate using ActiveX controls youll create a control in Visual Basic 6 and then youll import it into a .NET application. 1. Open the Visual Basic 6 development environment and choose ActiveX Control as the new project type, as shown in Figure 1.

24-2

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

ActiveX

Figure 1. Creating a new ActiveX control.

2. Right-click on the form (UserControl1) and choose Properties. Rename it Calculator in the Properties window. 3. Click on the project in the Project Explorer and in the Properties window, rename it ActiveXCalculatorControl. 4. Now you can add the four calculator functions by right-clicking on the CalcControl form, selecting View Code from the popup menu and typing in the following code:

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

24-3

COM Interop
Public Function _ Add(left As Double, right As Double) _ As Double Add = left + right End Function

Public Function _ Subtract(left As Double, right As Double) _ As Double Subtract = left right End Function

Public Function _ Multiply(left As Double, right As Double) _ As Double Multiply = left * right End Function

Public Function _ Divide(left As Double, right As Double) _ As Double Divide = left / right End Function

5. Return to the form and shrink it as small as possibleit will have no user interface. 6. Compile this to the ActiveXCalculatorControl.ocx file by choosing File|Make ActiveXCalculatorControl.ocx. 7. Open a second project in Visual Basic as a Standard EXE. 8. Name the form TestForm and name the project ActiveXCalcTest. 9. Save the file and project as ActiveXCalcTest. 10. Add the ActiveX control as a component by pressing CTRL+T and choosing CalcControl from the Controls tab. 11. This action puts a new control on the Toobox, as shown in Figure 2.

24-4

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

ActiveX

Figure 2. Adding the new control.

12. Drag the new control onto the form named TestForm and name it calcControl. Note that the new control will not be visible; this control has no user interface. 13. Add two text boxes, four buttons, and one label, as shown in Figure 3.

Figure 3. Creating the controls.

14. Name the buttons btnAdd, btnSubtract, btnMultiply, and btnDivide. C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

24-5

COM Interop
15. Implement the event handlers.
Private Sub btnAdd_Click() Label1.Caption = _ calcControl.Add(CDbl(Text1.Text), _ CDbl(Text2.Text)) End Sub

Private Sub btnDivide_Click() Label1.Caption = _ calcControl.Divide(CDbl(Text1.Text), _ CDbl(Text2.Text)) End Sub

Private Sub btnMultiply_Click() Label1.Caption = _ calcControl.Multiply(CDbl(Text1.Text), _ CDbl(Text2.Text)) End Sub

Private Sub btnSubtract_Click() Label1.Caption = _ calcControl.Subtract(CDbl(Text1.Text), _ CDbl(Text2.Text)) End Sub

16. Run the program. The test program delegates responsibility for computing the sum, difference, product or quotient to the calculator control you created, as shown in Figure 4.

Figure 4. Testing the code.

24-6

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

ActiveX

Importing to .NET
You now have a working ActiveX control. You can copy that control to your .NET environment and import it into your next .NET application.

Try It Out!
See ActiveXTest.sln To see how to import the ActiveX control, youll create a simple Windows Test form, not unlike the test form you created in Visual Basic 6. 1. Copy the .OCX file produced in the previous example to your .NET development environment. 2. Register the ocx file using Regsvr32. Do so by opening a command window and navigating to the directory with the control. Type regsvr32 activeXCalculatorControl.ocx. The system will respond now that the control has been registered, as shown in Figure 5.

Figure 5. Registering the control.

3. Create a new .NET Windows Forms project named ActiveXTest. 4. Design a form like the TestForm form you created in Visual Basic, as shown in Figure 6.

Figure 6. Create the test form in .NET.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

24-7

COM Interop
5. Import the control. To do so, choose Tools|Customize Toolbox from the menu. On the Com Components tab find the object you just registered, as shown in Figure 7.

Figure 7. Adding the control.

6. The control appears on your Toolbox, as shown in Figure 8.

24-8

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

ActiveX

Figure 8. The control added to the Toolbox.

7. Drag the new control onto your Windows form. 8. Add event handlers for each of the four buttons. The easiest way is to double-click on each of the buttons in turn. You need to convert the string in the text box to a double. There are two ways to do this: you can use the Parse static method of the Double class, or you can use the ToDouble static method of the Convert class. These are illustrated in the btnAddclick event handler:
private void btnAdd_Click( object sender, System.EventArgs e) { // note two ways to do this! double left = double.Parse(textBox1.Text); double right = Convert.ToDouble(textBox2.Text);

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

24-9

COM Interop

label1.Text = axCalculator1.Add( ref left, ref right).ToString(); }

private void btnDivide_Click( object sender, System.EventArgs e) { double left = double.Parse(textBox1.Text); double right = double.Parse(textBox2.Text); label1.Text = axCalculator1.Divide(ref left, ref right).ToString(); }

private void btnMultiply_Click( object sender, System.EventArgs e) { double left = double.Parse(textBox1.Text); double right = double.Parse(textBox2.Text); label1.Text = axCalculator1.Multiply( ref left, ref right).ToString(); }

private void btnSubtract_Click( object sender, System.EventArgs e) { double left = double.Parse(textBox1.Text); double right = double.Parse(textBox2.Text); label1.Text = axCalculator1.Subtract( ref left, ref right).ToString(); }

9. Compile and build the project, and then run the application. It appears nearly identical to the test application you built in Visual Basic 6, as shown in Figure 9.

24-10

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

ActiveX

Figure 9. Testing the imported ActiveX control.

What Happened?
Think about what you just did. You created an ActiveX control in Visual Basic 6 to represent an existing legacy ActiveX control. You built a new Visual Studio .NET Windows Form application and you used that control in your new form just by registering it on your .NET system and then adding it to your toolbar!

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

24-11

COM Interop

COM DLLs
Not all existing COM applications are ActiveX controls, however. Many are COM DLLs. You can use an existing legacy COM DLL in .NET just about as easily as you can use an ActiveX control.

Try It Out!
To represent an existing legacy COM DLL youll create a new COM project in See ComCalculator.vbb Visual Basic 6.

1. Return to the Visual Basic 6 development environment, and create a new project. This time, choose ActiveX DLL. This is the easiest way to create a standard COM DLL in Visual Basic 6. 2. Name the class ComCalc and name the project ComCalculator. Save the file and project. 3. Use the same methods in this example as you did in the ActiveXControl.
Public Function _ Add(left As Double, right As Double) _ As Double Add = left + right End Function

Public Function _ Subtract(left As Double, right As Double) _ As Double Subtract = left - right End Function

Public Function _ Multiply(left As Double, right As Double) _ As Double Multiply = left * right End Function

24-12

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

COM DLLs
Public Function _ Divide(left As Double, right As Double) _ As Double Divide = left / right End Function

4. Build the DLL by clicking File|Make ComCalculator.dll. 5. Reopen the earlier test program you created. 6. Remove the Calculator control from the form. 7. Try to run the program, but it should fail as shown in Figure 10.

Figure 10. After removing the control.

8. Open the Project Reference window. Add the ComCalculator you just created. In the event handler, instantiate a comcalc object and call its methods.
Private Sub btnAdd_Click() Dim theCalc As New ComCalc Label1.Caption = _ theCalc.Add(CDbl(Text1.Text), _ CDbl(Text2.Text)) End Sub

9. Make this change in each of the four methods.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

24-13

COM Interop
Private Sub btnAdd_Click() Dim theCalc As New ComCalc Label1.Caption = _ theCalc.Add(CDbl(Text1.Text), _ CDbl(Text2.Text)) End Sub

Private Sub btnDivide_Click() Dim theCalc As New ComCalc Label1.Caption = _ theCalc.Divide(CDbl(Text1.Text), _ CDbl(Text2.Text)) End Sub

Private Sub btnMultiply_Click() Dim theCalc As New ComCalc Label1.Caption = _ theCalc.Multiply(CDbl(Text1.Text), _ CDbl(Text2.Text)) End Sub

Private Sub btnSubtract_Click() Dim theCalc As New ComCalc Label1.Caption = _ theCalc.Subtract(CDbl(Text1.Text), _ CDbl(Text2.Text)) End Sub

10. Run the program. The results are identical to the previous test, as shown in Figure 11.

24-14

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

COM DLLs

Figure 11. Testing the COM DLL in Visual Basic 6.

Importing the DLL back into .NET


You are ready to import the DLL back to the .NET environment and use it in your next Windows Forms application. You have a decision to make, however. There are two ways to bind to a COM control. The first is called early binding and the second is called late binding. With early binding, you bind to the COM DLL as if it were part of your program. Early binding requires that you have a type library, but it is much more efficient than late binding. If you do not have a type library you must use late binding. Late binding uses reflection to discover the capabilities of the control in the DLL and is less efficient.

Try It Out!
See ActiveXTest.sln To try out early binding, youll return to the previous .NET test example and modify it to use the new COM DLL with early binding. 1. Copy the COM DLL to the .NET environment. 2. Register the DLL using RegSvr32 just as you did previously, as shown in Figure 12.

Figure 12. Registering the DLL.

3. Reopen the test program. C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

24-15

COM Interop
4. Go to the designer and remove the ActiveX control. 5. Rebuild the program. It will not compile, because it can no longer find axCalculator1. 6. Add the new COM DLL by navigating to Project|Add Reference and then clicking the COM tab. Double-click on the ComCalculator to add it to the list of Selected Components, as shown in Figure 13.

Figure 13. Adding the ComCalculator to the project.

7. When you click OK, Visual Studio .NET tells you that this assembly is not a .NET assembly, as shown in Figure 14. Click the Yes button and Visual Studio .NET builds the required wrapper class for you.

Figure 14. Importing the COM DLL with a wrapper.

24-16

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

COM DLLs
8. Modify the code in btnAdd_Click:
Double left, right, result; left = Double.Parse(textBox1.Text); right = Double.Parse(textBox2.Text);

9. To instantiate a ComCalc object, type ComCalculator followed by a period. The IDE tries to help you identify the object you want, as shown in Figure 15.

Figure 15. Visual Studio .NET recognizes your COM DLL.

Choose ComCalc and instantiate the object:


ComCalculator.ComCalc theCalc = new ComCalculator.ComCalc();

10. You want to assign to the variable result the value returned by calling Add on the ComCalc object. When you open the Add method, the IDE reminds you that the parameters must be references, as shown in Figure 16.

Figure 16. The IDE helps with the parameters.

Add the local variables left and right, passing them in as references:
result = theCalc.Add(ref left, ref right);

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

24-17

COM Interop
11. Set the text for the label.
label1.Text = result.ToString();

12. Make the same changes in the other methods.


private void btnDivide_Click( object sender, System.EventArgs e) { double left, right, result; left = Double.Parse(textBox1.Text); right = Convert.ToDouble(textBox2.Text); ComCalculator.ComCalc theCalc = new ComCalculator.ComCalc();

result = theCalc.Divide(ref left, ref right); label1.Text = result.ToString(); }

private void btnMultiply_Click( object sender, System.EventArgs e) { double left, right, result; left = Double.Parse(textBox1.Text); right = Convert.ToDouble(textBox2.Text); ComCalculator.ComCalc theCalc = new ComCalculator.ComCalc();

result = theCalc.Multiply(ref left, ref right); label1.Text = result.ToString(); }

private void btnSubtract_Click( object sender, System.EventArgs e) { double left, right, result; left = Double.Parse(textBox1.Text);

24-18

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

COM DLLs
right = Convert.ToDouble(textBox2.Text); ComCalculator.ComCalc theCalc = new ComCalculator.ComCalc();

result = theCalc.Subtract(ref left, ref right); label1.Text = result.ToString(); }

13. Compile and run the program. The results are identical to the previous test, as shown in Figure 17.

Figure 17. Testing the COM DLL.

Late Binding
You were able to use early binding with this COM DLL because Visual Basic 6 included a type library in the DLL when it created the DLL. If, however, you have a legacy COM DLL that does not have a type library, then you will need to use reflection to run the program.

Try It Out!
See ActiveXTest.sln To see how to do late binding, youll go back to modify the Visual Studio .NET test application just one more time. 1. Reopen the test application from the previous example. 2. Add the Reflection namespace to the top of the program.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

24-19

COM Interop
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Reflection;

3. Factor the code for creating the calculator object into a new method method: Invoke. Invoke will take a single parameter and a string indicating which operation is to be accomplished. Give Invoke a local variable to hold the values in the two text boxes and to hold the result.
private void Invoke(string whichMethod) { Double left, right, result; left = Double.Parse(textBox1.Text); right = Double.Parse(textBox2.Text);

4. Create a Type object to hold the information about the type youll be binding to.
Type comCalcType;

5. Assign to the Type object the result of calling the static method GetTypeFrom ProgID, passing in the ProgID of the DLL.
comCalcType = Type.GetTypeFromProgID( "ComCalculator.ComCalc");

6. Call the static method CreateInstance on the Activator class, passing in the Type object you created in Step 5.
object comCalcObject = Activator.CreateInstance(comCalcType);

24-20

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

COM DLLs
7. Create an array of objects to hold the arguments for the method youll invoke.
object[] inputArguments = {left, right};

8. Invoke the method, passing in the string you receive as a parameter (e.g., Add), the object you created with CreateInstance, and the argument array.
result = (Double) comCalcType.InvokeMember( whichMethod, BindingFlags.InvokeMethod, null, comCalcObject, inputArguments); // the method to invoke // how to bind // binder // the COM object // the method arguments

9. Put the results in the label.


label1.Text = result.ToString();

10. Go back to the event handlers and call this new method.
private void btnAdd_Click( object sender, System.EventArgs e) { Invoke("Add"); }

private void btnDivide_Click( object sender, System.EventArgs e) { Invoke("Divide"); }

private void btnMultiply_Click( object sender, System.EventArgs e) { Invoke("Multiply");

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

24-21

COM Interop
}

private void btnSubtract_Click( object sender, System.EventArgs e) { Invoke("Subtract"); }

11. Compile and build the application. The results are identical to the previous (early binding) example, as shown in Figure 18.

Figure 18. Late binding.

Late binding is somewhat slower and less efficient, but it works and allows you to use legacy COM applications even if you dont have a type library.

24-22

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

COM DLLs

Summary
The .NET Framework supports using legacy ActiveX and COM objects as if they were native .NET assemblies. Visual Studio .NET provides extensive support for importing both ActiveX controls and COM DLLs. Once you add an ActiveX control to your toolbar, you may use it like you would any other item on the toolbar. Once you import a COM DLL object into your environment, you may instantiate it (early binding). This requires that a type library be available. If a type library is not available for your COM object, you can use reflection to accomplish late binding to the object.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

24-23

COM Interop

(Review questions and answers on the following pages.)

24-24

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

COM DLLs

Questions
1. Why would you import an ActiveX control into .NET? 2. How do you register the ActiveX control on your .NET machine? 3. How do you add the ActiveX control to your Toolbox? 4. How do you register a COM DLL on your .NET machine? 5. How do you add a reference to the COM DLL in your .NET program? 6. How do you accomplish late binding with COM objects?

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

24-25

COM Interop

Answers
1. Why would you import an ActiveX control into .NET?
You would import an ActiveX control into .NET to use a control that was built and tested with COM before the advent of .NET. This allows you to continue to leverage the investment of previous work.

2. How do you register the ActiveX control on your .NET machine?


You register an ActiveX control with Regsvr32.

3. How do you add the ActiveX control to your Toolbox?


On the Tools menu, choose Customize Toolbox. Click on the COM Components tab and click on the control you want to add.

4. How do you register a COM DLL on your .NET machine?


Trick question: you register the COM DLL exactly as you did the ActiveX control, with Regsvr32.

5. How do you add a reference to the COM DLL in your .NET program?
On the Project menu, choose Add Reference. Click on the COM tab and double-click on the object you want to add. Click OK to add it to the project. When Visual Studio .NET offers to build a wrapper class, choose YES. This requires a type library.

6. How do you accomplish late binding with COM objects?


You accomplish late binding with reflection.

24-26

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

COM DLLs

Lab 24: COM Interop


NOTE These exercises do not have starter files because they are very easy to reproduce from scratch, and they do not have completed files because the registration of the ActiveX control would be far more complicated than simply recreating the controls from scratch.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

24-27

Lab 24: COM Interop

Lab 24 Overview
In this lab youll learn to create an ActiveX control in Visual Basic 6 and then to import that control into your C# application. To complete this lab, youll need to work through two exercises: Creating the ActiveX Control Importing the ActiveX Control

Each exercise includes an Objective section that describes the purpose of the exercise. You are encouraged to try to complete the exercise from the information given in the Objective section. If you require more information to complete the exercise, the Objective section is followed by detailed step-bystep instructions

24-28

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating the ActiveX Control

Creating the ActiveX Control


Objective
In this exercise, youll create an ActiveX control in Visual Basic 6 and youll test that control in a simple Visual Basic 6 application

Things to Consider
ActiveX controls can be created in virtually any programming language, but Visual Basic 6 makes it simpler. The control youll create now is built in a Visual Basic 6 rather than in a .NET environment, because ActiveX controls represent legacy applications.

Step-by-Step Instructions
1. Open the Visual Basic 6 development environment and choose ActiveX Control as the new project type. Click Open. 2. Right-click on the form (UserControl1) and choose Properties. Rename the form MathMaker in the Properties window. 3. Click on the project in Project Explorer. In the Properties window, rename the project ActiveXMathControl. 4. Right-click on the MathMaker form, select View Code from the popup menu, and type in the code for Square.
Public Function Square(value As Double) As Double Square = value * value End Function

5. Type in the code for DoDouble.


Public Function DoDouble(value As Double) As Double DoDouble = value * 2 End Function

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

24-29

Lab 24: COM Interop


6. Type in the code for DoTriple.
Public Function DoTriple(value As Double) As Double DoTriple = value * 3 End Function

7. Return to the form and make it as small as possibleit will have no user interface. 8. Click File|Save MathMaker and save the file as MathMaker.ctl. 9. Choose File|Make ActiveXMathControl.ocx to compile the ActiveX control. 10. Open a second project in Visual Basic as a Standard EXE. You will be prompted to save your existing project. Do this by using the default name. 11. In the new project, name the form TestForm and name the project ActiveXMathTest. 12. Save the file and project as ActiveXMathTest. 13. Add the ActiveX control as a component by pressing CTRL+T and choosing ActiveXMathControl from the Controls tab. This action puts a new control on the Toolbox. 14. Drag the new control onto the form named TestForm and name it MathControl. Note that the new control will not be visible; this control has no user interface. 15. Add a text box and name it txtInput. Set its text to a blank string (so that the text box appears to be empty). 16. Add a button and name it btnSquare. Set its caption to Square. 17. Add two more buttons, one named btnDouble with the caption Double and the third named btnTriple with the caption Triple. Make all three buttons the same size and distribute them evenly below the text box. 18. Add a label named lblOutput with the caption No value computed. The form should now look like Figure 19.

24-30

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Creating the ActiveX Control

Figure 19. The form for testing the ActiveX control.

19. Implement the event handler for the Square button. Double-click the Square button to bring up the event handler. 20. Add code to extract the value from the text box and convert it to a double, then pass that value to the MathMaker object and invoke the Square function.
Private Sub btnSquare_Click() lblOutput.Caption = _ MathControl.Square(CDbl(txtInput.Text)) End Sub

21. Implement the event handler for the Double and Triple buttons in the same way.
Private Sub btnSquare_Click() lblOutput.Caption = _ MathControl.DoDouble(CDbl(txtInput.Text)) End Sub

Private Sub btnTriple_Click() lblOutput.Caption = _ MathControl.DoTriple(CDbl(txtInput.Text)) End Sub

22. Run the program. The test program delegates responsibility for computing the square, double, and triple of the typed-in value to control what youve created.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

24-31

Lab 24: COM Interop

Importing the ActiveX Control


Objective
In this exercise, youll import the ActiveX control you created in the previous exercise into the .NET environment.

Things to Consider
After you copy the ActiveX control into the .NET work area, youll need to register it. Visual Studio .NET makes it easy to import ActiveX controls

Step-by-Step Instructions
1. Copy the .OCX file produced in the previous exercise to your .NET development environment. 2. Register the OCX file using Regsvr32. Do so by opening a command window and navigating to the directory with the control. Type regsvr32 activeXMathControl.ocx. The system will let you know that the control has been registered, as shown in Figure 20.

Figure 20. Registering the control.

3. Create a new .NET Windows Forms project named ActiveXImport. 4. Change the name and the text of the form to ActiveXTest. 5. Change the default form name in the Main method from Form1 to ActiveXTest. 6. Drag a text box onto the form and name it txtValue. Set its text to a blank string. 24-32 C# Professional Skills Development
Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Importing the ActiveX Control


7. Drag three buttons onto the form, and name them btnSquare, btnDouble, and btnTriple. Set their text to Square, Double, and Triple, respectively. Make them the same size and space them evenly apart. 8. Drag a label onto the form and name it lblOutput. Set its text to No value computed. 9. Your form should now look like the form shown in Figure 21.

Figure 21. The ActiveXTest form.

10. To import the control, choose Tools|Customize Toolbox from the menu. On the COM Components tab find the object you just registered, as shown in Figure 22.

Figure 22. Importing the ActiveXMathControl.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

24-33

Lab 24: COM Interop


11. Drag the MathMaker control from your Toolbox to the form. Visual Studio .NET will give it a default name of axMathMaker1. 12. Add event handlers for the Square button. Double-click the Square button to open the default event handler for the click event. 13. Convert the text in the text box to a double.
private void btnSquare_Click( object sender, System.EventArgs e) { double theVal = double.Parse(txtValue.Text);

14. Set the value of the output label by invoking the Square method on the MathMaker object. Be sure to pass the value by reference.
lblOutput.Text = axMathMaker1.Square(ref theVal).ToString();

15. Implement the event handler for the Double button.


private void btnDouble_Click( object sender, System.EventArgs e) { double theVal = double.Parse(txtValue.Text); lblOutput.Text = axMathMaker1.DoDouble(ref theVal).ToString(); }

16. Implement the event handler for the Triple button.


private void btnTriple_Click( object sender, System.EventArgs e) { double theVal = double.Parse(txtValue.Text); lblOutput.Text = axMathMaker1.DoTriple(ref theVal).ToString(); }

24-34

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Importing the ActiveX Control


17. Run the application. The output should look like Figure 23.

Figure 23. Running the application.

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

24-35

Lab 24: COM Interop

24-36

C# Professional Skills Development


Copyright by Application Developers Training Company and AppDev Products Company, LLC All rights reserved. Reproduction is strictly prohibited.

Potrebbero piacerti anche