Visual C# ASP.

NET Programming
Harold Davis – Sybex 2002 Edited by ff

Creating a Web Service
• Understanding web services • Creating a web service using Notepad • Creating an ASP.NET web service using Visual Studio • Adding a class module • XML documentation tags

I believe that the best way to learn something is to plunge in. Of course, that leaves the question of where it's best to plunge. This book is, of course, about a programming language - C# - and a programming environment - Visual Studio .NET. It would be natural - and typical - to start with one or the other. Another conventional possibility would be to start by creating a Windows application. But let's not be conventional! C# is a brand new language, and web services are a genuinely new programming concept. New languages and revolutionary programming architectures don't come along very often. Why not plunge in in a way that keeps things interesting and isn't the 'same old, same old'? This chapter will show you how to create a very simple ASP.NET web service by hand using Notepad (it will be automatically compiled when the service is opened the first time). You'll also learn how to build somewhat more complex ASP.NET web services using Visual Studio. Along the way you'll learn (of course) about web services - and also C# language concepts, and how to work with the Visual Studio environment. When all is said and done, this is a book about programming in C#, and web services are only one of the exciting things you can create using C#. In this chapter, I'll use web services as a launch pad for helping you to understand class - based programming in C# - a truism, since all C# programming is working with classes. Before we get there, you do need to understand a bit about web services.

Understanding Web Services
A web service is a mechanism for making components available across the Internet using open standards, including HTTP (Hypertext Transfer Protocol) and XML (Extensible Markup Language). The idea is to create 'black box' components that can communicate with each other, regardless of the operating system or programming language. A little more precisely, a web service is a component, or module, of executable code with a special interface that makes its methods available for use (also called 'consumption') by other programs using an HTTPbased request. This request is made using HTTP GET or using HTTP POST and Simple Object Access Protocol (SOAP). (You are probably familiar with GETs and POSTs from working with HTML web forms; SOAP is discussed further in this section.)

Component-Based Distributed Architectures
Web services are by no means the only architectural technology used for component - based distributed computing; for example, you are probably somewhat familiar with Common Object Request Broker Architecture (CORBA) and Distributed Component Object Model (DCOM). Table 1.1 compares some of the characteristics of CORBA, DCOM, and web services. The protocols listed under the Web Service column are described throughout the subsequent sections of this chapter. Table 1.1: CORBA, DCOM, and Web Services Compared Characteristic Mechanism for remote procedure call (RPC) CORBA Internet Inter - ORB Protocol (IIOP) DCOM Distributed Computing Environment Remote Procedure Call (DCERPC) Network Data Representation (NDR) IDL Web Service HTTP


Common Data Representation (CDR) Interface Definition Language (IDL) Naming service and trading service No


Interface description



System Registry

UDDI repositories

Works through firewall?

No High No

Yes Low Yes

Complexity of protocols? High Cross - platform? Somewhat

As you can see from Table 1.1, web services have some significant advantages over CORBA and DCOM: web services are less complex, can get through firewalls, and are accessible from any client platform. Note that this, of course, does not mean that web services are always a good replacement for CORBA and DCOM - these other protocols have their place in homogenous systems behind a firewall in which the platform is the same and the servers are directly connected, and where performance is an important concern.

Ways to Create Web Services
Essentially, a web service is implemented as a SOAP XML document. There are many ways to create this document. For example, IBM provides a Web Services Toolkit, as does the Apache project. You can

also hand - format the SOAP XML. Even within the Microsoft universe, there are several different ways of implementing SOAP - based XML web services. These include:
• Microsoft's SOAP Toolkit, which lets you expose COM components as web services (and does not

require the .NET Framework for deployment). To download the SOAP Toolkit, go to and search for SOAP Toolkit.
• Office XP Web Services Toolkit. • An ATL Server implementation written in C++. ATL Server is part of Visual Studio .NET but does not

require the .NET Framework for deployment.
• .NET Remoting, which lets classes inherited from a base class named MarshalByRefObject be

exposed as web services using SOAP.

You probably will not be surprised to learn that ASP.NET - using either Visual Basic or C# ('see sharp') is the easiest way on this list to create web services. As I'll show you shortly, you can write an ASP.NET web service by hand in a text editor such as Notepad and let ASP.NET compile and deploy it for you, or you can take advantage of Visual Studio .NET's rich integrated development environment. Note: In this book, I'll use the term 'web service' to mean an ASP.NET web service rather than any of the other kinds of web services described above.

Simple Object Access Protocol (SOAP)
The SOAP specification can be found at According to the specification abstract, SOAP is a lightweight protocol for exchange of information in a decentralized, distributed environment. It is an XML based protocol that consists of three parts: an envelope that defines a framework for describing what is in a message and how to process it, a set of encoding rules for expressing instances of application - defined datatypes, and a convention for representing remote procedure calls and responses. It's worth noting that:
• While SOAP can be used as a remote procedure invocation mechanism, it can also be used to

exchange XML documents.
• SOAP uses XML namespaces.

• The SOAP envelope mentioned in the specification contains the actual message in the body of the

envelope. It also contains SOAP headers, which can be used programmatically (see Chapter 13, 'Web Services as Architecture,' for an example).
• When you want to invoke a method remotely, you're sending a SOAP request and getting a SOAP


Web Services Description Language (WSDL)
Web Services Description Language (WSDL) describes the methods supported by a web service, the parameters the methods take, and what the web service returns. You can find the specification, sponsored by a cross - industry group that includes IBM and Microsoft, at A WSDL document is an XML schema that provides the required information about a web service methods, data types, and response - so that a proxy can be created (you'll see how to create and use a proxy in Chapter 2, 'Consuming the Service on the Web'). Generally, creators of ASP.NET web services do not have to worry themselves about WSDL; a WSDL document is automatically generated at runtime on the fly by the ASP.NET runtime using a process called reflection. (Reflection is a mechanism that allows metadata about a program to be examined at runtime.)

Universal Description, Discovery, and Integration (UDDI)
How do you find a web service that you might want to consume? Conversely, how do you publish a web service so that others can find it? One answer is word of mouth. I might tell you about a web service, or you might tell me. Similarly, it's no problem for us to find the web services that we'll create in the remainder of this chapter. When we want to consume them, we'll know what we named them, and what URL to use. Universal Description, Discovery, and Integration (UDDI) is a more general, cross - industry effort at creating a repository for publishing and finding web services. The UDDI project ( consists of a registry and a set of APIs for accessing the registry. IBM and Microsoft maintain cross synchronized UDDI registries that can be browsed. The Microsoft registry can also be accessed from the Visual Studio Start page, as explained in Chapter 2. In addition to UDDI, there are websites that provide directories of web services you can consume. You'll find more information about this in the 'UDDI' section of Chapter 2.

One If by Hand
You have enough background about web services to get started with creating one. We'll start with a text editor and the simple 'Hello, Web Service!' program shown in Listing 1.1 and in Notepad in Figure 1.1. Listing 1.1: 'Hello, Web Service!'
<%@ WebService Language="C#" class="Helloweb" %> using System.Web.Services; [WebService (Namespace="")] public class Helloweb { [WebMethod] public string HelloWebService() { return "Hello, Web Service!"; } }

Figure 1.1: A web service can be created using Notepad.

Let's have a look at this web service line by line. The directive at the top of the code
<%@ WebService Language="C#" class="Helloweb" %>

tells the compiler that this is a web service written in C# and implemented in the Helloweb class. The next line,
using System.Web.Services;

allows the program to use the types in the System.Web.Services namespace. (For more on the .NET Framework and namespaces, see Chapter 5, "Reflecting on Classes".)

Tip: The Visual Basic equivalent to using in C# is import. The next line of code adds an optional attribute to the class that implements the service:
[WebService (Namespace="")]

The WebService attribute allows you to set the default namespace for the web service. If you don't set this, ASP.NET will default the namespace to the URI and will display a message when you open the web service test page suggesting that you rename the default namespace.

URIs and URLs You should know that the default namespace is a URI (Uniform Resource Identifier) rather than a URL (Uniform Resource Locator). There's no expectation that a user can click the URI and gain access to a resource (as opposed to a URL, which does work this way), but it should a unique string and-if it's a domain-be under your control. In other words, a URI is for identification, not navigation (although if the URI is a URL, it can also be navigated to).

Next comes the Helloweb class declaration. Everything within the curly braces will be part of the class:
public class Helloweb { }

The [WebMethod] directive says that the method coming next is exposed as a web service. The C# method declaration names the method and says that its return is a string (once again, everything within the curly braces is part of the method):
public string HelloWebService() { }

The string literal following the return keyword is, of course, the value returned by the method. Here's the complete Helloweb class:
public class Helloweb { [WebMethod] public string HelloWebService() { return "Hello, Web Service!";

} } The Battle of the Curly Braces As you probably know, although C# is case sensitive, it is not white-space sensitive, meaning you can lay your programming statements out any way you'd like, even across multiple lines.

A statement of code is ended when you reach the delimiter, a semicolon (;), no matter how many physical lines it takes. Similarly, curly braces ({}) are used to mark the beginning and end of constructs such as namespaces, classes, and methods.

Since you are allowed to position these curly braces any way you'd like, you should aim to do so for readability. Which brings us to one of the greatest controversies of modern life: do you place the opening brace on the same line as the declaration, like so:

public class Class1 { // blah blah }

or do you position it below the initial character of the declaration, like this:

public class Class1 { // blah blah }

The two are syntactically equivalent, and you'll find both styles in the code examples in this book. My personal preference is to do it the first way, as I think it helps to make really clear what is inside the constructbut Visual Studio, and .NET auto-generated code, position the opening brace on a new line.

Deploying the Web Service
Deploying the web service is a simple matter of making sure that the file it is in has an extension of .asmx - the file extension for ASP.NET web services - and opening it in Internet Information Services (IIS). ASP.NET will automatically take care of compiling it.

Note: As you may know, you can also use the C# command - line compiler to compile C# programs created in a text editor. The C# compiler, csc.exe, which ships as part of the .NET Framework, can be found in a folder beneath \Windows\Microsoft.NET\Framework. C# command - line compiler options can be found by searching Visual Studio .NET's online help for C# Compiler Options. With our sample text editor web service in a file named helloc.asmx, the next step is to use the IIS administrative tools to create a virtual directory that points to it. It's pretty standard - but not required - to put ASP.NET (and ASP.NET web service) application files in directories below \Inetpub\wwwroot. In this example, I'll put helloc.asmx in C:\Inetput\wwwroot\SybexC1. The next step is to configure IIS to provide a virtual directory to point to this location. To do this, open the IIS administrative application (which is shown in Figure 1.2) by using the Start menu to select Control Panel > Administrative Tools > Internet Information Services. Click to expand the local computer and Web Sites icons, shown in the left pane of Figure 1.2, and select Default Web Site.

Figure 1.2: The IIS administrative application is used to configure your local instance of IIS.

Note: Internet Information Services is called 'Internet Services Manager' in Windows 2000. Choose Action > New > Virtual Directory, and the Virtual Directory Creation Wizard will open. You must designate the virtual directory with an alias (Figure 1.3) - for example, SybexC1. The alias is used as part of the URL to access the web application - for example, http://localhost/SybexC1/helloc.asmx.

Figure 1.3: A virtual directory is givenan alias.

The next panel of the wizard is used to point at the path to the actual content referenced by the virtual directory (Figure 1.4).

Figure 1.4: The directory that contains the contents is selected in the wizard.

When the wizard is complete, and the virtual directory has been created, you are ready to compile the web service and open it in your browser.

Testing the Web Service
Using the virtual directory you created, and the web service file name, as the URL - for example, http://localhost/SybexC1/helloc.asmx - open the web service in your browser.

Opening the service automatically compiles it. Note: If you need to make changes to the code, simply edit the text file and save it. The next time you reopen it in your browser, it will be automatically recompiled. You'll see a page like the one shown in Figure 1.5, displaying the web service class and the members that it exposes.

Figure 1.5: A page for the web service is displayed in the browser.

Note: that the web service, itself, is just a class with members (here, the only member of class Helloweb is the HelloWebService method). The pages displayed in Figures 1.5 through 1.7 are created around this class on the fly by ASP.NET. Click the HelloWebService link shown in Figure 1.5 (which, of course, corresponds to the HelloWebService method in the class). The next page (shown in Figure 1.6) allows you to test the web service method using HTTP GET by clicking Invoke.

has been returned.7: A new window displays the web service response to the HTTP GET. A new browser window will open that displays the XML response to the HTTP GET (Figure 1. 'Windows Uses Web Services. You'll see that the appropriate string . when the consumption of a web service is embedded in a web or Windows application (as explained in Chapter 2 and in Chapter 3. It's so much easier to unleash the power of .'Hello.6: You can test the web service method by clicking Invoke. Figure 1. Web Service!' . Note: Obviously. Too!'). Creating an ASP.NET Web Service in Visual Studio The example in the previous section is the one and only example you'll see in this book that creates code by hand in a text editor.Figure 1. users don't get to see the raw XML returned from these test pages.NET using Visual Studio .7).so why not go for it? .

Opening the New Project Dialog To open the New Project dialog. delete everything following the web server URL (which is most likely http://localhost/). When the new project opens. Still in the New Project dialog.asmx file appended) and that Visual Studio . With the New Project dialog open. Now add your own project name following the web server URL for example.To create a web services project in Visual Studio .8: To start a new web services project. select ASP. select Visual C# Projects in the Project Types pane. select Help > Show Start Page.NET using C#.NET has created a folder for the project under the default website location. Alternatively. Next.8). . the designer for the web service (ASMX) module will be displayed (Figure 1. select ASP. most likely \Inetpub\wwwroot. Click OK to create the project. meaning that this is the virtual URL that will be used to open the service (with the . in the Templates pane. click the New Project button on the Get Started tab of the Visual Studio Start page.9). SybexC2. in the Location box. The Location box should now contain http://localhost/SybexC2. If the Start page is not displayed. open the New Project dialog.NET Web Service as the project type (see Figure 1.NET Web Service as the project type. select File > New > Project. Figure 1.

dandy thing indeed. however. To see all the files in the project. such as a Timer. as opposed to a control.Figure 1.they consist of class members created in code. to navigate between these modules. are not visual .10). When you are building a Windows or web application. Most of the time the designer is not used with web services. You may also need to expand the nodes in Solution Explorer that are denoted with a plus icon. and to open tools that interact with these modules. But this really doesn't buy you much bang for your buck. the designer for the web service module is displayed. not having a visual interface at runtime). If you don't see Solution Explorer. click the Show All Files button in the Solution Explorer toolbar (Figure 1. .which is one of the reasons why I started a book about programming C# with web services: it lets me focus on the coding. It's true that you can drag a component. open it by selecting View > Solution Explorer. Web services. We won't be needing it.9: When the new project is opened. Solution Explorer Visual Studio's Solution Explorer is used to view the different modules that are parts of projects and solutions. because the act of creating a web service means the creation of classes in code . because you can visually drag and drop controls from the Toolbox onto the designer and have an instance of the control automatically instantiated. Many of the files that make up the web services project are now displayed in Solution Explorer. the designer for the Windows or web form is a very handy . So close the designer. onto the designer and have the code that instantiates it automatically generated (a component.

using System. Listing 1.cs code module. you can right . namespace SybexC2 { /// <summary> /// Summary description for Service1.Figure 1.2 shows what you'll find in the ready .asmx.NET Web Service Code Module using System. Warning: The buttons displayed on the Solution Explorer toolbar depend on the project module selected.Web. using System. using System. Alternatively. or just hit the F7 key.Services. Listing 1. using System.made web service code module.Diagnostics. /// </summary> . We're primarily interested in the SybexC2. using System.Data. using System.Collections. select it and click the View Code button in Solution the file and choose View Code from its context menu. which is the "out of the box" web service that this project will deploy.10: The files that make up a project are displayed in Solution Explorer.2: An 'Out of the Box' ASP.Web. To view the code contained in this file.ComponentModel.

/// <summary> /// Required method for Designer support .Dispose(disposing).Services. /// </summary> protected override void Dispose( bool disposing ) { if(disposing && components != null) { components. } #endregion // WEB SERVICE EXAMPLE // The HelloWorld() example service returns the string Hello World // To not modify /// the contents of this method with the code editor. press F5 // [WebMethod] // public string HelloWorld() // { // return "Hello World".Web. /// </summary> private void InitializeComponent() { } /// <summary> /// Clean up any resources being used. } base. uncomment the following lines then save and build the // project.Dispose(). .WebService { public Service1() { //CODEGEN: This call is required by the ASP.public class Service1 : System. } #region Component Designer generated code //Required by the Web Services Designer private IContainer components = null.NET Web Services Designer InitializeComponent(). To test this web service.

As you can see in Figure 1. the code on the line of the #region directive. C# also supports so . you should know that lines beginning with two forward slash marks (//) are comments (this is sometimes known as C++ comment style). #region Component Designer generated code is displayed. If you are not familiar with C# at all. I'll provide an overview of how this works towards the end of this chapter.11. For example: /* I am a comment! */ You should also know that the code between #region and #endregion directives does not display in the Code Editor (unless the region is expanded by clicking on the plus icon at the left side).// } } } We're not going to go through this in detail. but any subsequent code up to #endregion is hidden in the collapsed comments.called C . . They are designed to contain XML documentation of the code.but of a special sort. which begin with /* and end with */. although you should have a general idea of what you are likely to find when you open one of these modules. which can be automatically rendered into documentation for programs. Lines beginning with three forward slash marks (///) are also comments . Everything between the begin and end marks is a comment. which can span multiple lines. not shown in Listing 1.

. The project will be built. you can select Debug > Start Without Debugging (Ctrl+F5 is the keyboard shortcut) and the page will load faster than in debug mode. as shown in Figure 1. Note: If you're not planning on doing any debugging. Web Service!' The replacement boilerplate web service looks like this: [WebMethod] public string HelloWebService() { return "Hello. and the ASMX file opened in Internet Explorer.11: The Code Editor doesn't display code within a #region / #endregion block unless it is expanded. start the project in the development environment by choosing Debug > Start (you can.2 with our own 'Hello. } Now let's view the test pages for the web service we've created. alternatively. Let's go ahead and replace the commented .out 'Hello World' web service shown in Listing 1. choose F5 on the keyboard). Web Service!".Figure 1.12. To do this.

Figure 1.12: The test page for the web service is displayed in Internet Explorer.

Note: You can also build the project by selecting Build > Build Solution. The test page can then be opened by URL - in this case, http://localhost/SybexC2/SybexC2.asmx - in your browser. As you can see in Figure 1.12, the name of the service is defaulted to Service1. It would be nice to give it a custom name. We can also add a description of the web service, and we should also change to a URI under our control. To do this, add a WebService directive within the namespace and before the class declaration:
... namespace SybexC2 { [WebService (Description="Chapter 1 demo program", Name="SybexC2Service", Namespace="")] ... public class Service1 : System.Web.Services.WebService { ... [WebMethod] public string HelloWebService() { return "Hello, Web Service!"; }

... } }

While we're having a look at this, you should make note of the declaration for Service1:
public class Service1 : System.Web.Services.WebService

The colon in the middle of the statement says that Service1 inherits from the System .Web.Services.WebService class. Tip: The Visual Basic equivalent of the C# colon operator (:) is the Inherits keyword. As you'll see shortly, you can add multiple methods to a single web service. Each method is denoted using a WebMethod directive. OK. Let's run the revised web service and view its test page (shown in Figure 1.13). You'll see that it now displays a custom service name and description, and that annoying message about is gone.

Figure 1.13: The test page now shows the service name and description.

As a clean - up step, let's get rid of the auto - generated code added by Visual Studio that we don't really need, and delete the references to namespaces - which were also automatically added - that we don't really need. There's no overwhelming reason to do this, other than for clarity - although, of course, the unneeded code and references do consume some resources. Listing 1.3 shows the complete web service code module.

Listing 1.3: The Cleaned-Up 'Hello, Web Service!' Code Module
using System.Web.Services; namespace SybexC2 { [WebService (Description="Chapter 1 demo program", Name="SybexC2Service", Namespace="")] /// <summary> /// Service1 contains demo methods! /// </summary> public class Service1 : System.Web.Services.WebService { [WebMethod] public string HelloWebService() { return "Hello, Web Service!"; } } }

Adding a Class Module
A single web service can, of course, have more than one method - each web method being represented by a method that is a member of the web service class. In addition, you can add whatever classes you'd like to the web service code module to support its web methods. We're going to add three new web methods to our demonstration web service, but we'll take a slightly different tack in constructing them. The web service code module and class will be used only for the actual web method calls - the supporting code will be placed in classes in a separate class module that has been added to the project. To add a class module to the project, open the Add New Item dialog by selecting Project > Add Class. (You can also right - click in Solution Explorer, and select Add > Add Class from the context menu.) In the dialog (shown in Figure 1.14), make sure that Class is selected as the kind of object to add in the Templates pane. You can accept the default name for the class module, Class1.cs, or change it if you'd like.

Figure 1.14: The Add New Item dialog is used to add a class module.

When the new class module is added to the project, it will come 'out of the box' with the code shown in Listing 1.4. Specifically, note that the namespace used in the web service code module has been carried across to the class code module. Listing 1.4: A Boilerplate Class Code Module
using System; namespace SybexC2 { /// <summary> /// Summary description for Class1. /// </summary> public class Class1 { public Class1() { // // TODO: Add constructor logic here // } } }

To start with, we'll replace the nominal Class1 in this module with a class designed to support the web service, named - logically enough - ServiceSupport:
using System; namespace SybexC2 { /// <summary> ///Service Support supports the web services module /// </summary> public class ServiceSupport { } }

Next, let's add a method that uses a get property accessor to return the string "Web services are cool!":
... public class ServiceSupport { static public string ReturnText { get { return "Web services are cool!"; } } ...

There's no earthly reason you'd want to do this in the real world, in two respects: • • A straight method that returns the string makes just as much sense as returning it as a property. You don't need a support class to return a string - why not just return it in the web method as in the earlier example?

We've done it this way to easily demonstrate using the get property accessor. Note that the property as it appears here is read - only; there is no way to set it. It's also important to know that you can invoke the members of another class from a web method class. In this regard, you should note the use of the static keyword - which means that the member is shared so that an object based on the class doesn't have to be instantiated to use the member.

Tip: The Visual Basic equivalent to C#'s static is the Shared keyword. Let's go back to the web service module, and invoke the ServiceSupport ReturnText property in a new web method named HelloWS2:
[WebMethod] public string HelloWS2() { return ServiceSupport.ReturnText; }

You know you are on the right track when you type the class name in the Code Editor, and the IntelliSense auto - completion facility supplies the name of the member you'd like to use.

Running and Testing the Service
To test the new service method, run the project in the development environment. As you can see in Figure 1.15, the new test page that is generated has both web service methods listed.

Figure 1.15: The new test page that is generated has two web service methods.

Click the HelloWS2 link. You can now invoke the method and verify that the specified text string is returned, as seen in Figure 1.16.

Figure 1.16: Clicking the Invoke button (top) produces the appropriate string return (bottom).

Doing the Math
Let's add a method to the ServiceSupport class that does something a little more complicated (we can then expose the method as a web service). This method uses the mod operator (%) and the square root method of the System.Math object (Math.Sqrt) to determine whether an input is a prime number, something that is potentially useful in cryptography and encryption. Note: Note that you can leave the System namespace off when referencing its members: Math.Sqrt is the equivalent of System.Math.Sqrt. Here's the method:
static public bool IsPrime (long NumToCheck) { for (long i = 2; i <= Math.Sqrt(NumToCheck); i++) {

if (NumToCheck%i == 0) { return false; } } return true; }

Unlike the previous example, this method takes an input - the number to check - which is typed as a long integer. (You'll see in a minute where the input shows up on the test page.) The method returns a Boolean value of true or false, as indicated using the bool keyword, depending upon whether the input is a prime number or not. Tip: The Boolean values True and False are always lowercase within C# code: true and false. The capped versions appear in the Visual Studio Properties window and are acceptable in Visual Basic, since VB is case insensitive; but attempting to use "True" or "False" in C# code will cause the compiler to generate a syntax error. A for loop is used to check the potential divisors of the input number up to the square root of the number. If any potential divisor goes into the number evenly (as tested by the mod 0 result), then it is not a prime, and the method returns false. Otherwise, if there are no even divisors, it returns true. Turning to the web service module, here's the web method wrapper that invokes the ServiceSupport.IsPrime method:
[WebMethod] public bool IsPrime (long NumToCheck) { return ServiceSupport.IsPrime(NumToCheck); }

If you run the project, you'll see that the IsPrime web method has been added to the list of methods available in the service (Figure 1.17).

Figure 1.17: The IsPrime web method has been added to the web service.

If you click the IsPrime link shown in Figure 1.17, the test page for the method opens (Figure 1.18). This is a little different from the previous test pages because it allows you to input a value.

Figure 1.18: The IsPrime test page lets you input a value to be checked and then displays the value returned by the method.

In Figure 1.18, a relatively large prime number is shown input for testing. When the Invoke button is clicked, the return value of true indicates that it is a prime and that the method is working.

Reversal of Fortune
We'll implement a final web method in the service, one that reverses a string that is input. The implementation of this in the class module will be a bit different from the previous two examples. First, it will be implemented in a class of its own named StringManipulation, rather than as part of the ServiceSupport class. Next, the StringManipulation class members will not be declared using the static keyword, meaning that they won't be shared, so an object based on the class will have to be instantiated to use the class members in the web service module. The StringManipulation class implements a read - write property, Text, and a method, ReverseString. To use these members, the Text property must first be set with the value to be reversed. The Text property is then operated on by the ReverseString method, which takes no arguments. The changed string value is then retrieved from the Text property. Here's the class declaration and the implementation of the Text property:
public class StringManipulation { private string m_text = ""; public string Text { get { return m_text; } set { m_text = value; } } ... }

The value of the property is stored internally in a private variable, m_text, which is read and written with the get and set accessors associated with the public Text property.

The ReverseString method doesn't return a value; this is indicated by the void key - word. The method starts by declaring, and initializing, a new string variable, strNew. The keyword this is used to refer to the current instance of the object, so that this.Text is the value of the Text property. Tip: The Visual Basic equivalent of the C# keyword this is the Me keyword. Some built - in methods of the string class, Length and Substring, are used within a for loop that is iterated as many times as there are characters in the Text property. As each character at the front of the string is peeled off, it is added to the end of strNew using the concatenation operator (+). (For more on string manipulation in C#, see Chapter 9, "Everything Is String Manipulation.") Finally, after the loop is complete, strNew is assigned as the new value of the Text property:
public void ReverseString() { string strNew = ""; for (int i = 0; i < this.Text.Length; i++) { strNew = this.Text.Substring(i,1) + strNew; } this.Text = strNew; }

Let's use the StringManipulation class within the web service module to create a new web method. The first step is to declare and instantiate a StringManipulation object using the new keyword, stored in the variable SM:
StringManipulation SM = new StringManipulation();

The rest is straightforward. Following the [WebMethod] directive, declare a method ReverseString that takes a string as input and returns a string. Within ReverseString, using the string input, set SM.Text and invoke the parameter - less SM.ReverseString method. Finally, return the value of SM.Text:
[WebMethod] public string ReverseString (string inString) { SM.Text = inString; SM.ReverseString();

as shown in Figure 1. Listing 1.6. and the code for the class in module in Listing 1. you'll see that the ReverseString method has been added to the Web service.19: The ReverseString method has been added to the web service. To wrap this all up. you'll find the complete code for the web service module in Listing 1.return SM.5: The Complete Web Service Code Module using System.5. } If you run the project.Text. If you click the ReverseString link. as long as you don't enter a palindrome (which is the same backward and forward)! Figure 1.Web. you can enter text and verify that is returned reversed. namespace SybexC2 { .that is.Services.19 .

6: The Complete Class Module using System. } [WebMethod] public string HelloWS2() { return ServiceSupport. } [WebMethod] public bool IsPrime (long NumToCheck) { return ServiceSupport. SM.Web. Web Service!". } StringManipulation SM = new StringManipulation().ReturnText.Services.Text.Text = inString. [WebMethod] public string ReverseString (string inString) { SM. Namespace="http://www. .WebService { [WebMethod] public string HelloWebService() { return "Hello.[WebService (Description="Chapter 1 demo program".IsPrime(NumToCheck). Name="SybexC2Service".ReverseString().bearhome. return")] /// <summary> /// Service1 contains demo methods! /// </summary> public class Service1 : System. } } } Listing 1.

Text. i++) . i++) { if (NumToCheck%i == 0 ) { return false. } } return true.Length.namespace SybexC2 { public class ServiceSupport { static public string ReturnText { get { return "Web services are cool!". } } static public bool IsPrime (long NumToCheck) { for (long i =2. for (int i = 0. i <= Math. } set { m_text = value. public string Text { get { return m_text. } } public class StringManipulation { private string m_text = "".Sqrt(NumToCheck). i < this. } } public void ReverseString() { string strNew = "".

If you insert some simple XML documentation tags as you go along.{ strNew = this.20).Text. For a complete list. provide a value for the XML Documentation File property (Figure 1. . see "Tags for Documentation Comments" in online help. /// and writes it back to the Text property /// </summary> This summary will then appear in the generated documentation file. select Configuration Properties. an XML documentation file with the name specified will be created. With the property pages for the project open. } this.Text = strNew. } } } XML Comments I mentioned earlier in this chapter that a special kind of comment is indicated with C# code by three forward slashes at the beginning of the line (///). and choose Properties from the context menu. This is the answer to a common problem: no one ever has time to document code until it is too late. you could comment the ReverseString method discussed in the preceding section as follows: /// <summary> /// The ReverseString Method of the StringManipulation class /// reads the class instance Text property value. along with listings of classes and members. For example. These comments can be used to automatically generate XML documentation for a program. reverses it.1) + strNew. you won't have to worry about this. The next time the project is built. To generate an XML documentation file from these tags. right click.Substring(i. provided the XML used in the tags is well . select the project in Solution Explorer. On the Build page.formed. Two of the most commonly used tags are <summary></summary> and <remarks> </remarks>.

20: You can easily auto . select Tools > Build Comment Web Pages.21: You can generate a Code Comment Web Report based on the XML tags. You can also easily generate a Code Comment Web Report based on the XML tags in your project. The only thing done with them has been to verify that they work using the test pages generated on the fly by the .to create ASP.and very easy .Figure 1. Figure 1.consumed . .in the context of ASP.both synchronous and asynchronous . To do this.21).NET web applications.based programming in C#. I've introduced you to some important concepts involved with class . Let's move on to web service consumption . Conclusion As you've seen in this chapter.NET web services using C#.NET any way.generate an XML documentation file in the location specified. But the web services in this chapter haven't been used . it's fun . The result is a set of HTML files viewable from within Visual Studio or from a browser (Figure 1. Along the way.

side web applications work. an asynchronous call to the web service method allows other programming tasks to be performed while waiting for the response.) A web browser uses HTTP. In contrast. The final part of the chapter is concerned with discovering and using web services created by others. The first theme is using Visual Studio and C# to create ASP.consumed? Since these web services are implemented as methods. Understanding ASP. they may allow . These custom tags and server . the typical web scenario uses custom tags and script commands embedded in server .NET applications • Consuming web methods • Synchronous consumption • Asynchronous consumption • Finding services This chapter is a tale of two themes. When anything beyond static HTML is required. such as Cold Fusion.Consuming the Service on the Web Overview • Understanding ASP. we'll examine the difference between synchronous and asynchronous calls to the service method IsPrime developed in Chapter 1. The chapter introduces you to the key concepts you need to create an ASP. to request the server . For example. how are the web services that were created in Chapter 1 . and earlier versions of ASP [Active Server Pages]. The second theme picks up where Chapter 1 left off: How do you consume a web service in a web application? Specifically. this boils down to the question of how to remotely invoke a web service method. (There are many examples of this approach.side scripts perform many functions.NET • Creating ASP.side HTML pages.reversing a string and determining whether a number is a prime . Along the way. which checks to see if a number is prime.side 'page' that includes the custom tags. normally via a form POST or GET. JSP. A synchronous call to this method waits for the response back before the calling program can do anything else.NET web application.NET Let's start with the basic question of how server .NET web applications (sometimes called 'web forms' projects).

This means that if you look at the source code in a browser. you will not see the server . nothing has changed. old HTML that gets sent to the browser. In other respects. Internally. Softwaresometimes called an application server . . mutatis mutandi.NET approach is. in some sent back to the browser. the ASP. Figure 2.that is connected with (or part of) the web server expands these custom tags on the server side. reads and writes to the database. the same as it ever was. although you will probably see form POSTs and GETs to server .probably including client . and returns straight HTML via HTTP to the web browser.aspx file extension . An ASP.side 'page' generally occurs in a top .aspx page (which might be itself).database connectivity or invoke a server .NET application is invoked by opening an ASP. It's still the case that all the real action happens on the server side and that plain.side code module such as a control.1: Generically. The general arrangement is shown in Figure 2.side page that includes custom tags and programmatic commands is invoked using an HTTP request and returns straight HTML over HTTP to the browser.1. a server .over HTTP.which has an . This is all.side programs written in a scripting language such as JavaScript . When it is complete.down linear fashion.NET page interacts programmatically with the server Internet Information Services (IIS) .side using an HTTP form GET or POST to request an . a new set of 'straight' HTML . radically different from this traditional approach.side scripting tags or language. Processing of the server . You can verify this by viewing the sources in a browser such as Internet Explorer or Netscape Navigator. The ASP.NET web form page .

and events that can be used to modify the appearance and behavior of the page in the browser. which becomes a web page in the Internet Explorer browser when the compiled project is run .NET Web Application project has one web form. In the past. Too!') Just like Windows forms. which is actually an executable that outputs HTML to the client.side interface has been mixed up with the server .2.behind module. ASP. • • The last point is extremely important to creating applications that are maintainable. The development of an ASP.side code in the related code . flow control is organized around an event model. I'll show you how to use Visual Studio to build a Windows application in Chapter 3. the HTML form commands in the original project are processed on the server by a custom application.although you can add as many pages as you'd like to a project.NET and the . A complete separation of the HTML (and other client . what happens is that by invoking an . web forms have properties.down page processing. (If you don't already know.NET Framework: • A compiled program is created. Essentially. the client .side) content from the serverside programming has been effected. an ASP.side programming logic in a way that made maintenance a nightmare. A web form represents a web page in the browser in the same way that a Windows form represents an application window on the Desktop. Within the compiled program. 'Windows Uses Web Services.NET web application can be represented as shown in Figure 2. . and not limited to top . By default.NET programs are created using Visual Studio . with the HTML placed in the ASPX file and the server . methods.aspx file. as I'll explain later in this chapter.The radical difference is in the way ASP.NET web applications are built around web forms in the same way that Windows applications are built around Windows forms.

or Windows XP Professional. is not running locally-the server needs to have the .Figure 2.) The Windows 2000 and Windows XP software ship with current versions of IIS.NET development environment (the default web server is designated using the URL http://localhost/. and your installation of Visual Studio .NET should have automatically configured it correctly to work with ASP. Requirements for Running an ASP.NET Application In order to run web forms (ASP.NET applications are compiled programs that return straight HTML.) Note: that if your web forms application is deployed on an external server running IIS-in other words.2: ASP. You should have no problems running web forms applications from the Visual Studio . . (In order to install Visual Studio .NET applications.NET) applications.NET. you should be running Windows 2000 Server or Professional. you'll need to have Internet Information Services (IIS) version 5 (or later) and the FrontPage Server Extensions.NET Framework installed and the FrontPage Server Extensions configured.

a properly formed file that is named using the .As you'll see later in this chapter.NET application.NET Web Application To start a new web application.NET applications. name the project using the Location box. you can always open it by choosing Help > Show Start Page. While it is a great development environment. In Figure 2. you don't have to use Visual Studio to create an ASP. Just as with the web service created in Notepad shown in Chapter 1. choose selecting Debug > Start.asmx extension. your new Web Application project will be created in a folder named SybexC3 located in the virtual root folder for the localhost web server. the location is shown as http://localhost/SybexC3. and the project name becomes SybexC3. and the resulting ASP. In the New Project dialog.aspx suffix will be compiled when it is first opened in a browser.3: To create a web application.3. The part of the location entry following the server name (which is typically http://localhost/) becomes the project name. or F5 on the keyboard .NET Web Application from the right pane as the project template. which is normally \Inetpub\wwwroot. As you'll notice in Figure 2.or by invoking a web form (ASPX page) using the project's virtual URL in a browser: for .3. the Name box is disabled.NET Web Application in the New Project dialog. When you click OK. choose ASP. open the New Project dialog either by selecting File > New > Project or by clicking the New Project button on the Visual Studio Start Page.3. there are sometimes reasons to hand . The project can be run through the development environment . as shown in Figure 2. which was saved in a file with an . If you don't see the Start page. Creating an ASP.NET application will generate HTML to be rendered in the browser. Figure 2. With Visual C# Projects selected in the left pane as the Project Type.craft ASP.

aspx.4 to open the HTML editor). this is named WebForm1 and is saved with an . Note: By default. Note: Note that if you rename WebForm1. http://localhost/SybexC3/WebForm1.cs suffix (WebForm1.example. (If Solution Explorer does not appear. click the Show All Files button in the Solution Explorer's Toolbar and expand the .left of the designer in Figure 2.aspx). By default. which is a file with the same name as the web form and an additional . you can open it by selecting View > Solution Explorer.aspx file node.behind file does not appear in the Solution Explorer.4.cs).behind files then appear "under" the ASPX file in the hierarchal view in the Solution Explorer.aspx. a web form's code .aspx.) Figure 2.4: The new Web Application project features a web form.aspx to Default. a Visual Studio project containing a web form has been created.behind file. This web form provides a precise layout grid that can be used to host controls (as you'll see in the "Adding Controls to a Web Form" section later in this chapter) as well as an integrated HTML editor (click the HTML tab shown in the lower . To display the code . . you can place C# code related to the form in the connected code module. for reasons you'll see shortly. The code . In addition. The Default Document feature is set using the Documents tab of the website properties dialog in the Internet Information Services administrative utility. Depending on how you have set your development environment preferences.aspx suffix (WebForm1. is also called the code . it can be opened using the URL http://localhost/SybexC3/ (without mentioning the page name explicitly). which shows both a web form and the project files in Solution Explorer. it will look more or less like Figure 2.behind file in the Solution Explorer. Web Forms What has happened so far? First. The connected code file.

ComponentModel.UI.OnInit(e).EventArgs e) { // Put user code to initialize the page here } #region Web Form Designer generated code override protected void OnInit(EventArgs e) { // // CODEGEN: This call is required by the ASP.Drawing.SessionState. using System.behind module for a C# web form. base.Web.HtmlControls.Web.UI.Web. // InitializeComponent().NET Web Form Designer. } /// <summary> /// Required method for Designer support . using not modify . using System. using System.Page { private void Page_Load(object sender.Collections.Web.1 shows what you get 'out of the box' in the code . namespace SybexC3 { /// <summary> /// Summary description for WebForm1. /// </summary> public class WebForm1 : System.Data.1: 'Out of the Box' Web Form Code-Behind Module using System.Web. using System.Web. using System. using System.generated region has been expanded and included in the listing. using System. System. using System.UI.WebControls. (Note that the hidden code in the Web Form Designer .Listing 2.) Listing 2.UI.

You can use this tab of the designer to add a custom title to the web page that will be created: <title>Reverse those strings today!</title> and a header in the body of the HTML: <h1>Reverse your strings today!><br> </h1> Figure 2. To start setting this up.1).Page_Load).reversal web method created in Chapter 1.5)./// the contents of this method with the code editor. } #endregion } } Once again. we are not going to go through this in detail.EventHandler(this.NET web form using the HTML tab of the designer.side HTML (Figure 2. It will provide a web interface that will reverse a string entered by the user.2) and default Windows form module (Listing 3. although you may find it instructive to compare this 'out of the box' web form with the equivalent default web service (Listing 1. .Load += new System. /// </summary> private void InitializeComponent() { this. let's first click the HTML tab of the WebForm1 designer and have a look at the server .5: You can add HTML to your ASP. The SybexC3 project is intended to demonstrate invoking the string .

As you likely know.behind module.6. you can open it by selecting View > Toolbox).You should also note the @Page directive at the top of the designer.NET page parser and compiler . Going back to the Design tab. shown in Figure 2. . is used to add controls to a form (if the Toolbox is not displayed.designating. for example. you'll see that the HTML header appears: Adding Controls Now it's time to add the user interface. The attributes of this directive have important implications to the ASP. the language used and the name of the code . the Toolbox. which we'll create using five controls.

Area to input the string to be reversed. Figure 2. Use the Web Forms tab of the Toolbox to add the controls shown in Table 2. shown in Figure 2. Label TextBox Button lblResult txtInString btnRev RequiredFieldValidator RequiredFieldValidator1 Makes sure that the user inputs something.7: Set the Text property of the Label control.1 with their intended purpose. and set the Text property of Label1 (the default ID for the first Label) to "Enter text to reverse:". Table 2-1: Controls on the SybexC3 Web Form and Their Purpose Control Type Label ID Label1 Purpose Displays static text (no functional purpose).Figure 2.6: The Toolbox is used to add controls to a web form. . Displays the results of the string reversal. Note: Web form controls are identified by their ID property. Open the Properties window (by selecting View > Properties window). Starts the reversal process when the user clicks. the Properties window is used to set the properties of objects such as controls (and forms). rather than the Name property used with Windows form controls.7. As you most likely know.

Using the Properties window.side scripts to do this. Your web form should now look pretty much like the one shown in Figure 2. use the drop . meaning that you don't have to write client . set the ID of the remaining controls as indicated in Table 2. The RequiredFieldValidator is one of several controls that can easily be used to validate user input.8).1. Figure 2. Change the Text property of btnRev to "Click to reverse!".down list to set the ControlToValidate property to txtInString and supply some text for the ErrorMessage property (Figure 2.9: The finished web form . With RequiredFieldValidator1 selected in the Properties window.9. Clear the Text properties of lblResult and txtInString. Figure 2.8: The RequiredFieldValidator control makes it a snap to make sure that the user has entered something in the TextBox.

first open the Visual Studio . The code module provides classes that enable both synchronous and asynchronous calls to the methods exposed by the web service. rather confusingly. and adding web references is somewhat easier. at the prompt type wsdl /? and press Enter. For example: .NET Command Prompt window. To create a proxy.generation techniques you choose.exe (you'll certainly see it described.NET for asynchronous invocations. which communicates with the web service. so that's what we'll be using in the remainder of this book when it's necessary to auto . Additionally.line utility and adding a web reference to a project (you could also write one from scratch .) You should know about wsdl. (You'll find it on your Windows Start menu under Microsoft Visual Studio .generating this proxy.line switches available with the utility. I'll show you the general design pattern favored by Visual Studio . In order for our ASP.Adding a Proxy to the Web Method A proxy is a program that does something on behalf of another program.generate a web service proxy. Calling the web method synchronously is more straightforward. followed by the language choice (CS for C# and VB for Visual Basic).NET > Visual Studio . we need to create a proxy program that handles the job of communicating with the web service containing the web method (Figure 2.exe. I'll show you how to call the IsPrime web method both synchronously and asynchronously. so that's what we'll do with the string . wsdl.10). There are several ways of auto . Figure 2.NET web application to consume the ReverseString web method. enter wsdl. and then the URL of the web service with ?WSDL appended. But both techniques accomplish the same thing.reversal web method.) To see all the command .10: The Visual Studio program invokes a proxy. a code module will be created by parsing the WSDL file that describes the interface provided by the web service. in books about web services). Using the WSDL Utility To invoke the WSDL utility. (Later in this chapter. including using the Web Services Description Language (WSDL) command . Whichever one of the auto .NET Tools.not necessarily a huge job once you've looked at a few of them). in Chapter 3.

Since the contents of the proxy module are the same whether it is created this way or by adding a web reference to a project. The code module will now appear in Solution Explorer. and click Open. One step remains if you want to use this auto .asmx?WSDL Note: If you don't specify a file name and location. The Add Web Reference dialog will open (note that the Add Reference button is disabled).or.asmx . choose the proxy file from the file system.generated proxy code module with a Visual Studio project: you must add it to the project. If the proxy has been successfully created. Select Project > Add Existing Item. you can enter the URL for a remote web service . Adding a Web Reference The other way of adding a proxy to a project is to select Add Web Reference from the Project menu.wsdl /language:CS http://localhost/Sybexc2/SybexC2. the file name is created based on the web service at the current location. the local web service created in Chapter 1. In the Address box at the top of the dialog. a banner is returned by the WSDL utility containing the name of the file. we'll wait until a little later to have a look at its contents. http://localhost/SybexC2/SybexC2. in our case.

11: To add a web reference.generated documentation for the service appears in the dialog.generated proxy code module to the current project. and the Add Reference button is enabled (Figure 2.With the URL entered. (There was no need to specify a language to use . enter the URL for the web service in the Address box at the top of the Add Web Reference dialog.11). the Visual Studio . If you look in Solution Explorer. . you'll see that a new Web References section has been added to the project. The web reference that was just added has been named localhost by default. Click the Add Reference button to add the auto . Figure 2.) You'll have to expand the Web References section to really see what's going on.Visual Studio used the language of the current project by default.

It's easy to change the name of the web reference to something that might be more to one's taste (or more specific) than localhost. Use this when the web service has changed and you want to update the . Note: It's important to know about the Update Web Reference choice.clicking the localhost reference and selecting Rename from the context menu. Go ahead and change its name to the ever .popular sobriquet theService by right . which is also available on the context menu for the reference.

using System.0.Web.0.ComponentModel.Serialization. which we'll be using in this example.3705.Web. // Version 1. there are BeginReverseString and EndReverseString methods designed for asynchronous calls.Diagnostics.theService { using Listing 2.3705. but if you expand a level below the reference now named theService you'll see a module named Reference.0 // // Changes to this file may cause incorrect behavior and will be // lost if the code is regenerated.Xml. Listing 2.2 shows the portions of this proxy code module that are relevant to the ReverseString web method. using System.Services. the proxy code module file.2: Selected Portions of the Auto-Generated Proxy Code Module //--------------------------------------------------------------------// <autogenerated> // This code was generated by a tool. The proxy code module itself is kind of buried.cs. using System. . Primarily. namespace SybexC3.0.Map is Reference.VSDesigner. One node below Reference. using System.proxy.Services. // </autogenerated> //--------------------------------------------------------------------// // This source code was auto-generated by Microsoft.Protocols. // Runtime Version: 1. using System. note that in addition to the ReverseString method.

SoapBindingUse.Services.Web.asmx". new object[] { inString}).Literal. [System.WebServiceBindingAttribute(Name="SybexC2ServiceSoap".Protocols.Wrapped)] . ParameterStyle =". Use=System. Namespace="http://www. Use=System.bearhome.Protocols.Services.Web. } /// <remarks/> public System.Description. ResponseNamespace="http://www.SoapDocumentMethodAttribute ("http://www.bearhome.bearhome.DesignerCategoryAttribute("code")] [System. ParameterStyle =")] public class SybexC2Service : System.bearhome.DebuggerStepThroughAttribute()] [". return ((string)(results[0]))..Protocols.Services.Services.SoapBindingUse. ResponseNamespace="http://www. RequestNamespace=""".Web..Wrapped)] public string ReverseString(string inString) { object[] results = this.Protocols.bearhome.SoapDocumentMethodAttribute ("http://www.Services. } /// <remarks/> [System.Invoke("ReverseString". .Web.IAsyncResult BeginReverseString(string".Url = "http://localhost/SybexC2/SybexC2.bearhome.Literal./// <remarks/> [System. RequestNamespace="".SoapHttpClientProtocol { /// <remarks/> public SybexC2Service() { this.Services.Diagnostics.SoapParameterStyle.Web.Web.Web.ComponentModel.

let's get on with it and wire up that form already! To do so. When you double .because the code in the application will respond to the user's click to process the string that is to be reversed. asyncState). you can open the code .EndInvoke(asyncResult).cs. The easiest way to do this is to double . and you are taken to it in the Code Editor: private void btnRev_Click(object sender. callback. In other the Button in the Web Form Designer.System. you'll also find a line of code added to the hidden region of the module that registers the event handler: .aspx. object asyncState) { return this. return ((string)(results[0])). the Button control.behind module is WebForm1. and manually create the event handler.behind module in the Code Editor in other ways. since this is a book about programming.BeginInvoke("ReverseString". new object[] {inString}. the following method is created. %> directive on the HTML tab of the Web Form Designer.AsyncCallback callback. it also creates the scaffolding for the method that handles the Button's click event.) Note: If the web form is named WebForm1. we need to open the code behind module in the Code Editor and create the method that handles the click event for the Button control . } } } The Code Behind It always comes down to the code behind . You can change the related code .behind module by changing the value of the Codebehind attribute in the <%@ Page . (Of course..EventArgs e) { } By the way.behind module in the Code least in this book.aspx.IAsyncResult asyncResult) { object[] results = this. if you prefer.. the default name for the code . Not only does this open the code . } /// <remarks/> public string EndReverseString(System.

ToUpper())) { return false.EventArgs e) { theService.Click += new System. } As you can see. } if (String1.has been entered. } Note: The += operator allows new event handlers to be added to the Button's click event without destroying methods that have already subscribed to an compares the two strings input.btnRev. Within the event handler.btnRev_Click). System. create an instance of the web service stored in the variable theService by referring to the class contained in the proxy code module: private void btnRev_Click(object sender.ReverseString(txtInString. result: string result = theService. Here's the function that checks whether the input is a palindrome: public bool Palindrome(string String1. we're going to check to see whether a palindrome . } Next.SybexC2Service theService = new theService. return the reversed value of the string entered by the user in a new string variable. both . in case some user wants to make fun of this application. } return true.private void InitializeComponent() { this.ToUpper() == expression that is the same backward and forward . string String2) { if (!(String1.Text).SybexC2Service().EventHandler(this.Length == 1) { return false.EventHandler(this. Just to make things a little more interesting.Page_Load). this Palindrome function is pretty basic stuff . this.Load += new System.

"Everything Is String Manipulation!" If you check Listing 2. and returns false if they are not equal. It also returns false if the first string is only one character long.aspx).for example. With the web application running.12. as shown in Figure 2.Text)) { lblResult. If it is a palindrome. you can enter a string.converted to uppercase to side . Back in the click handler. } That's it! It's time to build and run the project by selecting Debug > Start or by building it (Build > Solution) and opening it by URL in your browser (http://localhost/ SybexC3/WebForm1.Text = "Don't even think of giving me a palindrome!". and if it's not a palindrome. } else { lblResult. you'll note that the Palindrome function is placed within the same class (WebForm1) as the click event handler. Figure 2. the Palindrome function is called with the return value from the web service and the input text as the arguments. stripping out punctuation and white space from consideration.txtInString.12: The application invokes the web service in the code - . and verify that it gets reversed. an admonition is displayed. It's possible that a true palindrome check might want to get more elaborate . the reversed text is displayed using lblResult's Text property: if (Palindrome(result.3.step differences between uppercase and lowercase letters. You'll find more information on relevant techniques in Chapter 9.Text = result.

. select View > Source.UI.side JavaScript). it's worth viewing the source that the browser is rendering to verify that it is straight HTML (and some client . You can also try entering a palindrome to see whether it gets past the Palindrome function (Figure 2.which is how I normally intend to present code in this book.13: The palindrome filter seems to work.13).Web.3: Non-Auto-Generated Portions of the Code-Behind Module for ReverseString .EventArgs e) { theService. With the application running in your browser. public class WebForm1 : System. Listing 2.generated .generated portions...SybexC2Service theService = new .behind module for the application.. To view the source in Internet Explorer. namespace SybexC3 { ...behind module to reverse the string. private void btnRev_Click(object sender. Figure 2. after first showing the auto .Page { . System. Listing 2. complete except for the portions of code that are auto .3 shows the code .

In the same fashion.Text = "Don't even think of giving me a palindrome!". however. there's a tangent we have to take (actually. string String2) { if (!(String1. one of several tangents). not the . there should be no particular problem in calling the IsPrime web method synchronously. More interestingly. its primeness . which. } } } Consuming the IsPrime Web Method Next on our agenda is the IsPrime web method. } else { lblResult. Well. I'll show you how to call the IsPrime web method asynchronously.Text = result. string result = theService. we're going to play a game that's a wee bit different. is part of the same web service as the previous example (SybexC2Service) that I showed you how to create in Chapter 1. so that your program can do other things while waiting to learn whether the number passed to IsPrime is or isn't a prime. Once you have the proxy built and added to the project.Text). passing it a long integer to evaluate for primality (that is. I'll show you how to do this (it doesn't particularly differ from working with the ReverseString method). calling a web method is just like calling any other method. Here.txtInString.ToUpper() == String2. and displaying an appropriate message depending on the result of the method call. } if (String1.Length == 1) { return false.theService.ReverseString(txtInString. it's what you learn along the way that counts. } } public bool Palindrome(string String1.SybexC2Service().whether it's a prime number).Text)) { lblResult. } return true. as you may recall. Before we can get to the asynchronous call to the web method. if (Palindrome(result. as you saw in the ReverseString example.ToUpper())) { return false.

if the number input is out of bounds. Of course.036. return ServiceSupport.destination. so the multiplication by 1000 is needed to convert the delay into seconds.223. right? Given the efficient nature of the algorithm for determining primality used in the IsPrime web method (at most. The web method needs to be changed to invoke the new function: [WebMethod (BufferResponse = false. int DelayInSec) { ServiceSupport. } } Note: The Thread.854.372.Sleep method takes an argument in milliseconds. It's important to make sure that the method is not passed an argument of zero (0). Extending the Service Interface Going back to the web service developed in Chapter 1. it just doesn't take very long to determine whether a number is a prime. In order to actually witness any asynchronous behavior. Here's a function that can be added to the class module behind the SybexC2 web service that sends the thread being executed to sleep for the designated number of seconds: static public void GoToSleep(int DelayInSec) { if (DelayInSec > 0) { System. it needs to iterate up to the square root of the number). avoiding this is achieved with the conditional check that DelayInSec > 0.IsPrime(NumToCheck).372.Sleep(DelayInSec * 1000). we're going to add a parameter to the IsPrime web service that delays the method's return by the designated number of seconds.223. which would suspend the thread indefinitely.808). it will throw an error.775. at least if the number is within the bounds of the long data type ( 9.036. CacheDuration = 0)] public bool IsPrime(long NumToCheck. } .854.Threading. we're going to add code to the IsPrime web method that will slow it down on demand.808 to 9.Thread.775.GoToSleep(DelayInSec).

Note: If you've already generated a proxy that references this method in a project. Listing 2.Services. you should update it by selecting the web reference in Solution Explorer.Services.Web.SybexC2Service using System.4 shows the entire revised web method. run wsdl. namespace SybexC2 { [WebService (Description="Chapter 1 demo program".Note: that I've added attributes to the WebMethod directive to make sure that the IsPrime web method is neither buffering nor caching its response to a call.14: The generated test page for the revised web service now shows two parameters. you should delete the current module.exe to create the proxy module.bearhome. Figure 2. Namespace="http://www. Name="SybexC2Service". and add the newly generated module.Web.14). If you go back to the SybexC2 project developed in Chapter 1 and run it with the revisions.4: The Extended IsPrime Web Method // SybexC2. you'll see that the test page for the web method generated by Visual Studio now includes input fields for the DelayInSec argument as well as NumToCheck (Figure 2.WebService {")] public class SybexC2Service : System. and selecting Update Web Reference from the context menu. If you used wsdl.exe again. Listing 2. right .

static public bool IsPrime(long NumToCheck ) { for (long i =2.15 shows a web forms interface in its designer along these lines. Figure 2. CacheDuration = 0)] public bool IsPrime(long NumToCheck. synchronous consumption is easy.Thread. i++) { if (NumToCheck%i == 0 ) { return false.. with the addition of a Button to perform asynchronous consumption and a second TextBox that will be used later on for the .GoToSleep(DelayInSec). [WebMethod (BufferResponse = false. a Button (to do the checking when the users clicks).. i <= Math. } } } } Synchronous Consumption As we've already said.IsPrime(NumToCheck).. Assuming you've added a web reference to the SybexC2Service. and a Label to display the results. } } // Class1.. } } return true... } static public void GoToSleep(int DelayInSec) { if (DelayInSec > 0) { System.Sleep(DelayInSec * 1000).. namespace SybexC2 { public class ServiceSupport { . int DelayInSec) { ServiceSupport. return ServiceSupport. } .. you can create a user interface consisting primarily of a TextBox (for the number to be checked).Threading.cs ..Sqrt(NumToCheck).

Text = "Not prime!".ToInt64(txtInNum.0)) { txtStatus. you can see that it puts a nice front end on the web service (Figure 2.SybexC2Service(). and displays the appropriate message based on the method return value: private void btnCheck_Click(object sender. since we want this call to return as soon as possible.15: It' s easy to create an interface that can be used to check synchronous and asynchronous consumption of the IsPrime web method. System.meaning.SybexC2Service theService = new theService. } else { txtStatus.EventArgs e) { theService. } } Of course. if (theService.Text = "Is a prime!". Here's the click event procedure that calls the web method synchronously to check whether the number input is a prime. the second parameter of the call to the IsPrime method is 0 .IsPrime(Convert.Text). .16).results of the asynchronous call. Figure 2. If you run the program. no extra delay.

This procedure writes periods (. you'll find a BeginIsPrime and an EndIsPrime. Asynchronous Consumption If you looked at the code for the auto .. Here's the click event procedure code that invokes BeginIsPrime asynchronously. . the proxy class includes three methods: • • • NamedWebServiceMethod BeginNamedWebServiceMethod EndNamedWebServiceMethod The Named. In the proxy class for the SybexC2 service.2.. The point of the example is to demonstrate something else happening while the web method call is waiting to return. method is used synchronously. whereas the BeginNamed. methods are intended for use with asynchronous calls.16: A synchronous call to the web method easily determines whether a number is a prime.... The general pattern is that for each NamedWebServiceMethod included in the web service. and EndNamed. in addition to IsPrime...generated proxy module back in Listing 2. you'll see that it created three methods for each web method that might be invoked..) into the TextBox that displays the status message while it waits (what do you do to idle away the time while waiting for something to happen?).Figure 2.

SybexC2Service().Text = txtStatus. you'll get a display like that shown in Figure 2.Text).Text = txtStatus. } } If you run the project. // do something until call returns txtStatus.private void btnASynchCheck_Click(object sender..SybexC2Service theService = new theService.BeginIsPrime(Convert. } else { txtStatus.Text + " Is a prime!". Figure 2.Text + " I'm back! ". if (theService. 1.Text = "Not blocked.Text + " Not prime!"..17 (although there may be a great many more periods when you run this in real life. while (ar. } txtStatus. and click the ASynch Check button. System.ToInt64(txtInNum.IsCompleted == false) { txtStatus.EndIsPrime(ar)) { txtStatus. don't have to wait.Text = txtStatus. enter a number to check.Text + ". null).Text = txtStatus. particularly if you put in a larger delay).EventArgs e) { theService.\r\n". IAsyncResult ar = theService. null. .".17: Periods are added to the TextBox while the asynchronous web method call does its thing.

\r\n".SybexC2Service theService = new theService. the third parameter is for a delegate of type System. 1. The first two arguments of the call to BeginIsPrime are. Finally.a period is added to the TextBox: while (ar. Next. As long as its value is false .Now that you've seen the asynchronous call in action.SybexC2Service().ToInt64(txtInNum. Note: The backslash indicates that \r and \n are escape characters .BeginIsPrime(Convert. a line of text is displayed in the TextBox: txtStatus. } .Text). of course.the number to be checked and the delay in seconds. The third and fourth arguments are each represented here by null.two . The fourth parameter is for a callback method . named theService.character tokens with special meanings. IAsyncResult ar = theService. the parameters required by the IsPrime method .. stores state information for the asynchronous call in a variable named ar.AsyncCallback. null. and \n means line feed (new line). based on the class defined in the proxy module in the normal fashion: theService. The next line.". You'll see an example in Chapter 3 of an asynchronous call that uses these parameters. one that does not refer to any object. As a preview. let's go through the code a little more carefully. \r means carriage return. The first line instantiates an instance of the web service class.Text + ". the IsComplete property of the IAsyncResult variable is polled using a while loop.Text = "Not blocked.IsCompleted == false) { txtStatus.the method that is called when the asynchronous call is complete. A delegate is one method standing in for another.Text = txtStatus. don't have to wait. a keyword that means a null reference.meaning the asynchronous call has not completed . null)..

hard . The advantage of this is that we can use the Page.In theory. (See. see Listing 1.NET web forms page .it waits for the entire response to the HTTP request before displaying anything. and then everything is written all at once . While it is true that a great many periods get written to the screen before the results of the asynchronous call are delivered. the procedure goes on in a normal fashion: txtStatus.Text = txtStatus. of course. a web service can be created by hand in a text editor if the file is saved with an . we can create an ASP.) The file is compiled into an ASP. In a similar fashion.Response object to hand .Text = txtStatus. The problem here is the way ASP.aspx.Text + " I'm back! ".coded in a small value (23) to check for primality. } else { txtStatus.NET web service the first time it is opened. For convenience sake. you could do all kinds of things in this loop. } I think you'll find if you set this program up and run it that there is a fly in the ointment.bypassing the Visual Studio . then the asynchronous call completion. and the file is played in an Internet Information Services virtual path accessible via URL. We can get around this by 'rolling our own' HTML. if (theService. It would be much more satisfying to actually see something being done while the call is being processed.NET processes web forms . not just display periods on the screen.Text = txtStatus.code HTML that is sent directly back to the browser .1 and related text.EndIsPrime(ar)) { txtStatus. provided it contains appropriate directives and is saved with an . or a serpent in paradise.NET web application by hand.Text + " Not prime!". Doing It Without Web Forms As you may remember from Chapter 1.asmx file extension. I told you there would be tangents!) The problem is the observable behavior of the program. and placed it in the virtual directory created for this chapter's example.generation process. I simply imported the proxy class created for the SybexC4 example in this chapter. Note: that the page still has to reference a proxy to the web service. (For details. . what happens is that it seems like there is a fairly long pause. named the file async. the file includes appropriate directives.aspx file extension in an accessible virtual directory.first the periods. When the loop completes (because the IsCompleted property of the IAsyncResult object has become true).Text + " Is a prime!".

Write("<br> Not prime!").Write("<b>W</b><br>").Flush(). // do something until call returns Response.Write("</body></html>"). } Response.Flush()." let alone displaying Ws or returning from the web method. this did not happen. don't have to wait. To my dismay. Response.Write("Not blocked. SybexC4. null.Write("<html><body>\r\n"). 0. } else { Response. null).<h3>").Write("<br> Is a prime!").EndIsPrime(ar)) { Response.SybexC2Service theService = New SybexC4. and in fact the browser would not render a page at all . if (theService.IsCompleted == false) { Response. I eventually realized that the browser was hanging in infinite limbo because it was being overwhelmed by the sheer number of Ws generated. IAsyncResult ar = theService. Response. don't have to wait. %> The intention was to open it with the URL http://localhost/SybexC4/async.SybexC2Service().Flush(). Response.theService. } Response.Write("<br></h3> I'm back! "). Response.aspx and to have the page display the letter W down a page until the asynchronous call completed. while (ar.not even the initial "Not blocked. Response.Write("I can do stuff while I wait!")..theService" %> <%Response.Here's the way the file initially looked: <%@ Page language="C#" %> <%@ Import namespace="SybexC4.BufferOutput=false.theService..BeginIsPrime(23. so I added a for loop that uses iteration and modulo arithmetic to greatly cut down the Ws and break out of the asynchronous while statement when the loop completes: .

Write("<b>W</b><br>").. } Response. // do something until call returns Response.IsCompleted == false) { for (long i = 1.Write("<br></h3> I'm back! ").5.theService. } } break. } } break.SybexC2Service(). i <= 100000000. null). i++) { if (i%10000000 == 0 ) { Response.SybexC2Service theService = new SybexC4. Response.NET Page <%@ Page language="C#" %> <%@ Import namespace="SybexC4.Flush().EndIsPrime(ar)) { Response.while (ar. Response.Write("Not blocked.18). null.theService" %> <%Response. Response.<h3>"). Listing 2.Write("<br> Is a prime!"). i <= 100000000.Write("I can do stuff while I wait!"). SybexC4. 0.5: Displaying Asynchronous Behavior in a Hand-Coded ASP. while (ar.theService. } .BeginIsPrime(23.IsCompleted == false) { for (long i = 1. i++) { if (i%10000000 == 0 ) { Response.Flush(). I was able to watch ten Ws gently plop onto my screen and then observe the completion of the asynchronous call (Figure 2..Write("<html><body>\r\n"). IAsyncResult ar = theService. don't have to wait. if (theService. which you can see incorporated in Listing 2. Response.Write("<b>W</b><br>").BufferOutput=false. } When I ran it with this addition.

Flush().but it worked better for me with the larger numbers.Write("</body></html>").else { Response. Finding Services to Consume Doing it by oneself may be satisfying and useful. then the asynchronous call completes. One might think that one could loop to 100 using modulo 10 and have the same impact as the code in the listing . it might be an interesting. Note: You may have to fiddle with the numbers in the loop depending on the speed of your system. } Response. and handy way to extend the architectures of your applications across the web . So how do you find web services to consume? Here are a few places to start. Response. The real point of web services is the ability to remotely use code methods provided by others on the basis of the WSDL interface description provided (or to provide web methods that other people can access). . but it is ultimately much more fun to do it with others.namely writing ten Ws to the screen .18: The Ws are written one after another to the screen..Write("<br> Not prime!"). novel.. Response.but hardly revolutionary.Flush(). If you could only consume services you yourself had written. %> Figure 2.

NET My Services.19: The XML Web Services tab of the Visual Studio Start page allows you to register web services and to search for web services by category. shown in Figure 2. Figure there's no need to go to this kind of trouble.NET provides a UDDI interface that allows you to register your web services and to search for web services you can use by category. UDDI The Universal Description.Passport Passport is probably the premier and best . choose XML Web Services from the Start page (you can also display the Start page by selecting Help > Show Start Page).com . Directories Several websites have extensive directories of web services you can use.gotdotnet.stop authentication and sign . Visual Studio .commerce purchases). You'll find a software development kit (SDK) available for download and much information at www. Passport is non . is not intended to be easily readable by mechanism by a great many websites (and it can also be used as a digital wallet to store credit card information for e . Some of the best of these include: • Got Dot Net: www. It's used as a one .trivial to implement.passport.known of the Microsoft . To open this interface. Discovery. While you could write your own UDDI client to browse UDDI repositories. and Integration (UDDI) directory service.19. conceptually introduced in Chapter 1. As web services go.

• • Salcentral: www.xmethods. learned how to put a thread to sleep. and explored the difference between calling a web method synchronously and asynchronously.NET Conclusion This chapter has been fun! In it. . I showed you how to create ASP.NET web applications and how to consume the web service methods created in Chapter 1.and revisits invoking our web service asynchronously in the context of a generalized asynchronous design pattern. you've seen how to work with web server controls. Let's move on to Chapter 3. which explores the creation of Windows applications using C# . Along the Xmethods: www.

events. In the New Project dialog.') No tool is better suited to Windows development than Visual Studio .1).NET .NET Windows executable program to be opened and run from within Internet Explorer. This chapter begins the exploration of Windows application projects with information about forms. Creating a Windows Application Project To create a new Windows application project.NET.particularly considering .NET's no . and shows the preferred general design pattern to use when making asynchronous calls in . Windows application development is likely to be where the vast bulk of programmers spend their time for the foreseeable future . and more.Windows Uses Web Services. open the New Project dialog (File > New > Project).and no language more appropriate than C#. and give the project a name and location ( Figure 3. even considering web services and the Web. Windows Application in the Templates pane. the roots of Visual Studio are deeply entwined with Windows application development. Too! Overview • Windows application projects • Forms • Form properties. which allows a . message boxes. . It concludes with an asynchronous invocation of the web service developed in Part I. In addition. (You could say that Windows development is Visual Studio 'classic.Show method • Changing the shape of a window • Asynchronous design pattern (used in web service consumption) For all the fun and glory of web services and web applications.touch deployment. select Visual C# Projects in the Project Types pane. and methods • The MessageBox.

as shown in Figure 3. select View > Solution Explorer.2).2.1: To create a Windows application project. and the project files will appear in Solution Explorer. The Windows form will be displayed in its designer. If Solution Explorer is not displayed. You can add existing . class modules. to see the form's designer. Figure 3.2: A form is created in its designer. The basic project shown in Solution Explorer contains one form. Visual Studio will create a new project based around a Windows form named Form1. To view all the project's files (as shown in Figure 3. select Visual C# Projects and Windows Application. along with the various other files required to run a Windows project (shown in Solution Explorer). and provide a name and location for the project files. you may have to select View > Designer. Note: Depending on your Visual Studio configuration. You can add new forms.Figure 3. click the Show All Files button on the Solution Explorer toolbar. and other objects to it using the Add New Item dialog (Project > Add New Item).

or starting place.2 (a default web service) and Listing 2.Form { private System. doubleclick the form in its designer.ComponentModel. with Hidden Region Expanded using System.1 (a default ASP.Collections. Listing the form in its designer or Solution Explorer and select View Code from the context menu.Windows. Forms The project module named by default Form1. using System.1: 'Out of the Box' Windows Form Code. You may be interested in comparing this with Listing 1. using System. right . // // TODO: Add any constructor code after // InitializeComponent call } . For that matter. using System.Forms.1 shows the auto . To view the code for the form module in the Code Editor.ComponentModel.Forms. for the application. you will have to supply an entry point for the project if you want it to run as an executable). Listing 3. using System.NET web forms application).items using the Add Existing Item dialog (Project > Add Existing Item).generated 'out of the box' form code. if you want.Drawing. Alternatively.cs contains the code required to instantiate the form object as well as a Main() procedure that serves as the entry point. namespace SybexC5 { public class Form1 : System.Container components = null. public Form1() { // // Required for Windows Form Designer support // InitializeComponent().Windows.Data. you can remove Form1 from the project (although in that case. using System.

do not modify /// the contents of this method with the code editor. which is the base class for all controls.Drawing.Size = new System.Size(300.Windows. /// </summary> [STAThread] static void Main() { Application. The form class itself represents a window or dialog box that is part of an application's user interface and inherits from Component by way of Control. } } base. this. the newly minted Form1 object inherits from System. } #endregion /// <summary> /// The main entry point for the application.Dispose().Run(new Form1()). Form's immediate parent is ContainerControl.300).Forms.Container(). /// </summary> private void InitializeComponent() { this. a class that provides .Text = "Form1". } } } As you can see in the class declaration in the form code.protected override void Dispose(bool disposing) { if (disposing) { if (components != null) { components. } #region Windows Form Designer generated code /// <summary> /// Required method for Designer support .ComponentModel. this.Form.Dispose(disposing).components = new System.

events.namely. In other words. Note: A control is a component with visual representation. Note: Some form properties. Figure 3. using the Properties window to set the form's Text property to "I am the form's text!" (Figure 3. After having a look at them.3) has the same consequences . (Here. Form Properties A property is a setting that describes something about an object such as a form. the Properties window is set to categorize rather .as executing this.3: The form's Text property is set in the Properties window. Properties can be set at design time using the Properties window and at run time in a code assignment. and methods. can only be set in code. We're going to quickly waltz through some Form properties.functionality for controls that can function as a container for other controls. Controls (and components) in the Toolbox are instantiated without additional code when added to a form and run.Text = "I am the form's text!". such as Region (explained later in this chapter). setting the Text property to the designated string when the form is displayed .even before you do anything to them. I'm sure you'll agree that forms are powerful objects with a great deal of functionality .

its behavior. Note: When you select a property in the Properties window.down list of possible settings (Figure 3. In the Properties window. Table 3. you'll find a brief description of what it does in the pane at the bottom of the Properties window. and also its behavior in significant ways. To select a FormBorderStyle. depending on the FormBorderStyle setting.4).than alphabetize properties. For example. You need to play with form properties in the Properties window to get to know them and understand what they do.1 describes the windows that result from the seven different FormBorderStyle choices.) in the form constructor (the procedure that creates the instance of the form object. appearance.just select a FormBorderStyle setting. We tend to take these things for granted. The good news for the programmer is that you don't need to do any work to get this scaffolding . but the reality is that a Windows form buys one a great deal of "scaffolding" in terms of behaving as users expect windows to behave. highlight FormBorderStyle and click the arrow in the right column to display a drop . This setting also determines whether a running window is resizable. . which are set using the FormBorderStyle property. an icon will (or will not) be displayed in the Windows Taskbar. the window can be set to display properties alphabetically or by category. A great many form properties can be set in the Properties window. open a form in its designer. In this section we'll just have a look at a few properties. The FormBorderStyle Property Windows forms come in various styles. Styles change the basic appearance of the window when the form is run. see the 'Constructors' sidebar later in this chapter) or the form load event. its background color. and more. Note: Using the first two buttons on the Properties window toolbar. ranging from setting the form icon.

You cannot drag the window's borders to resize it while it is running.1) to the property. 3D effect. this kind of window does not display an icon in the Windows Taskbar.resizable window with a Close button and small . but sizable. This is the default and most commonly used setting.size text in the title bar. A nearly normal window with a single .line border that is only resizable using its Minimize and Maximize buttons.1: Members of the FormBorderStyle Enumeration Constant None FixedSingle Resulting Window A window without a border or border . This is the setting to use for introductory 'splash' screens.Figure 3.4: The Properties window can be used to set form properties such as FormBorderStyle. . Table 3. Like FixedSingle. A non . with Minimize and Maximize buttons if desired. Fixed3D FixedDialog Sizable FixedToolWindow Like FixedSingle. except that the borders are created with a raised. SizableToolWindow Like FixedToolWindow. You can set the FormBorderStyle property dynamically at run time in code by assigning a member of the FormBorderStyle enumeration constants (shown in Table 3. Unlike the preceding settings. This setting is commonly used for dialog boxes A normal.related elements. resizable window. except that the borders appear recessed.

DockStyle. turns the form into a borderless window when it is executed (although it is unusual to execute this code.Right. DockStyle.Bottom. since most programs do not change window styles at run time). the control is bound to. The Dock property "docks" the border of a control to its container (such as a form). The Anchor and Dock Properties Anchor and Dock are two properties that greatly help with positioning controls on a form and save us from writing code to take care of things when the user resizes a running form.Top. this. and DockStyle. Figure 3.5: The Dock property simplifies managing the run . DockStyle.Fill to the Dock property.FormBorderStyle = FormBorderStyle. The distance between the form edge and the closest edge of the control will remain constant no matter how the form is resized. .5) or by assigning a value of DockStyle.Fill means that the control takes up all unused space in the center of the form.None. or DockStyle. The Anchor property describes which edge of the container. DockStyle.For example. such as a form.Left. Dock can be set visually (Figure 3.time position of controls.None means that the control is not docked.

or the system. followed by parameters (also called arguments) within parentheses. System.triggered . . For example.EventHandler. Here's the general form: this. The event method must also be hooked up as a delegate to handle the event using the += operator (you'll find this in the hidden region of the form code). . e). } Now. g). 2. to call a Button named button1's Click event in code from a Form click event: private void Form1_Click(object sender. button1's will be. One event method can be used by many events (each event can be assigned the same delegate). private void Form1_Click(object sender.EventArgs().EventHandler(this. Assign it to an event and provide a delegate (the name of the method that will handle it) to register the new event handler. program code. If you're not already within an event. I've introduced one way to create an event handler. An event is a procedure with a name.Form Events Event methods are placeholders for code that is only processed when the event is fired . System. such as button1_Click..EventArgs g = new System. You must be sure to pass to the event procedure the number and type of arguments it expects.EventArgs e) { button1_Click(sender. The steps are: 1. you can create your own System. So far in this discussion.Form1_Click). as well. which is strictly in code. Form1_Click( a user action.EventArgs e) { } You probably know that it is easy to call an event procedure in code.Click += new System.. Create a new System.EventArgs to invoke an event: System. when Form1's click event is fired.

Figure 3. 'The Life of the Object in C#. which is accessed via the Properties window. In contrast.down lists in the Code Editor. To open this interface. Using the Events tab of the Properties window. You can find out more about creating your own custom events in Chapter 8. The IDE will generate a new procedure with the proper arguments and register it as an event handler. the development tools for Visual Basic . you can select an object event from the list in the left column and type in a name on the right (you do not have to name it conforming to the object_event convention).' For now. let's have a look at the support that the Visual Studio interface provides for auto generating events. . You can now put code within the event procedure. the auto .generation of event scaffolding is done using the Objects and Procedures drop . click the lightning bolt icon (the lightning bolt means an event.generating event scaffolding. Create the framework for the event procedure. get it?) on the Properties window toolbar (Figure 3. C# has its own interface for auto .NET are the same.6: You can use the Events tab of the Properties window to name event procedures and have their scaffolding auto generated. but they differ greatly when it comes to events. In VB. Events in the Properties Window In most ways.6).3.NET and C# .

named in the standard way (e. and how to use the Properties window to do the same thing. Choose whichever way you like better! Monitoring Form Events It can be helpful to know which events are fired by an object. I'll show you an example. I've shown in this section how to write the code that adds event handlers to form events. Form1_Click).It's really useful to know that if you double . provided it has the right number of arguments. will be created. Figure 3. Besides typing in the same of an event procedure. and in precisely what order. in a little while..7).Closed += new System. You'll often find that you want to wire multiple events to one an event in the left column.7: You can also use the drop . possibly generated by more than one object.down list on the Events tab to assign an event to an existing handler. only the code wiring the event to the existing procedure: this.EventHandler(this. perhaps determining within the handler which object invoked the event. a new event procedure will not be created. of how this works. you first should add a using directive to the top of the form code so .g.down list (Figure 3. It's easy to monitor events being fired by placing a command in each event that uses the WriteLine method of the Debug object to display a notification in the Output window that the event has been fired. you can select an existing event handler from the drop . an event procedure. In this case. To monitor the form event sequence.OneForAll).

Listing 3. For example: private void Form1_Load(object sender.2 shows several form events being monitored. } Run the project in debug mode in the development environment (Debug > Start. using System.. namespace SybexC5 { public class Form1 : System. System. add a call to the Debug.8). #region Windows Form Designer generated code private void InitializeComponent() { .2: Monitoring Form Events ..Diagnostics. in each event you'd like to monitor. With the project running. Listing 3.Windows.WriteLine method. Next.EventArgs e) { Debug.Forms.Diagnostics. open the Output window (View > Other Windows > Output).Diagnostics namespace easily: using System.that you can access the System. or F5 on the keyboard)..Form { .8: You can use the WriteLine method of the Debug object to follow the progress of form events in the Output window. You can now watch the progress of events being fired (Figure 3. Figure 3..WriteLine("Form Load Event Fired").

EventHandler(this. System.EventHandler(this.EventArgs e) { Debug.. . Click.EventArgs e) { Debug. } private void Form1_Activated(object sender. } private void Form1_Deactivate(object sender.EventArgs e) { Debug. System.WriteLine("Form Load Event Fired").Enter += new System.EventHandler(this. System. System. private void Form1_Load(object sender. the same event.. ..WriteLine ("Form Enter Event a/k/a All_Hail_Form1_Was_Entered Fired"). } private void All_Hail_Form1_Was_Entered(object sender. } #endregion .EventArgs e) { Debug. for example.Form1_Activated)..WriteLine("Form Closed Event Fired")..All_Hail_Form1_Was_Entered).EventArgs e) { Debug. this.WriteLine("Form Activated Event Fired"). this. this.Load += new System. } } } Determining Which Object Fired an Event As I mentioned earlier.EventHandler(this. } private void Form1_Closed(object sender.Activated += new system. this.and provides different code accordingly. a common pattern is to have one handler that handles events for multiple objects . but not necessarily.EventHandler(this.Form1_Load).Deactivate += new System.Form1_Deactivate). The code in the handler then uses the Sender parameter to determine which object's Click event fired ... System.Form1_Closed). this.Closed += new System.WriteLine("Form Deactivate Event Fired").likely.

Forms. This abbreviated statement Control ctrl = (Control) sender..button1.Windows. to an object of type Control: System. this.Control) sender casts.Click += new System. and almost certainly an ancestor of anything that can fire a click event. Next. you'd get a syntax error due to strong typing when you tried to use sender.Control) sender. because it is very important. declared simply as object. The part of the statement to the right of the equal sign. explicitly cast the sender parameter.Forms.generated form code (see Listing3.Forms namespace is included with a using directive at the beginning of the auto . to both the Form and Button click events either in code or using the Events tab of the Properties window: this. namely a control.Control ctrl = (System. or more simply.Name to determine the kind of control that fired the event.Windows. the sender object to an object of the type within the parentheses. I'll show you a single handler for a Click event that determines whether a Button or Form fired it. Let's examine this statement carefully. First.Windows.Windows. I used the long form of the statement. Control means System.EventHandler(this.Windows. or converts. I chose Control as the type to cast into because it is a common ancestor of all controls.Control. If you didn't cast sender to a control.To see how this might work. named EventHandler.which_object).. In order to keep things really clear. .1). Control.Click += new System. is the equivalent of the longer version because the System. .Forms.which_object).Forms.Control I could have simply used the Control object without the qualifying namespaces in the statement. (System.EventHandler(this.Forms. assign the handler.Windows. The part before the equal sign declares a variable ctrl of type System.

9 and Listing 3. ..3. private void which_object(object sender. Note that C# is always strongly typed.Windows. } . Listing 3..which_object). Debug.Click += new System. this. as shown in Figure 3. you'd use a function such as CType() to explicitly cast.. System.Control ctrl = (System. Figure 3. this.Windows. it's easy to access it to determine which object it is.Tip: By way of comparison.9: The generalized click event handler can determine which object invoked it. or used in a more practical way..EventHandler(this.Name + " Click event fired!")..Forms.Click += new System. With the event's sender now stored in a variable of type Control. in Visual Basic. .EventArgs e) { System.3: Casting the sender Parameter to Determine Which Object Invoked the Click Event .. or convert.Forms.EventHandler(this. The information can be displayed in the Output window.button1. from one type to another.which_object).Control) sender...WriteLine(ctrl. . it's as if Option Strict could not be turned off in VB.

"A second chance.2 shows some of the most frequently used form methods and describes what they do.Close(). See also BringToFront. because it allows the programmer to build in an escape hatch. Methods are implemented using functions. In just the way that a property is a variable tied to an object. Loads and displays a modal form. In the Click event for the Button. and invoked by calling an object instance followed by the dot operator followed by the method: Object. Close Hide Refresh SendToBack SetBounds Show ShowDialog Unloads the form. Used to position a form.EventArgs e) { this. or hides it.Form Methods Methods are a way to tell an object to do something. Makes a form invisible. } Next. and name it btnClose.YesNo). Table 3. display a message box that asks whether the user is sure (message boxes are discussed in considerable detail in the next section): DialogResult answer = MessageBox. Table 3. System. Moves the form beneath all other forms. MessageBoxButtons. add a Button to a form. in the form's Closing event. such as a dialog box. a method is a procedure or function tied to an object. without unloading the form. See also SendToBack.2: Commonly Used Form Methods Method Activate Purpose Moves the focus to the form and makes it active.". which is interesting. invoke the form's Close method: private void btnClose_Click(object sender. Loads and displays a modeless form (a normal window).. Maybe the user really didn't want to close that window… To see how this works.Method.. . Let's have a look at how the Form Closing event works. Updates the appearance of a form by repainting the form.Show("Do you really want to close me?". BringToFront Moves the form on top of all other forms.

Show("Do you really want to close me?". and Querying Whether to Close. Note: You can use the Form Closing event for cleanup code of various kinds .CancelEventArgs e) { DialogResult answer = MessageBox.4: Closing. System.No) { e. MessageBoxButtons.Cancel = true.ComponentModel.Cancel = true.not just to query whether the user really wants to close the window. } } .If the user answers 'No.Close(). Listing 3.' then set the Cancel property of the passed System. } private void Form1_Closing(object sender. a Form private void btnClose_Click(object sender.EventArgs e) { this. System. "A second chance.".. if (answer == DialogResult.YesNo).EventArgs property to true: if (answer == DialogResult. Listing 3.4 shows a Close method and the related Closing event procedure with an included query about whether to close..No) { // Cancel the closure e. } The window will now stay open.

how do you know which radio button is selected . enclose an alphanumeric sequence in quotation marks. Then I'm going to use the message box as a jumping .The Lowly Message Box We just used a message box in the previous example to query the user (did she really want to close the form?). Show is a static method. buttons. defaultbutton.Windows. which must be entered in the order shown above: Text Contains the text that will be displayed in the message box. of message boxes in your time. the caption argument must be of string type. and enter their own text. To create a string literal. if not thousands. The contents of a variable in the text position must evaluate to a string. you have to include a string to be the text of the message box displayed.Show has the following format: DialogResult = MessageBox. One way to do this is with string literals.Show Syntax MessageBox.Show(text.and how can the information easily be used? MessageBox. meaning that it must be alphanumeric.Forms namespace. "I am a string!". You've undoubtedly seen hundreds. tells C# . options) Here are the meanings of the arguments.NET which buttons will be displayed. icon. because it is so useful and place for some variations on a theme: If you allow the user to create their own message box. I'm going to spend a little time going over the message box syntax. contains the text that will go in the message box caption bar. Like the text argument. caption. buttons Optional. This lowly dialog window gets no respect but is always ready to shoulder the burden and do more work! Message boxes are displayed using the Show method of the MessageBox object. and display a set of radio buttons that allow them to choose the buttons for the message box. It's also overloaded . but additional arguments after that are optional. Essentially. which is part of the System. caption Optional. meaning that you do not have to instantiate an object based on the MessageBox class to use the method. . as in.meaning that there are various versions of Show you can use with different argument lists.

specifying a right .3 shows the MessageBoxButtons members.Show(text. In other words. These include things like making the text right .3: The MessageBoxButtons Enumeration Constant Description AbortRetryIgnore Displays Abort. Retry. The following sections describe how to use the MessageBox. To use MessageBox. when you type MessageBoxButtons and enter the dot operator that follows it. options Optional.NET which button is activated by default (when the user presses the Enter key).to . or writing an entry in the system logs (see example below).aligned. defaultbutton Optional. Table 3.left reading order. and Ignore buttons OK OKCancel RetryCancel YesNo Displays an OK button only Displays OK and Cancel buttons Displays Retry and Cancel buttons Displays Yes and No buttons . I've also included these values here for your easy reference.NET which icon will be displayed. just use it in a statement without an equal sign: MessageBox.icon Optional. So where do you find the values to use for buttons and icons? The Code Editor's IntelliSense feature will generally supply this information. and Table 3. called member information. allows you to select some special options for the message box. icon). IntelliSense will automatically pop up a list of the members.Show arguments. tells C# . caption.Show to display a message box without returning a value.4 shows the MessageBoxIcon set of values. buttons. tells C# . or enumeration constants. Table 3. that you can use as a MessageBoxButton value.

These values are the members of System.YesNoCancel Displays Yes.DialogResult. and Cancel buttons Table 3. Here are the subject and caption. shown in Table 3. and a parameter designating the default button: .Show works.Show method. The Click event of btnService will be used to display the message box.Windows. or DialogResult for short. First. string caption = "MessageBox Demo". Table 3.5. string subject = "Service Notification by SybexC6".Forms. add a Button named btnService to a form. The elements of the message box will be placed in variables and then displayed using the MessageBox.5: The MessageBoxIcon Enumeration Constant None Question Stop Warning Description Does not display an icon Displays a question icon Displays an error icon Displays an exclamation icon Writing an Event Log Entry As an example of how MessageBox. No. let's use it to write an entry to the system logs. followed by the buttons.4: The MessageBoxIcon Enumeration Constant Asterisk Error Exclamation Hand Information Description Displays an information icon Displays an error icon Displays an exclamation icon Displays an error icon Displays an information icon You also need to know what the possible return values are for MessageBox.Show. icon.

MessageBoxButtons mbb = MessageBoxButtons. The message box should have made the most recent addition under the Application Popup category. mbo). mbo). MessageBoxDefaultButton mbdb = MessageBoxDefaultButton. .Show(subject. string caption = "MessageBox Demo". mbb.ServiceNotification. MessageBoxIcon mbi = MessageBoxIcon. Expand it until you see the System Event Logs (Figure 3. a message box like this one will be displayed: Listing 3.5: Using MessageBox.Show to Write an Event Log Entry private void btnService_Click(object sender. System. MessageBoxOptions mbo = MessageBoxOptions. MessageBoxButtons mbb = MessageBoxButtons. mbdb.ServiceNotification. and the message box can be displayed: MessageBox.Button1. mbi. Finally.Show(subject. open Server Explorer (View > Server Explorer). caption. } To see the entry that was created.Exclamation. mbb.OK. mbdb. mbi. If the project is run and the button is clicked.5. MessageBox. caption.EventArgs e) { string subject = "Service Notification by SybexC6". MessageBoxDefaultButton mbdb = MessageBoxDefaultButton.OK. a MessageBoxOptions variable is declared and set: MessageBoxOptions mbo = MessageBoxOptions.10). MessageBoxIcon mbi = MessageBoxIcon.Button1.Exclamation. The full click event code is shown in Listing 3.

10: The new entry can be viewed using Server Explorer (shown here) or using the operating system's administration facilities.11: Expanding the entry in the System log shows the full text of the message box. you can also find the log entry by opening the Event Viewer (found in the Administrative Tools Control Panel applet provided by Windows 2000 or Windows XP). . Figure 3.Figure 3.11). You can expand the entry in the Event Viewer to find the text of the message the notification and select Properties from the context menu to view the full text of the log entry (Figure 3. Note: Depending on your operating system. You can right . and looking for a recent Application Popup entry in the System log.

at a time. etc. RadioButton controls are typically used when you want to let a user make one .coded the value of the MessageBoxIcon parameter to the value MessageBoxIcon. caption. Note: You can create RadioButtons of uniform size by copying and pasting a RadioButton that has already been positioned on a form.12: The user interface is shown on the left. Label controls are never used . and buttons should be displayed in a message box. so they are named radioButton1 through radioButton6. e. But. rboOK. but you could expand this example by allowing the user to pick the message box icon as well as the buttons. When you add more than one RadioButton to a container such as a Panel or GroupBox. In the real world. The Checked property of radioButton1 is set to true. I've left the names of the RadioButton controls with the default.RadioButtons and Message Boxes The next application will provide several variations on the theme of allowing the user to decide which text.. This achieves the same results as dragging the RadioButton control from the Toolbox and can save some time.g. It's a good idea to rename any control that you will need to refer to in code with a name that reflects the type of the control and its function in the application. the Tag property is highlighted in the Properties window. in many cases. it should look something like the one shown in Figure 3.12. Figure 3. these RadioButtons form a kind of group in which only one can be 'checked. (I've hard .' or selected.Exclamation.) When the user interface is done.and only onechoice from multiple options. We'll use RadioButtons placed on a GroupBox to allow the user to pick the buttons for the message box. on the right. meaning that will be the one checked when the application first starts. it might be a good idea to name them after the choice they represent.

there's no real need to change the default control name. is a matter of your own taste as a programmer . and Ignore OK OK and Cancel Checked (Initial Value) true Only false false Tag Value (For Casting) 2 0 1 . and so on. as in Label1. I made the observation that I had already essentially entered the values of the MessageBoxButtons constants (see Table 3.6. the value of each radio button's Text property was as shown in Table 3. (This. The Ignore button was clicked. For example. which is Labeln. to make sure that two controls are aligned vertically. Alternatively. Table 3. of course.7: Values for the Buttons Pane Name radioButton1 radioButton2 radioButton3 Text Abort. one thing I hate almost more than anything else is typing the same string of text multiple times. Retry. The user did not click a button. In these cases. Label2. simply set the Location property X coordinate of each control to the same number. Table code. you can use the control's position coordinates. they are essentially 'decorative' in nature. The Yes button was clicked.3) in the Text property of each radio button. The Retry button was clicked.) You can use the Align and Make Same Size options on the Format menu to standardize the appearance of labels and buttons on a form.or the standards that have been established in a team project. The OK button was clicked.12. The No button was clicked. First Variation As a programmer.6: The DialogResult Enumeration Constant Abort Cancel Ignore No None OK Retry Yes Description The Abort button was clicked. Looking at the user interface shown in Figure 3. The Cancel button was clicked. which can be found in the Location and Size properties in the Properties window. to align controls. As things stood.

cast an integer to an enumeration constant.' There is no guarantee that the internal values of enumeration constants will stay the same in updated versions of . and placing the corresponding enumeration constant in the MessageBoxButtons variable that will be used to display the message box.radioButton4 radioButton5 radioButton6 Retry and Cancel Yes and No Yes. which is otherwise unused.Checked) { mbb = MessageBoxButtons. the value of the radio button Text property would be available to me programmatically at run time. this boils down to two problems: identifying the checked radio button. no matter how close the two are. and. they appear to be pretty similar. if (radioButton1. using a cryptic integer value is bad programming practice because it defeats the purpose of enumeration constants . it occurred to me that maybe I could cast the string in the radio button Text property to an enumeration constant .which is to clearly identify the value represented.AbortRetryIgnore. so that I could access it at run time.after all. Furthermore. This. Could I use the value of the selected radio button to identify the corresponding MessageBoxButtons enumeration constant? Anything to avoid having to code something along these lines: MessageBoxButtons mbb. however. Actually. } and so on for all the radio buttons. I'll show you a fairly elegant answer to the first issue in a second. No. Warning: Please be careful if you try this 'at home. Regarding the second problem. I could even make them identical. and Cancel false false false 5 4 3 Of course.6. so I used trial and error to come up with the values shown in Table 3. I placed the corresponding value in each radio button's Tag property.NET. . does not work: you cannot cast a string to an enumeration constant. You can. there seems to be no way to find this information. Short of trial and error. if necessary. it turns out. provided you know what integer equates to what enumeration constant value.

string caption = txtMsgCaption. MessageBoxIcon mbi = MessageBoxIcon. If it is.Text. the whole bunch of radio buttons).Checked == true) { // Is it checked? // If so.Checked evaluates to true. Determining the Checked Radio Button One way to determine which radio button is checked is to use a foreach loop to cycle through the controls on the group box (in other words..Controls) { if (c is RadioButton) { RadioButton rb = (RadioButton) c. of type RadioButton is declared. to do the iteration.OK. c. MessageBoxButtons mbb = MessageBoxButtons. each control is checked to make sure it is a RadioButton using a variable declared as (lowest common denominator) object.grpButtons. then do the assignment. DialogResult answer. } } } Inside the loop.Exclamation. . here are the preliminary variable declarations and assignments: string subject = txtMsgText. Here's how this works: foreach (object c in this. and the Checked property of rb examined. The iterating object variable is then cast to rb.Setting Up the Message Box Within the click event that will display the message box. a new variable. If rb. Note: that I've provided the variable mbb with an initial value.. even though this will likely change depending upon the user's radio button selection. if (rb. rb. do the assignment.Text.

you should have learned something about C# objects. caption.13).ToString()). which is stored in the variable mbb: mbb = (MessageBoxButtons) Convert.because not all controls have a Checked property.well.Object.the .Tag. it would be nice to do something with the result. If the project is run now. it's easy to display the message box: answer = MessageBox. mbi).ToInt16(rb.Note: I could have used a variable of type Control rather than Object to iterate through the group of controls. maybe it is! But if you followed the example through. . The complete click event code is shown in Listing 3.or maybe even a little over . Note: The all .ToString().6. Assigning the Value The following line of code uses the ToString() method to convert the contents of the Tag property to a string and the ToInt16 method of the Convert object to convert it to integer.Show(subject.Show("You clicked " + rococo . and conversion methods. mbb. Finally. The integer value is then cast to the correct constant enumeration constant. "Message Box Demo"). Here's one way to display it in another message box: MessageBox.lowercase keyword object is legal shorthand for System. casting. Displaying the Box and Doing Something Wild with the Result Now that we have the user's choice. If this seems a little baroque . commonly referred to as type Object. But I still would have to cast it to type RadioButton . the user can enter text and caption and select from a set of buttons (Figure 3.

grpButtons.Text. break.Controls) { if (c is RadioButton) { RadioButton rb = (RadioButton) c.Figure 3. MessageBox. foreach (object c in this. string caption = txtMsgCaption.ToInt16(rb. MessageBoxIcon mbi = MessageBoxIcon.Exclamation.Show(subject. DialogResult answer. } } } answer = MessageBox.Checked == true) { mbb = (MessageBoxButtons) Convert.EventArgs e) { string subject = txtMsgText. } .Tag. caption. System.13: The user can enter message box text and caption and can choose the buttons it will display. Listing 3.Text. "Message Box Demo"). mbi).OK.ToString()).Show("You clicked " + answer.6: Displaying a Message Box Based on Which RadioButton Was Checked private void btnDisplay_Click(object sender. MessageBoxButtons mbb = MessageBoxButtons. mbb.ToString(). if (rb.

But.Text and assign the enumeration constant accordingly.Checked == true) { switch (rb. Second Variation: The switch Statement The code in Listing 3. you could evaluate the DialogResult enumeration constants.AbortRetryIgnore. So. which is good..6 certainly didn't take much work to enter: it is short. for reasons I explained earlier.5. you could evaluate answer. Using the Text property. we can use a switch statement to evaluate the value of rb. against the value of the variable answer. relying on the integer equivalent of the MessageBoxButtons enumeration constants is not a very good idea. "No". Tip: Select. the designated message box is generated.Case is the VB equivalent of the C# switch statement. Clearly.When the Display Box button is clicked. Retry. Here's how the switch statement looks once we know we have the right RadioButton: if (rb. including its Text and Name properties. and Ignore": mbb = MessageBoxButtons. or "Cancel". Next. We know a good deal programmatically about each RadioButton in the group of controls.Text) { case "Abort. . let's bite the bullet and try a variation. which in the case of the message box shown in Figure 3. More rigorously. the button selected in that message box is displayed: There are a couple of ways to programmatically evaluate the response. shown in Table 3..ToString().13 would contain "Yes". A switch statement is used to branch depending on the value of an expression. Either of these could be tested when the Checked control is retrieved to determine the right enumeration constant value.

break.OKCancel.Text.Checked == true) { switch (rb.RetryCancel. case "OK": mbb = MessageBoxButtons. break.EventArgs e) { string subject = txtMsgText. case "Yes and No": .Exclamation.grpButtons. case "Retry and Cancel": mbb = MessageBoxButtons. break.AbortRetryIgnore. break.break. MessageBoxButtons mbb = MessageBoxButtons.7: Variation on the Message Box Using a switch private void btnVar1_Click(object sender. System. break.Text. case "OK and Cancel": mbb = MessageBoxButtons.OK. OK. . Listing 3. Listing 3. and Ignore": mbb = MessageBoxButtons. foreach (Control c in this.Controls) { if (c is RadioButton) { RadioButton rb = (RadioButton) c.OKCancel.7 shows the complete code for this variation.OK. DialogResult answer. MessageBoxIcon mbi = MessageBoxIcon.OK.. if (rb..Text) { case "Abort. so I did have to type in the text strings and all those constants (at least they were supplied by the Code Editor's auto . Retry. break. case "OK": mbb = MessageBoxButtons.completion facility). string caption = txtMsgCaption. case "OK and Cancel": mbb = MessageBoxButtons.

Listing 3.14: It's easy to have lots of fun with message boxes. mbi). old .fashioned way. break.8: Another Switching Variation . "Message Box Demo"). } } break. Figure 3. } Third Variation: Conditional on Top As I was looking at the second variation.YesNo.14. I still had to use a conditional that explicitly assigned each enumeration constant based on a hand . it's easy to have lots of fun with the lowly message box. MessageBox. Why not simplify matters by moving the conditionals to the top and forgetting about the loop? Listing 3.'s the good.ToString(). mbb. break. } } answer = MessageBox. and Cancel": mbb = MessageBoxButtons.YesNoCancel.Show(subject.coded value (the control's Text property). caption.mbb = MessageBoxButtons.Show("You clicked " + answer. it occurred to me that I really haven't gained that much with my fancy loop through the controls contained by the GroupBox.8 shows how to do this using if statements . As you can see in Figure 3. case "Yes.

MessageBoxIcon mbi = MessageBoxIcon. } answer = MessageBox.Show("You clicked " + answer. mbi). if (radioButton1. import the System. } else if (radioButton2.AbortRetryIgnore. any shape that you would like. System.OK.Checked) { mbb = MessageBoxButtons. } else if (radioButton6. DialogResult answer. The GraphicsPath object can be defined as almost any kind of shape. } else if (radioButton5.Text. MessageBoxButtons mbb = MessageBoxButtons.private void btnVar2_Click(object sender. MessageBox. } Changing the Shape of a Window Why should windows always be square? One of the great things about .Checked) { mbb = MessageBoxButtons.RetryCancel.Drawing. such as Windows forms. To start.Checked){ mbb = MessageBoxButtons. } else if (radioButton4.Checked){ mbb = MessageBoxButtons. caption.Show(subject.Checked) { mbb = MessageBoxButtons. Let's do this with the form we've been working with in the message box example.EventArgs e) { string subject = txtMsgText. "Message Box Demo").YesNo.NET is that it is easy to make visual objects.OK.Drawing2D namespace to make referring to the . mbb. at the top of the form module.ToString().Checked) { mbb = MessageBoxButtons. } else if (radioButton3.Text.YesNoCancel. string caption = txtMsgCaption. an oval window might make users think your application is really cool! The trick is to assign a GraphicsPath object to the Form's Region property.OKCancel. For example.Exclamation.

Drawing2D Making a Square Window Round Next. ApplyInitialRegion. 600.EventArgs e) { ApplyInitialRegion(). this. and assign it to the Region property of the Form object. . private void ApplyInitialRegion() { GraphicsPath myGraphicsPath = new GraphicsPath(). 0.AddEllipse(new Rectangle(0.Drawing2D library members easier: using System. 600...15). When the click event is run. 450)). 450)). } . 0. within the form class code. as shown in Listing 3. create a procedure. } All that remains is to create a way to call the form. Listing 3. define a GraphicsPath object using its AddEllipse method. the form turns into an oval (Figure 3. myGraphicsPath. } private void btnCircle_Click(object sender. which you can do in a button Click event. using System. System.AddEllipse(new Rectangle(0.Drawing...Region = new Region(myGraphicsPath). myGraphicsPath.. private void ApplyInitialRegion() { GraphicsPath myGraphicsPath = new GraphicsPath().Region = new Region(myGraphicsPath). this.. Within ApplyInitialRegion.9.9: Making a Square Form Oval . .Drawing.Drawing2D.

ApplyInitialRegion() . public Form1() { ..Windows. Warning: Depending on the shape you choose for your form.Figure the form load event or in the form's constructor.1). as in the example here.Form { . it may not have the normal mechanisms for closure. Constructors A constructor is a block of code that is executed when an object that is an instance of a class is created by using the new keyword. for example..15: The Region property of the form can be used to make it any shape you'd like. because the top part of form has been eliminated. ... In C#. } You'll find more about constructors in Chapter this example. Make sure that you provide a way to close the form (such as a Close button).Forms.. you can find the constructor within the class: public class Form1 : System. for the Form1 class (see Listing 3. } .. the constructor is a public procedure within the class with the same name as the class. You can open a form with whatever shape you'd like by placing the call to the method that changes the form's shape .

AddPolygon(myArray).A Polygon Form By way of variation. we should call the AddPolygon method. 200).Region = new Region(myGraphicsPath). Point[] myArray = { new Point(230. myGraphicsPath. new Point(300. new Point(500. if you run it. Listing 3. instead of calling the AddEllipse method for the GraphicsPath. the form will be a nice polygon. Listing 3. 400). new Point(570. new Point(400. rather than an oval. with the array of points as the argument: myGraphicsPath. 100). new Point(570. new Point(500.10: The Form Is a Nice Polygon private void ApplyInitialRegion() { GraphicsPath myGraphicsPath = new GraphicsPath(). Next.AddPolygon(myArray). 200). 100). 200).10 shows the revised ApplyInitialRegion() procedure.16. new Point(300. like the one in Figure 3. To do this. let's turn the form into a cute polygon. 400) }. 400). new Point(400. } . we need to create an array of points: Point[] myArray = { new Point(230. 200). this. 400) }.

you don't have to rethink it each time! The . the general pattern applies to the asynchronous invocation of anything. 'Reflecting on Classes. Note: To find out more about how this design pattern works. Although the example shows the asynchronous consumption of the IsPrime web service. 'Consuming the Service on the Web. let's consider briefly what we mean by a 'preferred pattern. (You'll find more material about asynchronous programming in Chapter 10.' Design patterns. As you'll see in Chapter 5.' I promised you an example that showed how to asynchronously use a web service from a Windows application using the preferred pattern for asynchronous invocations.' are systematic high .Figure 3. including: • • The called object does not know whether it is invoked synchronously or asynchronously. The Asynchronous Pattern Back in Chapter 2. which will be explored further in Chapter 5. Before we get there.") . A great virtue of patterns is that once you know the best way to do something.oriented programming. Let's have a look at this in the context of a practical example.16: Calling the AddPolygon method produces a polygon path from the array of points you provide. The .NET asynchronous design pattern involves a number of points.NET Framework automatically creates Begin and End asynchronous methods to go along with synchronous methods explicitly exposed by the called object. look up 'Asynchronous Design Pattern Overview' in online help. • A result object is supplied via a callback mechanism. "Working with Streams and Files.level solutions to a recurring problem in object . software design patterns are often likened to architectural solutions.

. passing it the ServiceCallback procedure as an argument so that it knows where to send the results: AsyncCallback cb = new AsyncCallback(ServiceCallback).Text). following the process explained in Chapter 2.BeginIsPrime(Convert. let's start coding.OK. System. cb. create a new procedure. Now you can go ahead and put whatever code you want in for execution while the asynchronous call is completing. "AsyncCallback Demo". If 10 seconds is too long for you to wait. don't have to wait.Information). to hold the result object (note that it takes an IAsyncResult as an argument): public void ServiceCallback(IAsyncResult ar) { } Moving to the Button's click event. and the web reference added. cService).". In our case.. 10.Text = "Not blocked. Invoke the BeginIsPrime method with the two arguments required by IsPrime (the number to check and the delay) followed by the AsyncCallback object and the service object.ToInt64(txtInNum.SybexC2Service(). Next. MessageBox. MessageBoxButtons. You must also add the SybexC2 web service to the project as a web reference.Start a new Windows application project with a TextBox for the user to enter a number to check for primality and a Button whose click event will invoke the asynchronous call to the IsPrime web service. Note: Note that the second parameter sent to the BeginIsPrime method is the delay in seconds. we'll write some text to a label and display a message box: lblResult. Assign the return value of BeginIsPrime to the IAsyncResult object that is passed to ServiceCallback: IAsyncResult ar = cService. First. ServiceCallBack. create a variable to hold an instance of the service: private void btnAsync_Click(object sender. and rename it theService. With the user interface in place. you can replace it with a smaller integer. instantiate an AsyncCallback object.EventArgs e) { theService.Show("I can do other things!".. MessageBoxIcon.SybexC2Service cService = new theService.

we need to create an instance of the service based on the passed IAsyncResult object: theService.Text = lblResult.AsyncState. as well as the callback procedure).Text + "Is a prime!".11 shows the complete click event code. Listing 3.17). back at the callback ranch. Figure 3.EventArgs e) { . Next. } else { lblResult. System. the EndIsPrime method can be checked for the return value from the web service: if (cService. } The project is now ready to run (Listing 3. you'll see the message box displayed while the asynchronous call is underway (Figure 3.Text = lblResult.EndIsPrime(ar)) { lblResult.Text + "Not prime!".SybexC2Service) ar.SybexC2Service cService = (theService.11: Using a Callback to Perform an Asynchronous Call to a Web Method private void btnAsync_Click(object sender.17: The message box is a demo of the use of a new thread to perform tasks while the asynchronous web call is completing. If you enter a number in the box and click Async.Meanwhile.

Finally. "AsyncCallback Demo". when the asynchronous call completes.Text = lblResult.BeginIsPrime(Convert. if (cService.Text + "Not prime!".". cService). lblResult.SybexC2Service().' picks up where we left off. you learned how to create oval .AsyncState.and polygonshaped windows.Text = "Not blocked.Information). cb. 'Building a Better Windows Interface. menus. You'll find bouncing round controls.OK. don't have to wait.theService. you could place any code you'd like to have executed in place of the message box display.ToInt64(txtInNum. Forms were covered in great detail. IAsyncResult ar = cService.SybexC2Service) ar. MessageBoxButtons.. // Do more stuff } public void ServiceCallback(IAsyncResult ar) { theService. AsyncCallback cb = new AsyncCallback(ServiceCallback). the callback procedure takes care of it and displays the appropriate results in the label: Conclusion Visual Studio . Meanwhile.. Chapter 4.EndIsPrime(ar)) { lblResult. 10.SybexC2Service cService = new theService. MessageBoxIcon.Show("I can do other things!". we spent some time with the lowly message box and playing various variations out in determining which radio button was selected. MessageBox. and this chapter has shown you a lot about building them using C#.SybexC2Service cService = (theService. ListBoxes. } } Clearly. the journey meandered back to something I promised in the last chapter: an asynchronous consumption of the web service developed in Chapter 1 using a callback object for the result. and much more .Text).Text = lblResult. Along the way.Text + "Is a prime!".NET is great for building Windows applications. } else { lblResult.

One of the examples in Chapter 3. how to create menus using code. TextBoxes. how to use the common dialog controls. using the logic explained in . and so on. as in the example in Chapter 3. It's therefore a property you can use for all objects that inherit from Control. This chapter picks up where the last chapter left off . Too!'. This includes the Form object. In a slightly less frivolous vein. showed you how to change the shape of a window to a circle (or a polygon).) Region is a member of System. and other controls .there's more information here about building the windowing interface. (The AddEllipse method was used to make the GraphicsPath oval. 'Windows Uses Web Services. It also includes all controls in the Toolbox that have a visual interface: Buttons. Why just windows? The first project in this chapter will show you how to make ovoid labels.and then use a Timer component to make these controls 'dance' across the screen.Windows.Control that sets (or gets) the window region associated with a control. windows! Here's a truism for you: Most Windows applications have user interfaces built around windows. Making a Button Round Let's write a general method that we can use to make a control round. buttons. which inherits from Control via ScrollableControl and ContainerControl.Building a Better Windows Interface Overview • Making round controls • Animating controls • Working with CheckedListBoxes • Building menus • Taking advantage of the common dialog controls • Constructing MDI applications Windows. Labels. windows.Forms. and how to create an MDI application. this chapter will show you how to work with ListBoxes. Round Buttons Dancing In Chapter 3 I used the Region property of a form to set the shape of the form by assigning a GraphicsPath object to the property.

to act as the control panel for this application (see Figure 4. 0. System. of course. This button has the text 'Round All'. as explained above (this will be expanded in a second to toggle back and forth between 'roundness' and 'squareness'). The panel has three buttons whose click events will be used to: • Make a single control round. Turn all the controls in the client area of the form .Chapter 3 in which the AddEllipse method of a GraphicsPath object is used to make an oval based on the size of the control: public void MakeItRound(Control c) { GraphicsPath gp = new GraphicsPath(). docked along the bottom of a form.) If you call the MakeItRound method with a control. this is a matter of your choice.Size. Place a button named button1 on the form. • • . } Note: I've subtracted five pixels off both the width and the height of the oval to eliminate the line produced by the 'raised' bevel when you make a Button round. Animate all controls in the client area.1). with the text toggling between 'Dance' and 'Stop'. c.Size. gp. as an argument. use the Properties window to delete the contents of its Text property and the other controls used later in this example although.EventArgs e) { MakeItRound(button1). This button is named btnRound and has the initial text "Round One". c. (For the sake of aesthetic harmony.5. the form excluding the panel that I placed on it .round. } Let's add a Panel. such as button1. They can have text if you'd like. the control will become round: private void btnRound_Click(object sender. c.AddEllipse(new Rectangle(0.Region = new Region(gp).Width .that is.5)).Height .

Region theRegion.Text = "Square One".EventArgs e) { if (btnRound.Text == "Round One") { theRegion = button1.. Next...Figure 4.Form { . System. and border.Drawing. btnRound. Toggling the Round Button To toggle the single control.1: Making round controls dance Note: The client space of a form is the form background area. exclusive of title bar. System.Region. .. we need to store the initial value of its Region property in a variable declared outside the click event (so that the value persists). the click event of btnRound can be modified to store the initial control Region and restore it when toggled: private void btnRound_Click(object sender. MakeItRound(button1).Windows. } else { . menus.Forms. We can do this by declaring a variable named theRegion as part of the initial class declaration: public class Form1 : System. toolbars.

} } Animating the Controls Now that we have all these nice. round controls. it sits on the 'tray' beneath the form which is symbolic of the fact that the Timer has no run .Region = theRegion. and its Interval property . btnRound. With a bunch of controls added to the client area of the form. If you click again. add a gaggle of Buttons. the Timer component is a mechanism for the time .based firing of an event. One for All and All for One It's easy to extend the rounding process to all the controls in a form's control collection.Controls) { MakeItRound(c).is repeatedly fired after the Interval (expressed in milliseconds). As you may know. When you drag a Timer component from the Toolbox to a form.time visual representation. TextBoxes . making sure that the Text property of each is empty (just to keep the aesthetics neat). the Timer's sole event .button1. If Enabled is true. it is back to its normal shape. If we let the control pass out of the form's coordinates. The object of animating the controls is to move them in the Tick property in one direction. System. button1 will become round.Text = "Round One". it won't be visible. code can be added to the click event of the Round All button to cycle through the form's control collection.EventArgs e) { foreach (Control c in this. Labels. the key Timer members are the properties Enabled and Interval. rounding each one in turn: private void btnRoundAll_Click(object sender. let's use a Timer component to animate them. Use the Properties window to set the Timer's Enabled property to false. and to reset things when they reach the edge of a form. Essentially. To see this in the form.Tick . } } Try it! If you run the project and click Round One.and any other control you'd like .

Enabled = true.Y + yIncrement). } if (c.X > this. System. it's helpful to use the Debug object to track where objects actually are.Text = "Stop".Enabled = false. int yIncrement = 5. btnDance.Size.EventArgs e) { int xIncrement = 5.Size.Location.Width .Location = new Point(c. } } Next.X + xIncrement.Text == "Dance") { timer1.50) { yIncrement = -500. } if (c. System. foreach (Control c in this.EventArgs e) { if (btnDance. in the Timer's Tick event.X <= 0) { xIncrement = 5.Height .to 100 (or one . } c.Controls) { if (c.tenth of a second). btnDance.Text = "Dance".right: private void timer1_Tick(object sender. toggle the Timer's Enabled property: private void btnDance_Click(object sender. } else { timer1. } if (c.Y <= 0) { yIncrement = 10. c.Location.Y > this. sending each one back to the upper .Location.Location. In the click event of the button used to start the animation. .left of the form as it goes off the lower . add the code to move each control in the control's collection.Location.30) { xIncrement = -500. } } In creating this kind of procedure.Location.

5)).Diagnostics. btnRound. Debug. 0. ..X. Debug.Drawing2D.WriteLine("YLocation: " + c.AddEllipse(new Rectangle(0.Size..ToString()).ToString()).. } } .Region. If you run it.ToString()).Y.EventArgs e) { if (btnRound. MakeItRound(button1).ToString()). c..WriteLine("Y Increment: " + yIncrement. Debug. System.Region = theRegion. you'll see that turning controls round and animating them is almost as good as watching a lava lamp! Listing 4. } else { button1.Drawing.WriteLine("XLocation: " + c. c. along these lines: using that code can be adjusted accordingly.1).Region = new Region (gp).5.EventArgs e) { foreach (Control c in this.Text == "Round One") { theRegion = button1. btnRound. private void btnRound_Click(object sender.Location.Height . Debug.Text = "Round One". System.Location.Text = "Square One".c.Width .1: Making Round Controls Dance . } private void btnRoundAll_Click(object sender. gp. using System.WriteLine("X Increment: " + xIncrement.Size.Controls) { MakeItRound(c). } } public void MakeItRound(Control c) { GraphicsPath gp = new GraphicsPath(). Save the project (the code is shown in Listing 4.

all inheriting from System.Enabled = true.Size.Location.Enabled = false. The ComboBox combines an editing field with the list of items in a ListBox. The .Y <= 0) { yIncrement = 10. System.Y + yIncrement). } if (c. foreach (Control c in this. Indexers.Text = "Dance".Size.Controls) { if (c.EventArgs e) { int xIncrement = 5. (For more information about programming the collections of items that lie underneath the ListBox classes. which allows the user to select an item from the list or to enter a new item.Text = "Stop".X + xIncrement.') The three ListBox controls you'll find on the Toolbox .30) { xIncrement = -500.X <= 0) { xIncrement = 5. int yIncrement = 5.Height .Y > this. and Collections. } else { timer1. } if (c.private void btnDance_Click(object sender. 'Array. and CheckedListBox.50) { yIncrement = -500. ListBox.Windows. btnDance. } c. } if (c. } } ListBoxes Listing Moving on. see Chapter 7. These controls are all far more alike than they are unalike.Location. As such.Width . System.Location.X > this.EventArgs e) { if (btnDance. c.Location = new Point(c.Forms.Location. they are an important part of many user interfaces.ListControl are ComboBox. } } private void timer1_Tick(object sender. ListBoxes in their various varieties provide a visual mechanism for dealing with collections of items. btnDance.Location.Text == "Dance") { timer1.Location.

2: It's easy to add items to a ListBox.EventArgs e) { checkedListBox1. Here's code to generate an array of items (Item # 1 . as shown in Figure 4. CheckedListBoxes do not support multiple selection. Adding an Array The Items. but you could substitute ComboBoxes or ListBoxes without changing very much. (To enable multiple selection with a ListBox.AddRange method allows you to add an array of items to the Items collection of a ListBox.. Item # n) and add them to a ListBox. Adding an Item The Add method of the CheckedListBox's Item collection will add an item at the bottom of the CheckedListBox. because those checks are so much fun.Items. System. } Figure 4. int size) { string[] myarray = new string [size].CheckedListBox is just like a ListBox.2: private void btnAdd_Click(object sender. i < size.Add(txtAdd. Both the specific control and the number of items are passed in to the procedure: private void AddToList(ListBox lb. } . for (int i = 0. In addition..ToString().) The example in this section primarily works with CheckedListBoxes. set its SelectionMode property to MultiSimple or MultiExtended. except that it includes a Checked property. i++) { myarray[i] = "Item # " + (i+1).Text).

Insert method to position an item in the specified place as shown in Figure 4. } Figure 4.1. Tip: Remember that the Items collection is zero based.Add. the second is the text to add): checkedListBox1.4 (the first argument is the index position. txtAdd.Insert(Convert.Count .3: You can add an array of items to a ListBox in a single statement. Alternatively.AddRange(myarray). you can position an item in CheckedListBox's items collection using the Items. You could use the Items. .lb.Items. 10). and the last item has an index of Items. System. } You can invoke this method with a specific control and the number of elements you want to add.3: private void btnAddArray_Click(object sender.ToInt16(txtIndex.Insert method.Text). so that the first item in it has an index of zero.Text). Positioning by Index We've already added an item to the bottom of the items collection using Items.EventArgs e) { AddToList(checkedListBox1.Items. with the results shown in Figure 4.

Count)) { checkedListBox1. the code in this chapter omits exception handling. to check that the insertion is within the range of the items in the collection: private void btnInsert_Click(object sender.ToInt16(txtIndex.Insert(Convert. } else { MessageBox. you can display an appropriate message: Warning: Generally.4: You can insert an item anywhere you'd like using an index value. 'Zen and Now: The C# Language.Text) >= 0) && (Convert.Show("Out of Range".Figure 4. MessageBoxButtons.Text). and easy enough. It's probably a good idea.Text).Items. MessageBoxIcon. if the user attempts to enter an index that is out of range. For more about exceptions. txtAdd.OK.EventArgs e) { if ((Convert. see Chapter 6.ToInt16(txtIndex.ToInt16(txtIndex. System.Exclamation). "Please try again". but this is not a good idea in the real world. } } This way.Text) <= checkedListBox1.Items.' .

} } else { MessageBox.Text.OK. MessageBoxButtons. whether an item is checked: private bool IsItChecked(CheckedListBox clb.Show("Selected Item Text: " + // checkedListBox1. let's write a function that determines. Assuming that there is an item selected.Show("Checked"). if (IsItChecked(checkedListBox1. we can display it (Figure 4.EventArgs e) { if (checkedListBox1. checkedListBox1.SelectedIndex)) { MessageBox. "Here is your text". .Show("Nothing selected!". by item index.ToString() amounts to the same thing. System.SelectedItem.SelectedItem. int theIndex) { if (clb.Show("Unchecked").GetItemChecked(theIndex) == true){ return true. checkedListBox1. checkedListBox1. checkedListBox1.Text != ""){ MessageBox.) Before we display the selected item.Information).Text property. we also need to make sure that an item is actually selected by testing the checkedListBox1. } else { MessageBox.Retrieving Selected Text It's easy to retrieve the text of a selected item from a CheckedListBox by using its Text property.Text. } else { return false."No text today".ToString(). } } First. (Alternatively.5) and display whether it is checked: private void btnSelect_Click(object sender. MessageBoxIcon.

Show (checkedListBox1. MessageBoxIcon.Text)].Items [Convert. the item is added to the new CheckedListBox (and marked checked): .Information). System. MessageBoxButtons.MessageBoxButtons. } } Figure 4.5: It 's easy to retrieve selected text. } } Retrieving Multiple Checked Items Sometimes you'd like to get all the checked items in one list (such as a CheckedListBox) and read them into another CheckedListBox. Retrieving by Index You can also retrieve the text of an item by using its index in the Items collection.Show("Checked").ToInt16(txtIndex.ToString(). if (IsItChecked(checkedListBox1.Information).Show("Unchecked").EventArgs e) { MessageBox. "Here is your text".Text))) { MessageBox.OK. MessageBoxIcon.OK. Convert. Each time a checked item is found. You can do this by iterating through the Items collection of the first CheckedListBox. } else { MessageBox. For example: private void btnRetrieve_Click(object sender.ToInt16(txtIndex.

clb2.private void btnGetChecked_Click(object sender.Items. i < checkedListBox1. i++){ if (IsItChecked(checkedListBox1.Count. int theIndex) { if (clb. For example.Clear().Items. System. } } } When the iteration is complete.CheckState.EventArgs e) { for (int i = 0. System.i)) { clb2.Checked). the checked items have been added to the new box (Figure 4.Add(checkedListBox1. . Figure 4.6: You can loop through the CheckedListBox to retrieve all checked items. This procedure uses the IsItChecked function.Clear(). to delete the items in a pair of CheckedListBoxes and a ListBox: private void btnClear_Click(object sender.EventArgs e) { checkedListBox1. } } Clearing an Item It's easy to delete a collection of items using the Clear method. Here it is again: private bool IsItChecked(CheckedListBox clb. } else { return false.Items. which you'll recall I first showed you in the section on retrieving text.Items.GetItemChecked(theIndex) == true){ return true.Items[i].6).

the RemoveAt method is used.Remove(checkedListBox1.Text) >= 0) && (Convert.Remove(txtAdd.listBox1. using the same procedure used to populate the CheckedListBox: private void btnPopulateLB_Click(object sender. int size) { string[] myarray = new string [size].Text) <= checkedListBox1.Text).SelectedItem). Similarly. deletes the corresponding item: if ((Convert. This code checks to see that a given index is within the count of the items collection and. First.RemoveAt(Convert. we'll need to add a ListBox to the project and set its SelectionMode property to MultiSimple.ToInt16(txtIndex. if you want to delete an item from the items collection by position (index).Items.Text)).Items.EventArgs e) { AddToList(listBox1.Clear(). if you know the text of the item you want to delete: checkedListBox1.Count)) { checkedListBox1.ToInt16(txtIndex. i < size. if it is.Items. } Deleting Items You can delete an individual item from a CheckedListBox if it is selected using the Remove method of the Items collection: checkedListBox1. } In case you don't remember. } Retrieving Multiple Selections Retrieving multiple selections from a ListBox works in the same way as retrieving multiple checked items from a CheckedListBox (see 'Retrieving Multiple Checked Items' earlier in this chapter). We can populate it.ToInt16(txtIndex.30).Items.Items. here's the AddToList method: private void AddToList(ListBox lb. for (int i = 0. System. On the other hand. i++) { .

and very useful. } lb.AddRange(myarray). populate the ListBox. Figure 4.Count. and click the Get Selected button.myarray[i] = "Item # " + (i+1).7).2.Items. run it.Text). i++){ clb2. The ListBox operations performed in this section are shown in Listing 4. select some items.Add(listBox1.SelectedItems[i]). adding items to a 'results' CheckedListBox for each selected item in the ListBox: private void btnGetSelect_Click(object sender. } } If you save the project.EventArgs e) { for (int i = 0.EventArgs e) { checkedListBox1.Items.Items. and their respective Items collections are lots of fun .SelectedItems. the selected items will be displayed in the new CheckedListBox (Figure 4.ToString().Add(txtAdd. CheckedListBoxes. } . I'm sure you'll agree that ListBoxes. System. we can iterate through the ListBox. System. i < listBox1. } Finally.7: You can have lots of fun with ListBoxes (retrieving all selected items shown). Listing 4.2: Fun and Games with CheckedListBoxes (ListBox Operations) private void btnAdd_Click(object sender.

MessageBoxIcon. checkedListBox1.EventArgs e) { if ((Convert. MessageBoxButtons.Text) <= checkedListBox1.Items. } else { MessageBox. txtAdd.EventArgs e) { AddToList(checkedListBox1.SelectedItem.OK.ToInt16(txtIndex.Information).Text.ToInt16(txtIndex.Show("Checked"). MessageBoxIcon. System. "Please try again". } } else { MessageBox. for (int i = 0. MessageBoxIcon.ToString(). 10).Exclamation). MessageBoxButtons.Items. checkedListBox1.Show("Selected Item Text: " + // checkedListBox1.EventArgs e) { if (checkedListBox1. } private void AddToList(ListBox lb.Count)) { checkedListBox1.OK.Text). } lb.AddRange(myarray). "No text today".private void btnAddArray_Click(object sender.Items.Show("Nothing selected!". System.Text) >= 0) && (Convert. if (IsItChecked(checkedListBox1.Information). "Here is your text". System. } else { MessageBox. int size) { string[] myarray = new string [size].Show("Unchecked").OK. } } private void btnSelect_Click(object sender.Text). MessageBoxButtons. i < size.Text != ""){ MessageBox. } private void btnInsert_Click(object sender. } } .Show("Out of Range".Insert(Convert.SelectedIndex)) { MessageBox.ToString(). i++) { myarray[i] = "Item # " + (i+1).ToInt16(txtIndex.

Remove(txtAdd.EventArgs e) { MessageBox.OK. } } private bool IsItChecked(CheckedListBox clb.Items.Count)) { // checkedListBox1. } else { MessageBox.Clear(). System.Text) >= 0) && // (Convert. System.ToInt16(txtIndex. } else { return false.Items.Text) <= checkedListBox1.Text)].Text)).GetItemChecked(theIndex) == true){ return true.Items.ToInt16(txtIndex.Items.Show("Checked").Items.RemoveAt(Convert.EventArgs e) { checkedListBox1. System.Remove(checkedListBox1. MessageBoxButtons.private void btnRetrieve_Click(object sender. // } // selected // checkedListBox1.Clear(). } private void btnRemove_Click(object sender.ToInt16(txtIndex. "Here is your text".Items. } private void btnGetChecked_Click(object sender.EventArgs e) { .Information).ToInt16(txtIndex.ToString().EventArgs e) { // by index // if ((Convert.ToInt16(txtIndex.SelectedItem). clb2. listBox1. } } private void btnClear_Click(object sender. // by text checkedListBox1.Text).Clear().Text))) { MessageBox. if (IsItChecked(checkedListBox1. int theIndex) { if (clb.Items. Convert. System.Show("Unchecked").Items[Convert.Show (checkedListBox1. MessageBoxIcon.

Items. With the MainMenu control sitting on the tray. you can go ahead and add menus . by entering the appropriate information in position (Figure 4.Items.Checked). the instance of the control added to the form appears on the tray beneath the form. System.7 with its 10 clunky buttons. using the tools supplied by Visual Studio.i)) { clb2.8). the high .level menu items across the top of the form . i < checkedListBox1. i++){ clb2.30). System. } private void btnGetSelect_Click(object sender. drag a MainMenu control to your form.EventArgs e) { for (int i = 0.' it is the one shown in Figure 4.CheckState.Count. } } Menus If ever a user interface cried to heaven.SelectedItems. i++){ if (IsItChecked(checkedListBox1.for (int i = 0.Add(listBox1.Items[i].Count.Items.that is.and menu items. } } } private void btnPopulateLB_Click(object sender. Like the Timer. which are the items beneath each menu. So let's put this user interface out of its misery. .EventArgs e) { AddToList(listBox1. i < listBox1. and give it a menu already! To create a menu visually. 'Give me menus.Add(checkedListBox1.SelectedItems[i]).

The application will have four menus running along the top of the form: Add. and so on. and then check out the autogenerated menu code that Visual Studio has created on our behalf. Ma. . Delete. Let's use the visual interface to add some menus and menu items. This means that no new code needs to be written to make these menus functional: mnuAdd's click event handler is assigned to the (already existing) handler for btnAdd's click event. no code' theme of the menu designer. Retrieve.1 (with access keys ignored). I'll just look at the first menu.1: Add (mnuAdd) Menu Items Names and Text Menu Item Name mnuAdd (parent menu) mnuAddItem mnuAddItembyIndex mnuAddArray Text Add Item Item by Index Array of Items In keeping with the RAD 'look. and ListBox. the menu insertion is performed in Edit Names mode. which as you can see in Figure 4.9). The menu item names and text are as shown in Table 4. you should note that the Events tab of the Properties window can be used to assign menu item click events to button click event handlers (Figure 4.clicking and selecting Edit Names from the context menu. you can edit the menu visually (shown here in Edit Namesmode). the Add menu. Table 4.Figure 4.8 is in the normal place of a File menu.8: When you add a MainMenu control to the tray. which allows you to edit in place the internal names of the menu items you are adding. To keep things simple. By right .

the MainMenu and each of the MenuItems are instantiated: this.mnuDelete.mnuAdd..mnuAdd.MenuItem mnuAddItem. The Auto-Generated Menu Code What code has been created for us to implement the menus we've visually added? To start with. we'll look at mnuAdd only. Next.Windows.MenuItems.Forms.Forms.. ignoring the other three menus.Windows.AddRange(new System.Windows.9: Menu item events can be assigned an existing handler using the Events tab of the Properties window.MainMenu().MenuItem().Forms. .Forms. this.mnuRetrieve. the main menu collection of menu items is created.Forms.Windows.MainMenu mainMenu1. so you will have to expand the region to view the action..mnuAddItem = new System. Next. Note: Most of the action takes place within the 'hidden' region of Windows Form Designergenerated code. .Windows.MenuItem mnuAdd.MenuItem[] { this.Forms.mnuListBox}).mainMenu1 = new System. private System. this.mnuAdd = new System.level menu and uses the AddRange method of its menu items collection to add its contents: this. this. along with MenuItem variables for each of the menus and menu items: private System.MenuItem(). . Retrieve. using the collection's AddRange method to add the four top . a variable of type MainMenu has been declared.mainMenu1. this.Windows. Delete.Figure 4. As promised. private System.Index = 0. Here's the code that positions mnuAdd as the first top . and ListBox): this..Windows.Forms.level menu items (Add. this.

Form { . this.Windows. private System.3: Auto-Generated Menu Code (Excerpted) public class Form1 : System.MainMenu().Forms.mnuRetrieve..MenuItem(). this.. this. this.Windows.Windows. ..mnuAddItem.Forms. this. .Forms. // within Initialize Component. this.mnuAddItemByIndex.mainMenu1.mainMenu1 = new System.mnuAdd = new System.mnuAdd.MainMenu mainMenu1.btnAdd_Click).MenuItem[] { this.mnuAdd.mnuAddArray}).Windows.Forms. text and event handlers) fleshed out: this. but not least.MenuItem mnuAdd.Forms. and its properties (e.3 shows the relevant portions of the auto .this. As a last.Windows.mnuAddItem.MenuItem[] { this.Forms.Forms.Windows. the whole menu construction is then assigned to the Menu property of the form: this.Text = "&Item". Listing 4. Finally. called by Form1 constructor this.Windows.MenuItem().AddRange(new System.Menu = this.Click += new System. this.mnuAddItem. private System.Forms.mnuAddItem.generated menu code.Windows.mnuListBox}). required step.mnuDelete.MenuItem mnuAddItem.g..MenuItems.MenuItems. private System.Index = 0.AddRange(new System. // // mainMenu1 // this.EventHandler(this.mainMenu1. this. this.. each menu item below mnuAdd is given a position by Index in mnuAdd's collection. Listing 4..Windows. .mnuAddItem = new System.Forms..

this.MenuItems. Let's construct a menu in code for a completely new application that will use a RichTextBox control and the common dialog wrappers to build a Notepad . // // mnuAddItem // this. this.Click += new System. you may find it less work to construct menus in code yourself rather than relying on Visual Studio for auto ..MenuItem[] { this.Windows.mnuAddItemByIndex.// // mnuAdd // application. this.mnuAddItem.. Based on this.Text = "&Item". use the collection Index property to assign a location on the menu.. position it in the collection using the Index property.mnuAdd.btnAdd_Click). Use the AddRange method of the MainMenu's menu item collection to add the toplevel menus. depending on your preferences.mnuAddItem. this.AddRange(new System.generated one.mnuAddItem.generation. } Analyzing what the auto . this. Table 4. Ultimately.level menu.mainMenu1. . and then use its collection's AddRange to add its own menu items.mnuAddItem. the general process is: • • • • • Declare and instantiate a MainMenu and MenuItems for each menu item.2 shows the menus that we need.generation does. Table 4.EventHandler(this.mnuAdd. For each menu item. Assign the whole menu structure to the Menu property of the form. For each top .2: 'Notepad' Application Menus File Edit Format ..Index = 0.Index = 0.mnuAddArray}). and assign properties such as text and handlers. this. .Menu = this.Forms. it certainly looks like we could pretty easily construct a menu ourselves in code doing at least as well as the auto .

within the form's constructor. This would create the ability to be . mnuFileOpen.MenuItems[0]. MenuItems[0]). In this code.(separator) Exit (mnuFileExit) Cut (mnuEditCut) Copy (mnuEditCopy) Paste (mnuEditPaste) Font (mnuFormatFont) Color (mnuFormatColor) First. The key concept is that a MenuItems collection can itself contain a MenuItems collection! Listing 4. EventArgs e){ } A shortcut key is also assigned to mnuFileOpen. be improved by substituting variables for the hard .4 could. go ahead and add the items to the File menu's menu items collection. instantiate a MainMenu object and assign it to the form's Menu property: Menu = new MainMenu(). mnuFileOpen.10. effectively.MenuItems.Add(mnu). the omitted this is implied.MenuItems.4 shows the completely fleshed . A click handler is assigned to it.Open (mnuFileOpen) Save (mnuFileSave) . Next. mnuFileOpen is Menu. The File menu will be referred to as Menu. and add it to the main menu's MenuItems collection: MenuItem mnu = new MenuItem("&File"). starting with the Open menu item: MenuItem mnuFileOpen = new MenuItem("&Open"). Note: The above statement is equivalent to this. perhaps. declare and instantiate a File menu item. In other words. mnuFileOpen.CtrlO. is declared and instantiated..out menu. a new menu item. Menu.MenuItems[0]. this is a fully functional menu. Now.Add(mnuFileOpen). As you can see in Figure 4. up to all factory specifications. The code shown in Listing 4.MenuItems[0]. Menu. the Add method for the File menu collection of items is used to add the item (so. Finally.Shortcut = Shortcut.coded index values. so you have to create the framework for the event handler: void mnuFileOpen_Click(object sender.Click += new EventHandler(mnuFileOpen_Click).Menu = new MainMenu().

10: You can easily create fully functional menus in code.generate it.Add(mnuFileOpen). Figure 4. mnuFileOpen. // File Save MenuItem mnuFileSave = new MenuItem("&Save"). // File menu MenuItem mnu = new MenuItem("&File").Add(mnu).CtrlO. Menu.easily flexible about menu order in the future.MenuItems.Click += new EventHandler(mnuFileOpen_Click).4: Coding a Menu by Hand // Form1 constructor public Form1() { InitializeComponent().MenuItems. Listing 4. // Add Menus Menu = new MainMenu(). mnuFileSave. mnuFileOpen.MenuItems[0]. // File Open MenuItem mnuFileOpen = new MenuItem("&Open"). you should be aware of what the menu designer is doing 'under the hood' in case you need to tweak it. However. at the least. A menu is probably easier to maintain if you create it in code this way than if you auto . the choice is yours.Click += new EventHandler(mnuFileSave_Click).Shortcut = Shortcut. Menu. .

Shortcut = Shortcut.MenuItems[0]. mnuEditCopy. // Edit Paste MenuItem mnuEditPaste = new MenuItem("&Paste").Add(mnuFileExit).Click += new EventHandler(mnuEditCopy_Click).MenuItems[0]. Menu.MenuItems.MenuItems. // Format mnu = new MenuItem("&Format").CtrlF.Click += new EventHandler(mnuFormatFont_Click). mnuFileExit. // Edit mnu = new MenuItem("&Edit").MenuItems.Add(mnuEditCut). Menu.mnuFileSave. Menu. // Edit Cut MenuItem mnuEditCut = new MenuItem("Cu&t"). mnuEditPaste. Menu. .MenuItems[1].Add(mnuFileSave).Shortcut = Shortcut.CtrlV. // Edit Copy MenuItem mnuEditCopy = new MenuItem("&Copy").Shortcut = Shortcut. Menu. // Format Font MenuItem mnuFormatFont = new MenuItem("&Font").Shortcut = Shortcut. Menu.MenuItems. // File Exit MenuItem mnuFileExit = new MenuItem("E&xit").MenuItems.CtrlS.Add(mnu). mnuFormatFont. mnuEditPaste.MenuItems. Menu.AltF4.Shortcut = Shortcut.Click += new EventHandler(mnuEditPaste_Click). mnuEditCut.CtrlX. Menu. mnuEditCut.Add(mnu).Click += new EventHandler(mnuEditCut_Click).MenuItems[1]. // separator mnu = new MenuItem("-").Add(mnuEditPaste).MenuItems[1].Add(mnuEditCopy). mnuEditCopy. mnuFormatFont.Click += new EventHandler(mnuFileExit_Click).CtrlC.Shortcut = Shortcut.MenuItems[0].MenuItems.MenuItems.Add(mnu). mnuFileExit.

NET common dialog controls to display these dialog application that allows users to: open and save rich text files. // Format Color MenuItem mnuFormatColor = new MenuItem("&Color"). let's build an application around the menu.MenuItems. the appearance of your applications becomes standardized. By using the .Shortcut = Shortcut. mnuFormatColor..MenuItems[2]. Doing the Common Dialog Thing Now that we have a menu. Users see dialog boxes that they recognize and already know how to use. EventArgs e){ } void mnuEditCut_Click(object sender. copy. EventArgs e){ } void mnuEditPaste_Click(object sender. Common dialog controls provide a 'wrapper' for dialogs that perform commonly needed tasks. Common dialog controls will provide users a way to make choices. which means that they provide easy access to the functionality of standard Windows dialog boxes. EventArgs e){ } void mnuFormatColor_Click(object sender.Add(mnuFormatColor). and paste selected text to and from the clipboard.the same dialog box you see in Microsoft Word and other applications. The primary user interface will be provided by a RichTextBox control.Add(mnuFormatFont). } . This will be a Notepad . EventArgs e){ } void mnuFileExit_Click(object sender. Menu. . and change the color and font characteristics of selected text. EventArgs e){ } . For example. cut.MenuItems.MenuItems[2]. EventArgs e){ } void mnuFileSave_Click(object sender..Menu.CtrlShiftC.. void mnuFileOpen_Click(object sender. created in code. the SaveFileDialog control displays the standard Save As dialog for naming and locating a file to be saved .Click += new EventHandler(mnuFormatColor_Click).. mnuFormatColor. EventArgs e){ } void mnuFormatFont_Click(object sender. EventArgs e){ } void mnuEditCopy_Click(object sender.

5: An Edit Menu . It's very easy to implement functionality using the methods of the RichTextBox. add ColorDialog. meaning it will take up the entire client area of the form and change size with the form.5. You should also know that the Clipboard can alternatively be programmed using the members of the Clipboard class. Set its Dock property to Fill. Listing 4. But you will need to know how to work directly with files (see Chapter 10. OpenFileDialog. Each control appears on the tray beneath the form (Figure 4. although the common dialog controls show dialog boxes allowing the user to make choices.However. Implementing an Edit Menu Next. copy. To get the ball rolling. shown in Listing 4. Name it rtb and clear its Text property. and paste . FontDialog. use the methods of the RichTextBox to implement the items on the Edit menu . the SaveFileDialog control doesn't save the contents of a file after the user chooses to save a file. they don't actually do the work.11). 'Working with Streams and Files'). and SaveFileDialog controls to the form (these are the common dialog controls the project will implement). For example. add a RichTextBox control. and saving and opening files. in the same project with the hand .11: The form in design mode with common dialog controls in the tray.cut.created menu. Figure 4. working with the Clipboard. so that we can concentrate on the common dialogs. We'll use the methods of the RichTextBox control to do the actual work of changing the characteristics of selected text.

AnyColor = true.manipulation controls . By setting the AllowFullOpen property of the ColorDialog control to true. You'll find that the TextBoxBase class has other useful methods that could be used to extend an Edit menu . the user can use the Color dialog to define custom colors. EventArgs e){ rtb. } void mnuEditCopy_Click(object sender. TextBoxes share most of the same functionality. if not. colorDialog1. The Color Dialog Within the click handler for the Color menu item.Paste().Copy().AllowFullOpen = true.void mnuEditCut_Click(object sender. Next. only solid colors .so. display the dialog (see Figure 4. EventArgs e){ rtb. } void mnuEditPaste_Click(object sender.Cut(). which implements the core feature set of text . If the AnyColor property is set to true. } These editing methods are inherited by the RichTextBox class from the TextBoxBase class. for example.those with 100% opacity . Redo and Undo methods. EventArgs e){ rtb. configure the Color dialog: colorDialog1.ShowDialog().are selectable.for example. the dialog displays all available colors. also via inheritance from TextBoxBase. .12): colorDialog1.

rtb.SelectionColor = colorDialog1.SelectionFont = fontDialog1. you can set its characteristics in advance of invoking it.13). perhaps to reapply it later to some text.Color. the Font dialog is displayed (Figure 4. as in Figure 4.ShowDialog().Color.Figure 4. } When the user selects some text and chooses the Font menu item.12: The Color common dialog is used to set the color of the selected text. If you'd like. . as in the example: void mnuFormatFont_Click(object sender. Font choices made in the dialog are applied to the selected text. set the selected text to the color chosen in the dialog: rtb. Note: that you might want to store the user selection of color. for example: Color theColor = colorDialog1.Font. To do this. The Font Dialog The Font dialog works in much the same way as the Color dialog. Finally. or you can just use the default.14. you could use a variable to capture the user's selection. EventArgs e){ fontDialog1.

Figure 4. The Save As and Open Dialogs The SaveFileDialog control allows the user to pick a file name and location for saving a file via the standard Save As dialog.13: The Font common dialog is used to set the font and related attributes of the selected text. for more general . The OpenFileDialog control lets the user select a file for opening using the Open dialog.Figure 4. We'll let the RichTextBox do the lifting here. To say it once again: designating the file is all these dialogs do.14: Changes to the text formatting are displayed in the RichTextBox. they don't do any of the actual work of saving or loading.

other than the suffix. Next. As a first order of business.*) | *. And here's another item: All Files (*. set some of the initial characteristics of the Save As dialog. SaveFileDialog1.ExecutablePath. even when you find one. you can't be absolutely sure what it should look like). In the example above.information about working with files. that is dear to my heart!).DefaultExt = "rtf". followed by a pipe. This sets the initial directory to the application's executable path (in the case of a C# application running in the development environment. followed by the file suffix. Saving the Contents of a RichTextBox It's really very easy to use the SaveFileDialog to implement saving the contents of the RichTextBox. Note that the functionality for saving the rich text contents is implemented before the functionality for opening it. set the initial file name to Harold. saveFileDialog1. Each item consists of a description.InitialDirectory = Application. openFileDialog1.rtf. Finally.rtf) | *. formatting and all.*) | *. usually using wildcard characters. SaveFileDialog1. First.Filter = "Rich Text Files (*. set a filter to determine the choices that are available to the user in the Save As Type drop down list.*". see Chapter 10.rtf) | *.rtf (a name. It's generally easier to do things in this order rather than looking around for an RTF file (and. this is the \bin\Debug directory that stores the application's EXE file).* . Another pipe is used to start the next item. The Filter property is a text string delimited by the pipe character (|). the following is one item: Rich Text Files (*.rtf It is the description followed by the specification.FileName = "Harold". we'll save the contents of the RichTextBox and then retrieve its contents.rtf|All files (*. namely *.

rtf in the \bin\Debug directory. Initializing the OpenFileDialog works in the same way as initializing the SaveFileDialog: openFileDialog1. with the file name selected by the user as its argument: rtb. the user can pick an existing file.ShowDialog().DefaultExt = "rtf". Warning: Be careful not to include extra spaces between the end of one item and the beginning of the next. show the dialog: SaveFileDialog1.*. Accept the file name. Next. type. Otherwise. *. The Save As dialog will open. the filter may not work properly. openFileDialog1. Next. Click Save. A file with the rich text contents will be created at the indicated location. The contents of the RichTextBox are saved using its SaveFile method.SaveFile(SaveFileDialog1. run the program and enter some heavily formatted text in the RichTextBox. To verify this. openFileDialog1. Setting OverwritePrompt to true causes a message with a warning to appear but allows the user to proceed if they still want to. possibly resulting in overwriting its contents. you can locate the file and open it in Microsoft Word.FileName). Retrieving Rich Text from a File The next step is to create the code that will load rich text into the RichTextBox in the application. and location Harold. To see that this works. Warning: Unless you set the OverwritePrompt property of the SaveFileDialog to true (either in code or the Properties window).ExecutablePath.FileName = "Harold". displaying files of all types. choose File > Save. and location suggestions.InitialDirectory = Application.Filter = . type. To do this.This has the specification. we will display an Open dialog using the OpenFileDialog control. suggesting a file name. openFileDialog1.

and for setting the selected text color and font. When a file is selected and the Open button clicked.15: The contents of the RichTextBox can be saved and then opened. EventArgs e){ openFileDialog1.FileName = "Harold". openFileDialog1.6. Listing 4.6: Opening.ExecutablePath. is shown in Listing 4. the Open dialog will allow a choice of a file (Figure 4.*) | *.rtf) | *.15). we then need to show the dialog and use the LoadFile method of the RichTextBox to load the contents of the file selected by the user into the control: OpenFileDialog1.FileName). the Filter property works the same way in the OpenFileDialog and the SaveFileDialog. the file is loaded into the RichTextBox.rtf|All files (*. openFileDialog1.InitialDirectory = Application.*". and Setting Font and Color with the Common Dialogs void mnuFileOpen_Click(object sender. When the user selects File > Open from the menu. Saving. rtb.Filter = . openFileDialog1."Rich Text Files (*. Note: As you probably know. Figure 4.ShowDialog(). The complete code for opening and saving Rich Text files. With the OpenFileDialog's properties set.LoadFile(OpenFileDialog1.DefaultExt = "rtf".

saveFileDialog1. Be that as it may. saveFileDialog1. rtb.rtf|All files (*. rtb. } void mnuFileSave_Click(object sender. whatever their type.ShowDialog().ShowDialog(). It is perhaps the case that MDI applications are no longer very stylish.SaveFile(saveFileDialog1. it's easy to rig MDI applications in .Font.InitialDirectory = Application."Rich Text Files (*.rtf|All files (*. colorDialog1. rtb. .rtf) | *.ExecutablePath. } void mnuFormatColor_Click(object sender.Color.FileName).and you should know how. EventArgs e){ colorDialog1.LoadFile(openFileDialog1.AnyColor = true.rtf) | *. The thinking is that it probably makes just as much sense to open multiple copies of a single application as to have one application with multiple client windows. or parent form. there is one MDI form.*) | *. } MDI Applications In Multiple Document Interface (MDI) applications.FileName = "Harold".FileName). colorDialog1. openFileDialog1. } void mnuFormatFont_Click(object sender.*) | *. As an example of how to 'wire' an MDI application.ShowDialog(). There are usually many MDI child forms.DefaultExt = "rtf".ShowDialog().SelectionFont = fontDialog1.Color.*". let's turn the Notepad applet created in the last section into the child form of an MDI application.NET . There can be more than one type of MDI child form. saveFileDialog1. EventArgs e){ saveFileDialog1.AllowFullOpen = true. // Color theColor = colorDialog1. rtb.SelectionColor = colorDialog1.*". but all children. must fit into the client space of the parent MDI window.Filter = "Rich Text Files (*. EventArgs e){ fontDialog1. saveFileDialog1.

16). Right. Add a panel to the bottom of the form. Choose Project > Add Windows Form to open the Add New Item dialog (Figure 4. The new parent form will be added to the project. Next. Open frmParent in its designer. Use the Properties window to set the Anchor property of btnShowChild to Bottom. make sure Windows Form is selected. use the Properties window to set the IsMDIContainer property of frmParent to true (Figure 4. and a button. .cs. In the Add New Item dialog. to the right side of the panel (the bottom . and click Open. btnShowChild.17). name the form frmParent.16: The Add New Item dialog is used to add a 'parent' form to a project.The first step is to add a new form (to serve as the parent) to the project.right of the form). Figure 4.

You need to give frmParent one now: [STAThread] static void Main() { Application.17: Setting the form's IsMDIContainer to true means that it can become a parent. Form1. } The Run method of the Application object is invoked with the constructor method for the frmParent class. the basis for the Notepad applet. had a Main method. Because the original form in the project. Note: The [STAThread] attribute tells the Common Language Runtime that a single . you'll note that the client area of frmParent shifts in color. becoming a darker shade of gray.Run(new frmParent()). Visual Studio didn't automatically give frmParent a main method.Figure 4. In order for a form to be a Startup Object. Setting the Startup Object The next step in helping frmParent to become a parent is re . it needs a Main method. Tip: When you set IsMDIContainer to true.threaded .jiggering the application to let it start from frmParent.

18: If there are multiple modules with entry points . Or. This goes in the click event handler for btnShowChild.clicking. the mechanism for displaying the child form needs to be created. the MdiParent property of frmChild is set to the current form. is instantiated: Form1 frmChild = new Form1(). Figure 4.Run(new Form1()).by selecting the project in Solution Explorer. frmParent: . you can open the project Property Pages dialog . of type Form1.18). Next. right . the application will start by default from frmParent. and choosing Properties from the context menu . Form1 still has its own Main method: [STAThread] static void Main() { Application. First.and explicitly set frmParent as the Startup Object using the General tab (Figure 4.apartment model is being used.Main methods .then the Startup Object is set using the project's properties pages. a variable named which case. } You now have a choice: you can delete the Main method from Form1 . Displaying Children With the application set to open frmParent first off.

Windows. which was declared at the class level.7: Adding Child Forms . the new instance of Form1 is shown: frmChild.Windows.7 shows the frmParent class declaration. Finally.Button btnShowChild. The counter. to indicate which child it is: frmChild..Form { private System.EventArgs e) { Form1 frmChild = new Form1(). the counter variable.Forms..Show() Listing 4..Show(). is iterated: i++.Forms. int i = 1. frmChild. i. public class frmParent : System..Panel panel1..MdiParent = this.. . i++.frmChild. The child form is given text for its caption bar using a counter variable.ToString().MdiParent = this. System. and the click event handler for displaying child forms..Windows. frmChild. its Main method. .. } . [STAThread] static void Main() { Application.Forms. frmChild.ToString().Text = "I am child #" + i.Run(new frmParent()). Listing 4. private void btnShowChild_Click(object sender. private System. } .Text = "I am child #" + i.

by selecting one from the list on the MDI Window menu. The MDI Window is constructed in the MDI container . sets up the MDI list of child windows.8.level menu's MdiList property to true: mnu.MergeOrder = 3.Add(mnuCascade).Add(mnu). Menu. mnu.MenuItems.MdiList = true. . Listing 4. Note that setting a top . Aesthetically.8: An MDI Window Menu Menu = new MainMenu(). as shown in Listing 4. Menu. in our example. // Tile Horizontal MenuItem mnuTileH = new MenuItem("&Tile Horizontal"). You can create it by adding a MainMenu control to the form and using the visual menu designer .The MDI Window Menu A crucial part of the look and feel of an MDI application is a special MDI Window menu.frmParent.8 merges with the menu belonging to a child form when the child is displayed. // Cascade MenuItem mnuCascade = new MenuItem("&Cascade"). // MDI Window menu MenuItem mnu = new MenuItem("&Window"). mnu. It's also worth observing that the menu shown in Listing 4.or by constructing it entirely in code.Click += new EventHandler(MDImenu_Click).MdiList = true. mnuCascade. This is controlled by the MergeOrder property: mnu.MenuItems. it's also conventional and convenient to include the ability to arrange the children forms (in several ways) on the MDI Window menu.MergeOrder = 3. This menu displays a list of all open MDI children and places a check mark next to the currently active child window.MenuItems[0]. It allows the user to navigate between child windows. the child menu should come before the Window menu. In addition.

The LayoutMdi method of the parent form is then used with the appropriate argument to arrange the child windows as shown in Listing 4. Menu. EventArgs e) { MenuItem mnu = (MenuItem) sender. break.MenuItems[0]. // Tile Vertical MenuItem mnuTileV = new MenuItem("Tile &Vertical"). break.LayoutMdi(MdiLayout..MenuItems.9: Implementing MDI Window Functionality void MDImenu_Click(object sender. for each choice.Add(mnuArrange). Listing 4.TileVertical).. has the job of determining which menu item invoked it by casting the sender parameter to a MenuItem and then checking its Text property: MenuItem mnu = (MenuItem) sender.Click += new EventHandler(MDImenu_Click). mnuTileV. .8 does not implement functionality in child window arrangements. // Arrange Icons MenuItem mnuArrange = new MenuItem("&Arrange Icons").Click += new EventHandler(MDImenu_Click).Add(mnuTileV). case "Tile &Vertical": this. it merely invokes a common event. The MDIMenu_Click event handler. mnuArrange.9.9. case "&Tile Horizontal": this.mnuTileH.MenuItems.Cascade). switch (mnu. Menu. switch (mnu. MDImenu_Click.LayoutMdi(MdiLayout.Text) { case "&Cascade": this.Text) { . shown in Listing 4.MenuItems[0].MenuItems[0].Click += new EventHandler(MDImenu_Click). Menu. Note: that the menu shown in Listing 4.MenuItems.Add(mnuTileH).LayoutMdi(MdiLayout.TileHorizontal). break.

and the previous one . more generally.NET Framework to create functional and exciting user interfaces.' we'll move on to have a look at the organization of the classes within the . 'Reflecting on Classes. break. are there patterns of practice that are useful in constructing class . Conclusion This chapter .based applications? What is the vocabulary used to notate these patterns and how are they recognized? . Visual Studio. Figure 4.19: It's easy to create MDI applications.19). Using this organization as a model of clarity leads to an interesting and fruitful field of speculation: what are the best ways to organize classes? And. In Chapter 5.LayoutMdi(MdiLayout. you'll see that it is fully functional within normal parameters for an MDI application (Figure 4.ArrangeIcons).NET Framework. and the .has explored using Visual C#. } } If you run the "&Arrange Icons": this.

Assemblies can contain many namespaces. Certainly.NET classes will help you organize for maximum usability.NET Framework. can contain other namespaces.NET Framework for functionality and can only run through the grace of the Common Language Runtime (CLR). . It's time to have a look at the connectedness of C# programs that you write to the class libraries that make up the . and more for a .' wrote seventeenth century poet John Donne.NET application. which is used to glean information about the types that make up a compiled program. Every time you build an executable (EXE) or a library (DLL) file in . in turn.NET namespaces • Reflection • Working with the Class View window and the Object Browser • Creating a class library No man is an island. to avoid ambiguity. This famous epigram equally applies to program code . Namespaces are used to make it easier to refer to items. internally and for deployment. and quite likely invoking run . version control.NET code. on an operating system. you are creating an assembly. When it comes time to deploy your own class libraries.alone executable files or DLL libraries. In other words.NET Framework organized? I explain how to use the Object Browser. assemblies are deployable units of code that correspond to stand . C# code has been presented basically as separate and apart .NET program has at least one related assembly. Assemblies and Namespaces Assemblies are the fundamental unit for deployment. the best discovery tool for exploring the . I also explain reflection. Namespaces are used to organize the classes within assemblies. this information can be used dynamically while the program in question is running. as it were. Each compiled C# .time libraries of code. after compilation. which.NET. This chapter explores the organization of programs. following the design guidelines for . security. which uses the class libraries of the .Reflecting on Classes Overview • Assemblies and namespaces • Using . How are the classes in the . and to simplify references when large groups of classes are an island. So far in this book.NET Framework. this is true of C# .it runs in a context.

it is the basis of an assembly. Figure 5.NET applications don't depend on Registry values. some of the general manifest information is contained in a file that is part of the project. Figure 5.cs.The Assembly Manifest When you start a new C# project. In C#. listing and describing the files that make up the assembly • An assembly reference list.1 shows a small project in the Visual Studio Solution Explorer and shows the contents of a sample AssemblyInfo. . which is a catalog of external dependencies The external dependencies in the assembly reference list may be library files created by someone else.1: Each C# . The assembly manifest can be thought of as a 'table of contents' for an application. as part of the executable or library. version.cs file when opened with the Visual Studio editor. named AssemblyInfo.NET Framework. or a class within an assembly. Within each built assembly is a manifest. The manifest carries information about content. and dependencies so that C# . Assembly References To use an assembly. and it's likely that some of them are part of the .NET project includes a file that forms the basis of the assembly manifest. It includes the following information: • The assembly's name and version number • A file table. the assembly must be referenced in your project.

2: The Add Reference dialog is used to add a reference to .Depending on the type of project.2. although both do reference certain important . you'll find that many of the assemblies that are part of the . Right . as shown in Figure 5. Figure 5. Different project types have different default references. follow these steps: 1.) The Add Reference dialog will open. select the References node in Solution Explorer.dll. and select Add Reference from the context menu. (Alternatively.NET Framework are referenced by default. The references that come 'out of the box' for a Windows forms project are not the same as those for a web forms project. Select Project > Add Reference. You can see which assemblies are already referenced in a project by expanding the References node in the Solution Explorer: If you need to reference an assembly that is not already included in your assemblies such as System.

Namespaces If you drill down one step below the assembly level.NET Framework uses a dot operator (. Reading left to right. that is part of the Windows namespace. (What can get a little confusing is that sometimes a namespace and an assembly can have the same name. although you can change this default name. click the Browse button in the upper . For example System.a project. 2. For example. is the namespace name. 3.type in the System namespace.NET tab.2). that is part of System. so that they can be more easily found. Click OK to add the reference to the . you'll find that the members of a given assembly are namespaces. In other words. Microsoft. Select a .MessageBox designates the MessageBox class within the Forms namespace. Alternatively.) syntax to designate hierarchies.NET tab).Boolean designates a Boolean value .Forms. You should also know that namespaces can span multiple assemblies. every executable file you create in C# .right corner of the Add Reference dialog. is the type name. to the right of the final period. Locate the assembly to be added and click Open. It contains classes that support compilation and code generation using the C# language. Click the Select button to add the assembly to the Selected Components list (shown at the bottom of Figure 5. the first part of a type.NET contains a namespace with the same name as your project. Related types are grouped into namespaces.Windows.NET component.NET assembly to add (listed in the Component Name column of the . The assembly will be added to the Selected Components panel of the Add Reference dialog. .CSharp is a namespace that is part of the System assembly. System. then myNameSpace is treated as a single set of names. up to the first dot.) By default. 4. Click OK to add the reference to your project. The Select Component dialog will open. if two assemblies both define classes within a namespace myNameSpace. if the assembly you want to add a reference to does not appear on the . Another way of putting this is that namespaces are the internal organization of the classes defined in an assembly. The last part of the name. The .

Windows. Namespace Directives You have several ways of referring to an object based on a class within a namespace once the assembly containing the class you are interested in has been referenced.Windows. shorthand name for a namespace (or a namespace plus a type belonging to the namespace).are contained in all .NET classes. possibly followed by types .NET Framework. This can save you a great deal of typing of long. Note: The using System directive will automatically be included in most C# modules. The using Directive You can use the fully qualified name of the item.Forms.such as GetType() and ToString() .NET Framework.NET type hierarchy and the ultimate parent (or superclass) of all classes in the . also called the Object class.Button btnClear. This class is the root of the . like this: private Button btnClear.Object. Alternatively. The Alias Directive An alias namespace directive allows a program to use its own. All base data types used by all applications are included in the System namespace or the Microsoft namespace. as in this variable declaration for btnClear: private System. After you add a using directive.Windows.Forms.Forms. One of the most important types within the System namespace is System. all of the names in the imported namespace can be used (provided they are unique to your project).Button btnClear will mean the same thing as System.As these examples suggest. internally assigned. This means that you don't have to explicitly invoke System: Windows. This implies that the members of the Object class .Button btnClear.and it can help make your code .Forms. you can place a using namespace directive at the beginning of a code module. the System namespace is the root namespace for all types within the . as shown here: using System. qualified namespaces.

MessageBoxIcon.cs using System. swf.Forms. For example: using swfb = System. } .Windows. Listing 5. namespace noman { namespace isan { namespace island { public class theClass { public theClass() { // No constructor code } public static void bellTolls (string inStr){ swf.Forms. Here's how it works. then the identifier can be used in place of the assigned namespace (and type). using swf = System.1: Nested Namespaces // Namespaces. The keyword namespace is followed by an identifier that is the name of the namespace. Curly braces enclose the contents of the namespace . Creating Namespaces It's truly easy to create a namespace..1 shows an example of namespaces nested three levels deep containing a single class. "For thee. the members of the namespace (see the following section).Button. there's no need to instantiate an object based on the class to use it).MessageBox.clearer. Namespaces can be nested within each other. Listing 5.OK. a corresponding variable declaration for a variable named btnClear of type Button could look like this: private swfb btnClear.MessageBoxButtons. If you add an identifier and an assignment to the using directive.meaning.. Given this directive. The class contains a single static method that displays a message box (because the method is static.Exclamation).".Windows.Show("The bell tolls for " + inStr + "!". swf.

the following will do the trick: private void btnGreeting_Click(object sender.EventArgs e) { noman. } Note: If the namespace code is compiled in an assembly external to the code trying to invoke the method.1 can easily be invoked.1).theClass.isan. namespace blocks may contain: • Other namespaces • Classes • Delegates • Enumerations • Interfaces .} } } } The static method shown in Listing 5.bellTolls("Dolly"). Somewhat more formally.island. Namespaces can encapsulate other namespaces (as shown in Listing 5. System. everything. but no other language element can encapsulate a namespace. Namespace Members Namespaces are at the pinnacle of the tree of C# language elements. So one way of answering the question of what can go in a namespace is. from within a form module button's click event. then a reference to the assembly must be added to the calling project. in the same project as the nested namespaces code module. For example.

in namespaces that are likely to be most important to C# .Drawing. queues.Drawing. strings. datatype conversion. 'The Life of the Object in C#. events.CSharp System Description Supports compilation and code generation using the C# language. exceptions. Consists mainly of the classes that comprise the ADO. Contains fundamental classes that define types. and dictionaries. System.1.Drawing including System. Includes a set of classes that lets you manage collections ofobjects. Provides classes used for debugging. such as lists. System.Data System. arrays. application environment management.Text provide more advanced and specific GDI+ graphics functionality). Contains classes and interfaces that provide type inspection and the ability to dynamically bind objects.Reflection. event handlers. 'Zen and Now: The C# Language. Contains classes that provide access to GDI+ basic graphics functionality (namespaces that are hierarchically beneath System. Generates assemblies on the fly.Diagnostics System.NET Namespaces Namespace Microsoft.IO Contains types and classes used for reading and writing to data streams and files.Reflection System.NET architecture.' . event logs.Collections System. and performance counters. and interacting with system processes.NET programmers are described in Table 5.Drawing2D and System. and general input/output (I/O) functionality. tracing. arrays.' and 8. mathematics. and much more. You'll find out more in this chapter and in Chapters 6.Emit . interfaces.• Structs • using and alias directives Note: If you don't know what these things are.NET Namespaces Some of the built .Drawing System. hash tables. don't worry. Table 5.1: Important .

Reflection Reflection is the ability to use the metadata provided by a program to gather information about its types. and the user clicks the Get Type Info button.Services System.based user interface. and more. Clicking Open in the common dialog displays the selected file in a TextBox. fields. When the Perform Reflection button is clicked.UI System. Contains the classes used to build and consume web services. System. Provides classes that support processing XML. the types in the selected file are displayed in a Types ListBox. explained later in this chapter.Web.Timer Provides the Timer component (see the section 'Round Buttons Dancing' in Chapter 4. To see how reflection works. the OpenFileDialog will allow the choice of a file for reflection.' for an example using the Timer). let's create a Windows forms application that will open and 'inspect' any . and events for the type are displayed in respective ListBoxes.Forms System.3. converting blocks of characters to and from blocks of bytes. .XML Contains the classes that are used to facilitate browserserver communication and other web . System. However. The information you can gather is not as extensive or as convenient to use as that provided by the Object Browser tool that ships with Visual Studio. properties.NET assembly (.exe or .Windows. When a type.RegularExpressions Contains classes that provide access to the . The application will provide an Open button. This user interface is shown in design mode in Figure 5. including automated and dynamic code and assembly generation. 'Building a Better Windows Interface.related functionality.dll file). with the ability to gather information about the internal program elements of compiled code on the fly. the constructors. or class. System. a great deal of advanced functionality becomes possible.NET assemblies. Contains the classes for creating a Windows . dynamic 'late bound' run .Web.time determination of what code needs to be executed. The example in this section will show you how to use reflection to gather type and member information about compiled . is selected.Text.System. and automated code documenters.Web System.Text Contains classes used for character encoding. When the user clicks this button. methods.NET Framework regular expression engine. Provides classes and interfaces used in the creation of the user interface of web pages and controls.

DefaultExt = "exe".Text =openFileDialog1. Here's the code that displays the Open dialog and loads the user's choice into the TextBox: private void btnOpen_Click(object sender.Filter = "Executable (*.including the running file. } The user can now select any .ExecutablePath. as shown in the Open dialog depicted in Figure 5.dll)|*. System.EventArgs e) { openFileDialog1.exe|DLL (*. openFileDialog1.Figure 5.exe)|*.3: The form has a ListBox for types in an assembly and separate ListBoxes for each kind of member of a type.dll". openFileDialog1. openFileDialog1.InitialDirectory = Application.FileName. . txtFileToOpen.NET assembly to examine .4. Note: Working with ListBoxes and the common dialog controls was explained in Chapter 4.ShowDialog().

of type System. within the form class. theAssembly is set to the file selected by the user using the Assembly. first add a directive to use the System. The elements of the array are then added to the Type ListBox (see Figure 5. Assembly theAssembly. GetTypeInfo(theAssembly). Next.Reflection namespace: using System. When the user clicks Perform Reflection.LoadFrom(txtFileToOpen. } } .Assembly: Type[] typeArray.EventArgs e) { theAssembly = Assembly.4: You can use reflection to examine the metadata of the program that is running. System.Reflection.Reflection. foreach (Type type in typeArray) { lstTypes.Text). To use reflection to pull the types out of the assembly.Items.5): private void btnReflect_Click(object sender. } private void GetTypeInfo(Assembly theAssembly){ typeArray = theAssembly. declare an array (named typeArray) to hold the type information and a variable.GetTypes(). and the Assembly. theAssembly.Add(type.Figure 5.LoadFrom method.FullName).GetTypes method is used to load the types in the assembly into typeArray.

Text != ""){ . } .GetMethods().Type[].Reflection.. So far. you'll see that the other kinds of members are reflected as well): if (lstTypes.. foreach (MethodInfo method in theMethods) { lstMethods.. 'Windows Uses Web Services.5: The types.EventArgs e) { // Clear the ListBoxes except lstTypes foreach (object o in this. but if you look below at the complete code for the Get Type Info procedure.Figure 5. the members of the type will be displayed in the appropriate ListBox. } } .GetMethods method to retrieve the methods for the type. within the assembly are displayed.Clear(). I've used the code explained in Chapter 3. } else { . uses the System..Items.Name == "lstTypes")) lb. we probably should make sure that the ListBoxes we are going to use are empty .ToString()). this is great! We've used reflection to display the types in an assembly.Controls) { if (o is ListBox) { ListBox lb = (ListBox) o. here's the code that checks to see whether a type is selected. if (!(lb. or classes. by selecting it in the Types ListBox.Add(method. before we get started with this.SelectedIndex]. Too!' to loop through all the ListBoxes on the form (rather than clearing them by name): private void btnGetTypeInfo_Click(object sender. Continuing with the reflection.Items. When the user clicks Get Type Info. To do this. System.. MethodInfo[] theMethods = typeArray[lstTypes. But. The next step is to let the user choose a type. then displays them by adding them to the Methods ListBox (I'm showing method reflection that the type information for only one assembly is displayed..

Windows. Listing 5. methods.dll". public class Form1 : System. MessageBoxIcon. and events ..6: Member information for a type is displayed.dll)|*.EventArgs e) { openFileDialog1.Form { .OK. SybexC11.3 shows a class module. properties. that I added to the reflection project to simply show a variety of type members..2: Displaying Types and Members Using Reflection // Form1.. private void btnOpen_Click(object sender...Reflection. fields. . "Nothing Selected!".exe)|*. You'll see these members "reflected" in Figure 5. Assembly theAssembly. System.2 shows the complete code for generating member information .Show("Please select a type for further info!". Type[] typeArray. openFileDialog1. MessageBoxButtons.about types in an assembly using reflection.Forms.. .. Listing 5.Filter = "Executable (*. information about constructors. } Listing 5.ShowDialog().cs .InitialDirectory = Application. openFileDialog1.exe|DLL(*.MessageBox.6. openFileDialog1.Reflection.ExecutablePath.Information). using System.that is.DefaultExt = "exe". Figure 5.. .

Items. foreach (FieldInfo field in theFields) { lstFields. GetTypeInfo(theAssembly).SelectedIndex].Add(constructor.Name == "lstTypes")) lb. System.Text =openFileDialog1.Add(type.Items.txtFileToOpen.Items. } } private void btnGetTypeInfo_Click(object sender.SelectedIndex].Add(field. } private void btnReflect_Click(object sender.GetMethods().Controls) { if (o is ListBox) { ListBox lb = (ListBox) o. foreach (MethodInfo method in theMethods) { lstMethods.ToString()).EventArgs e) { theAssembly = Assembly. } MethodInfo[] theMethods = typeArray[lstTypes.LoadFrom(txtFileToOpen.GetConstructors().Clear().Add(method.FileName. foreach (ConstructorInfo constructor in theConstructors){ lstConstructors. if (!(lb.Items.ToString()).GetFields(). } } if (lstTypes.FullName).EventArgs e) { // Clear the ListBoxes except lstTypes foreach (object o in this. } private void GetTypeInfo(Assembly theAssembly){ typeArray = theAssembly.Text). System.Items. .ToString()).SelectedIndex].GetTypes().Text != ""){ ConstructorInfo[] theConstructors = typeArray[lstTypes. foreach (Type type in typeArray) { lstTypes. } FieldInfo[] theFields = typeArray[lstTypes.

namespace SybexC11 { public class Reflection { public int myInt. "Nothing Selected!".Items.GetEvents(). . MessageBoxButtons.GetProperties(). } } .Add(anEvent.} PropertyInfo[] theProps = typeArray[lstTypes.cs using System. } } else { MessageBox. public Reflection() { } public string myProperty { get { return "reflection". MessageBoxIcon..SelectedIndex].OK.SelectedIndex]. public string TrueLove. foreach (PropertyInfo prop in theProps) { lstProperties.3: A Class with Members to Demonstrate Reflection // Reflection.Add(prop. } Listing 5.ToString()).Information).Show("Please select a type for further info!". foreach (EventInfo anEvent in theEvents) { lstEvents.Items. } EventInfo[] theEvents = typeArray[lstTypes..ToString()).

Figure 5.3 uses the ToString() method to simply display information about each member. public event myDelegate MyEvent = new myDelegate(myMethod)..7: Variables declared at the form's class level appear as .7). } } Note: The code in Listing 5. Of course. Before we do. When you run the application now..Windows. public string myTrueLove = "Phyllis". You'll find quite a bit of additional programmatic capabilities in the System. let's add some class . } public delegate string myDelegate(). and events (Figure 5. public class Form1 : System.level variable declarations and assignments (these will show up as Fields): .Reflection namespace. properties. you can also turn the reflection spotlight on the form module itself in the project. public bool isTheFairest = true.. reflecting on itself and getting type information about the class that represents the form module.} } public static string myMethod() { return "True love".. you'll see these fields as well as the standard form methods.Form { .Forms.

as shown in Figure 5. Figure 5. properties. . The reflection application can be used to inspect the assemblies that are part of the . Tracking Members with the Class View Window Visual Studio's Class View window (Figure 5. a key class of the .8: Select System. all form methods.dll.NET Framework (top).Fields.8. and class members within a project.NET Framework. All the types in the assembly will be displayed (bottom). classes. and click Open.9) is an excellent way to keep track of namespaces. and events are displayed in the appropriate ListBoxes.

10: The Class View window can be conveniently opened using a toolbar icon. select View > Class View.10).Figure 5.9: You can use the Class View window to understand how namespaces. . classes. Figure 5. and their members are structured in a project. The Class View window can also be opened by clicking a button on the Visual Studio toolbar (Figure 5. To open the Class View window.

11: By choosing Browse Definition with a member selected in the Class View window.NET objects. And Browse Definition opens the Object Browser with the member loaded (Figure 5. Thus. use one of the following methods: . a context menu appears. events.NET Framework. From this context menu.11). But it's probably the single most important discovery tool included in the Visual Studio . You can use it to discern the hierarchy of classes. and methods of each object. or classes. Opening the Object Browser To open the Object Browser. rather than a tool you use to actually do anything.NET development environment. and the relationships of objects to each other. Navigating with the Object Browser The Object Browser lets you determine the members of . members of classes. as well as the the Object Browser is a tool of discovery. Go To Definition takes you to the code that declares the member (opening the Code Editor if necessary). if you right . the Object Browser will open. Go To Reference takes you to the first use of the member. displaying the member's definition. You can easily use the Object Browser to learn about the objects available for use in your programs. Figure 5.With a member of a class selected in the Class View window. The Object Browser also teaches about the structure of the .

. Ctrl+Alt+J. or procedure or functional call. For example. Go To Definition does nothing. Go To Definition takes you to the declaration for the object. • • • Highlight a member in the Class View window. this declaration was generated for you when you added the OpenFileDialog control to your form. Likely. right . right .12.Select View > Other Windows > Object Browser from the Visual Studio menus. With the cursor over the statement using openFileDialog1.Windows. In the Code Editor. as shown in Figure 5. Note that this does not work if the cursor is hovering over a variable or method. and choose Go To Definition.Forms. and choose Browse Definition. suppose you have a component in your project named openFileDialog1 of type System. Press the keyboard shortcut. the Object Browser will open with the highlighted object defined.NET object's context menu). right . If you move the cursor to OpenFileDialog at the end of this declaration statement. In the case of a keyword. place the cursor on a . Opening the Object Browser Using Go To Definition When you open the Object Browser from the Code Editor (by choosing Go To Definition from a .OpenFileDialog and select Go To Definition. and then select Go To Definition again.Windows. It's probably along these lines: private System.OpenFileDialog. You will be taken to the declaration for openFileDialog1 at the beginning of the module. In the case of a variable. the Object Browser will open to the definition of the OpenFileDialog object.

Using the Object Browser Interface The Object Browser interface consists of a toolbar and three panes: the Objects pane. and the Description pane provides information about the selected object.Figure 5. with information for the MessageBox class. the Members pane.13 shows the full Object Browser interface. Figure 5. The Objects Pane . Figure 5. and the Description pane.13: The Members pane shows the members of an object selected in the Objects pane.12: The class definition of an object is shown in the Object Browser.

Color selected and some of the members of Color visible in the Members pane. one other icon. They are immensely valuable for quickly gathering information about an object. a key. In addition. events. so expanding System. Members mean properties. Here is an example of the Objects pane with System. a great deal about the object . A different icon indicates each different kind of member. Clicking the + or . but it usually includes the following: • A description of the object • The name of the object and its parent object • The object syntax • Links to related objects and members Note: The links in the Description pane take you to objects that are related to the selected object.such as the classes that it is based on . methods.) The Description Pane The Description pane provides a great deal of information about the object currently selected in the Objects pane.will be displayed in the Objects pane. This information isn't the same for all objects. or only accessible from within its own class (or a derived class).icons in the Objects pane expands or contracts the tree view of objects. means the member is protected. (For more on this topic.Drawing.Drawing shows the Color class.The Objects pane provides a hierarchical view of the objects (or classes) contained in the namespaces that are within the scope of the Object Browser.) If you fully expand an object in the Objects pane. . variables. see Chapter 8. The Members Pane The Members pane shows the members of an object selected in the Objects pane. constants. and enumeration values.Drawing. (Color is a member of System.

Forms). The two scope options are available in the Browse drop . Click the Add button in the Selected Components dialog to open the Component Selector dialog. click the Customize button to the right of the Browse drop . but called symbols here). Setting the Object Browser Scope When the Object Browser opens.left corner of the Object Browser. System. if checked. you have a choice regarding the scope of the browser (or which objects it will see). Components added to the Selected Components box at the bottom of the Component Selector will appear in the Selected Components dialog and. arrange the contents of the Object Browser. To add other projects. System.NET and COM components to add. the dialog shown in Figure 5.Windows. Selected Components is the default initial selection.based application. navigate. executables.) To customize the objects included within the Selected Components scope of the Object Browser.14 opens. which includes the active project and its references (for example. When you click the Find Symbol button. and other types of files (such as OLB or TLB type libraries or dynamic link libraries). in the case of a form . . It's likely that the most useful toolbar button is Find Symbol (the button with the binoculars icon). and find identifiers (such as object names. (But you could go back to the project and add or remove a reference in the Solution Explorer.down list. You can use the Component Selector to choose .The Toolbar The Object Browser toolbar is used to customize the Object Browser scope. and.down list in the upper . The other scope option is Active Project. You can customize the objects included in this scope. will be available to browse in the Object Browser. The Active Project setting does not allow any customization of the objects that can be browsed. click the Browse button.

But pure recreational browsing can teach you a great deal about the way . The Find Symbol dialog allows you to search for objects including namespaces. . see the next section). After I've shown you how to create each of these members. and an event. Note: Obviously. Figure 5. use the New Project dialog to create a Class Library project by selecting Visual C# Projects in the Project Types pane and Class Library in the Templates pane. I'll show you how to use them from a Windows forms project. Creating a Class Library Now that you've seen how to inspect the class libraries that are part of the . a method.Figure 5. which will in turn contain a field. These class members will not be fancy. a property. how to work with it. let's walk through the process of creating your own class library.using the object's name.NET Framework.and the members of these objects . the Object Browser tools such as the Find Symbol dialog are the easiest way to locate specific objects within the Object Browser. The point of this is to be very bare bones.14: The Find Symbol dialog lets you search for objects using the object's name. and structures . classes. and even the best way to construct your own object hierarchies (for more on this.NET is structured.15: A new project can be opened to serve as the basis for a class library. The class library will contain a single class. and giving the project a name (Figure 5.15). To start with.

since we're not going to be using it . (You should particularly have a look at 'Naming Guidelines. even if they don't have access to the source code. and the objects based on the class will be specific babies. By following the . you will be doing things in the way that other developers expect . change the class name to Baby to reflect the kinds of objects that will be created based on the class. Our class will be the template representing a baby.and they will find your work accessible and understandable.NET programming largely involves working with these classes.we'll now have a code module that looks like this: using System.cutting down on maintenance effort and making it easier for others to use your libraries.NET Design Guidelines Getting familiar with the Class View and Object Browser tools is a very good thing! There's also no doubt that it will pay you to become familiar with the organization. but let's say it anyhow: the order of members within a class does not matter. and way of doing things that goes with the classes in the .' which explains the . But the biggest payoff is in improved usability of your own classes and class libraries. If we keep the namespace.NET Framework . namespace SybexC12 { public class Baby { } } Note: It probably goes without saying.When the project opens.) .' This document functions as a table of contents for class library design topics.NET Framework design guidelines.NET structure. search online help for 'Design Guidelines for Class Library Developers. To the extent that you pattern these after the . structure.NET design guidelines. The . your code libraries will behave in a predictable and familiar way .since successful . it will have a class code module containing (by default) the project name as the namespace and a class named Class1. and touches on many more areas than will be covered in this section.NET suggested naming conventions for types in class libraries. and remove the class constructor . To view the .

using the set accessor . } set { m_Age = value. Since each baby object has a name.access to the property: public int Age { get { return m_Age. respectively. If you want to. The first step is to declare a private variable to hold the property value internally (in the example. Adding a Property Properties differ from fields. since accessor get and set methods serve as a kind of "gatekeeper" for access to the value contained in the property. as you'll see in a moment.. Let's add an Age property to the Baby class. You can also include validation code within the property procedures . raise (meaning "fire" or "trigger") an event when a property value is changed. a property procedure is used to provide read . . public class Baby { public string Name = "". } } The property is set using the special keyword value.or. Next. let's reflect that fact by giving the Baby class a Name field: .. you can make the property read only or write . it is initialized to 0): private int m_Age = 0.and write .only by only including a get or set accessor function...Adding a Field A field is simply a variable declared at the class level using the public keyword. .using the get accessor .

e).. this presumably means that the baby object is a year older). We can do this from within the Age property set method.meaning a method's arguments. the class declares the event by applying the event keyword to a delegate. OnBirthDay(this.. Here are the delegate and event declarations for an OnBirthDay event: public delegate void BirthDayEventHandler (object sender.EventArgs(). Here's one that returns a sound often made by baby objects: public string GetSound() { return "Waaah!".Adding a Method A method is simply a public function belonging to the class. System. } . } Adding an Event To raise an event in the Baby class.EventArgs e).EventArgs e = new System... } } m_Age = value. and the method return type. It remains to raise the event. public event BirthDayEventHandler OnBirthDay. as follows: . In our Baby class. . set { if (value > m_Age) { // Raise the event if (OnBirthDay != null) { System. A delegate instance can hold and invoke a method (or methods) that matches its signature. their types. we must first declare a delegate. which is a type that defines a method signature . In turn. which is done by calling the OnBirthDay method with the arguments indicated in the related delegate's signature. let's raise the OnBirthDay method when the Age property changes so that it is greater than it was (since it is an integer field.

Listing 5.EvenetArgs()). Assuming that the event has subscribers.otherwise there is no point in firing it. e).EventArgs(). the two lines of code System. OnBirthDay(this.EventArgs e = new System.4: The Baby Class using System. it is then raised. Listing 5. or. new System. The next step will be to open a new Windows forms project with the purpose of instantiating a baby object based on the Baby class and invoking the class methods.4 shows the complete code in the Baby class. subscribing to the event .EventArgs(). namespace SybexC12 { public class Baby { // Field public string Name = "". . OK.EventArgs e = new System. In the meantime. By the way. Let's go ahead and build the class library (Build > Build Solution). // Private variable for property private int m_Age = 0. in the lingo.The conditional OnBirthDay != null checks to see that something is using. } set { if (value > m_Age) { // Raise the event if (OnBirthDay != null) { System. // Property public int Age { get { return m_Age. could be rewritten for compactness (although not for clarity): OnBirthDay(this. with a few comments added for clarity.

In the Select Component dialog. e). . } } // Method public string GetSound(){ return "Waaah!". locate SybexC12 and click Open.OnBirthDay(this. and click the Browse button in its upper . To do so.16: The Select Component dialog. Figure 5. shown in Figure 5.16. System. } } m_Age = value. as explained earlier in this chapter. opened from the Add Reference dialog. open the Add Reference dialog (Project > Add Reference). } } Invoking the Class Members Open a new Windows forms project.EventArgs e). The first step is to add a reference to the SybexC12 assembly so that we can instantiate an object based on the Baby class contained in that assembly.right corner. } // Event declarations public delegate void BirthDayEventHandler(object sender. is used to locate the assembly containing the class library. public event BirthDayEventHandler OnBirthDay.

dll assembly. Let's add TextBoxes for the baby name and age.18). If you open the Object Browser. The next step is to add some TextBoxes and Buttons to the form to use to invoke the members of the class library.The SybexC12.17: The assembly containing the class library now appears in the Selected Components pane.18: Once the reference has been added. a Button to set them. Figure 5. The reference to the SybexC12. which contains the Baby class library. Figure 5.17).Baby class library has now been added to the project. . and a Get Sound button to get a representation of the noise often made by the baby object. now appears in the Selected Components pane of the Add Reference dialog (Figure 5. you'll easily be able to find the Baby class members (Figure 5. the class members appear in the Object Browser. Click OK to add the reference.

. the first step is to add a using directive that refers to the SybexC12 namespace. we'll instantiate an object. . Baby myBaby = new Baby().Exclamation).Form { ... ..GetSound method in the click event of the Get Sound button: private void btnGetSound_Click(object sender. } Setting Fields and Properties It's equally simple to set the Name field and Age property in the click event handler of the Set button: .OK. MessageBox. MessageBoxIcon. Next. in this case stored in a variable named myBaby. based on the Baby class: . System.GetSound().... Using a Class Method It's a simple matter now to invoke the myBaby. MessageBoxButtons.Windows. using SybexC12. public class Form1 : System.Forms.. "Hi from my Baby!".Show(msg.EventArgs e) { string msg = myBaby.Instantiating an Object Based on the Class Turning to the form code.

since the initial value for the age is 0). This method will be executed when the event is raised. myBaby. If you run the project and enter an age for the baby object that will trigger the event (for example. MessageBoxButtons. in the form's constructor.OnBirthDay event is wired via the event delegate to the Baby_OnBirthDay method.OnBirthDay += new Baby. "Happy.BirthDayEventHandler(this. 1. we first need to create a method in the form class with the same signature as the event delegate.ToInt16(txtAge.Exclamation). System.Text). For example. you will see that the OnBirthDay event will be triggered. MessageBoxIcon. the myBaby. System.Show("The Baby.Baby_OnBirthDay).Name = txtName.Text. we can simply display a message box in a method named Baby_OnBirthDay whose signature matches the event delegate: private void Baby_OnBirthDay(object sender. myBaby.19. as shown in Figure 5. } Next.EventArgs e) { MessageBox.private void btnSet_Click(object sender. .EventArgs e) { myBaby.OnBirthDay event was raised!".Age = Convert. } Wiring the Event To wire the event. Happy Birthday to my baby!".OK.

Conclusion Programming with C# and the .NET Framework means programming with classes. .Figure 5. This chapter has covered a lot of ground and started you down the path of programming well with classes. In the meantime. But it is not an island! You'll find a great deal more about object .oriented programming in Chapter 8.19: The OnBirthDay event is raised when the age is changed. it is back to basics: Chapter 6 explains the basic building blocks of the C# language.

OK). This chapter reviews the nuts and bolts of the C# language. string DeeFlat = "DeeFlat". Variables!". C# identifiers are case sensitive. variables. and DEEFLAT are all different. types. methods. C++. MessageBox. It has no 'creatures' in it.Show(deeFlat + " " + DeeFlat + " " + DEEFLAT. Since C# is new. for that matter.EventArgs e) { string deeFlat = "deeFlat". operators. constants. C# does have things like types. Nor. in the opinion of this author. While C# borrows from Java. as running this click procedure will show: private void btnClickMe_Click(object sender. objects. in fact. wonder!' exclaims Miranda in Shakespeare's play The Tempest.The C# Language: Zen and Now Overview • C# vocabulary: Identifiers and keywords • Manipulating values: Variables and constants • Types and type conversion • Commenting C# code • C# syntax: Operators and flow control • Structs • Exceptions O. the three variables deeFlat.and. MessageBoxButtons. a brave new language: C#. "Hello. 'How many goodly creatures are there here! How beauteous mankind is! O brave new world that has such people in't!' Our brave new world is. and expressions. and since language and syntax are the blocks on which programs are built. and so on. glued together using a distinctive syntax. Identifiers Identifiers are names used to denote variables. For example. O brave new language! The introduction of a new language is a rare event. Brave New World. } . and even Visual Basic and Pascal. System. does it have the pharmacopoeia of Aldous Huxley's famous dystopia. C. it's especially important to pay attention to the rules of the language. string DEEFLAT = "DEEFLAT". DeeFlat. quite wondrous. it is a truly new language . An identifier begins with a letter or an underscore and ends with the character just before the next white space.

Keywords Another way to think of a keyword is as a predefined reserved identifier. For more on Microsoft's suggested identifier naming initial uppercase letter.Show(@if. if you were really bound and determined to create an identifier like a keyword. look up 'Naming Guidelines' using the Search feature in online help. but @if can. see the 'Self . these kinds of bugs are found pretty easily once you are on the watch for supposed to be used for identifiers that represent method names and most other non . GetColorValues. Variables!". Table 6.for example. "Hello.variable objects. may find that C#'s case sensitivity regarding identifiers leads to the introduction of bugs. you must use a keyword as an identifier. Note: It's always good programming practice to use identifiers that clearly communicate the contents and/or nature of the variable or other object identified. deeFlat. In addition. Tip: Visual Basic programmers. Camel notation means an initial lowercase letter followed by uppercase letters ('internal caps') for initial letters of subsequent words in the identifier . Code that uses clear identifiers may be almost self . For that matter.OK). Fortunately. who are used to case insensitivity.Documenting Code' section later in this chapter. if cannot be used as an identifier (because it is a keyword). MessageBoxButtons. For more suggestions of this kind. this code snippet shows it employed as a string variable: string @if = "@if".) If. Pascal notation .documenting. you could simply vary the case. For example. for some really perverse reason. and paying attention to the case of identifiers will become second nature shortly. . It is not legal to use an identifier that is also a C# keyword. you can preface the keyword with the @ symbol.1 shows the complete list of C# keywords.noun combination to describe the purpose of the method .albeit an idiotic choice because it is similar to a keyword and so potentially confusing .because the keyword if is all lowercase. with internal caps as needed . MessageBox. So If is a perfectly legal identifier . an identifier used for a method should use a verb .for example.Microsoft suggests using 'camel notation' for identifiers used as names of variables. (You'll find a list of C# reserved keywords in the next section.

Variables A variable combines a type with a way to store a value of the specified type.1: C# Keywords (Reserved Identifiers) abstract as base bool break byte case catch char checked class const continue decimal default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long namespace new null object operator out override params private protected public readonly ref return sbyte sealed short sizeof stackalloc static string struct switch this throw true try typeof uint ulong unchecked unsafe ushort using virtual void volatile while You can find this list of C# keywords by looking up 'keyword. such as int [integer] and string.) The value of a variable can be assigned.Table 6. C#' using the online help's Index facility. string deeFlat. and that value can also be changed programmatically at any point. . The online help topic is then hyperlinked to the definition of each keyword. A variable is created in C# by declaring its type and then giving it an identifier. For example: int theInt. but you are certainly already familiar with some of the C# types. (I discuss types later in this chapter.

"Hello. meaning given an initial value.although this is not required. you could declare the variables and later assign them values: int theInt. Definite Assignment One thing you should know is that C# requires that variables be assigned a value . As they say. So.before they are used. . int i = new int(). with the same effect. Variables!". string deeFlat. produces a syntax error and will not compile because of the attempted use of an unassigned variable: .The variable can be initialized. theInt = 42. Alternatively. an uninitialized variable is like an unmade bed . of course. string unMadeBed.OK).you never know what you'll find in it. deeFlat = "This is a string!". MessageBoxButtons.. This is known as definite assignment and codifies what is good practice in any case.either through initialization or programmatic assignment . is the equivalent to the standard int declaration: int i.Show(unMadeBed. To digress for a second here: you may not know that even simple value types such as int inherit from object. at the time it is declared . string deeFlat = "This is a string!". This implies that you could declare an int (or other value type) variable using the new constructor. For example.. MessageBox. Here are the same variables declared and initialized: int theInt = 42.

Assigning 6. when the constant value is assigned.and a better practice . might be used in a financial application. For example.or lowercase: m or M. to a decimal . here are the declarations for three notional constants: const int maxSearchRecords = 41928. A constant must be initialized when it is declared. and the assignment of value. an only change it once.) Constants should be used for fixed values that occur many places in your code.also called symbolic constants .) . it's certainly easier . const decimal interestRate = 6. which alone would be a literal of type double.75M.Constants A constant is an identifier used in place of a value. Note: For constants that you may need to change.) If the interestRate value is used repeatedly in the program. interestRate. and once it has been initialized the value cannot be changed. as appropriate. might represent the maximum number of records in a search routine. rather than throughout the application. Constants used in this way . or in their own class module. const string companyMotto = "Best of the Best of the Best!". (Of course.type constant would produce a conversion error. it is also a good idea to put them in a place where you can find them . using an identifier in this manner makes it much clearer what it represents than would be the case if the value itself was used. The second constant.are declared in C# using the const keyword followed by the constant's type.code. They should also be used in place of a value whose purpose is not immediately clear from the value.and recompile . (The trailing letter here can be upper . in the real world you'd probably want to do this technique one better and provide a way to change the interest rate used without having to edit . Even if you use this value only once. (You can think of a constant as a variable whose value cannot be changed. maxSearchRecords. The M following the value tells the compiler that the value is of type decimal rather than double. The first of the top of a procedure.

The final constant example. can be used in the place of constants in an application. The fourth MessageBox. allow you to load properties from an external configuration file.called enumeration constants .completion feature of the Code Editor when you type in a MessageBox. you only have to change it in one place. .Show (Figure 6.Show method: MessageBox. "Sir!". These values. companyMotto. found at the top of the Properties window. look up 'Introduction to Dynamic Properties' in online help. each a member of the MessageBoxIcon enumeration.Show(companyMotto. is included primarily to show that constants can contain string values. Note: Dynamic properties. "Windows Uses Web Services. This icon is selected by choosing from a list of enumeration constants. Here's companyMotto used with a MessageBox. as well as other data types. For example. (The list of constants within an enumeration is also called the enumerator list. For more information. MessageBoxButtons. and if the company changes its motto. use of the MessageBox.of the same type. which can easily be changed without the need for recompiling a project.) Built-In Enumerations You've almost certainly used the enumeration constants that are part of the . The same reasons for using constants rather than literal values apply to string types. Enumeration Constants Enumerations are lists of named constants .Show method argument represents the icon that will be displayed in the message box.1).Show method was explained in Chapter 3.NET Framework's pre . and perhaps companyMotto is an example of this: You don't have to retype the string in numerous places in your application.OK).built types. Too!". You'll see the list of constants for the enumeration supplied by the auto .

Exclamation). MessageBoxButtons. You can also find member information for an enumerator list in the Object Browser.OK. . as shown in Figure 6. "Sir!".2: The members of an enumeration constant list are shown in the Object Browser.2. MessageBox. MessageBoxIcon. Figure 6.Show(companyMotto.completion feature of the Code Editor supplies the members of an enumerator list.1: The auto .Figure 6. The icon that represents the choice made by the selection of the enumeration constant from the list of MessageBoxIcon members will be displayed when the message box is displayed: const string companyMotto = "Best of the Best of the Best!".

train 0 toys. it defaults to int.completion list. the first item in the enumerator list has a value of zero.dinosaur 1 toys. if you created an enumeration named toys with three items: enum toys {train. which is the underlying C# intrinsic type for items in the enumeration. and each successive enumerator is increased by one. If the base type of an enumeration is not specified. the items would have values as follows: Enumerator Value toys. when you attempt to use this enumeration. dinosaur. just like a built . For example.NET . using the enum keyword. the members appear alphabetically in the Code Editor's auto .Custom Enumerations Enumerations are too good to just use with types provided by .in enumeration: The members of the toys enumeration will also appear in the Object Browser: . In the default'll want to use your own enumerations. you needn't assign a value to each enumeration constant (but you can if you wish).truck 2 Now. truck}. An enumeration has a base type. In addition. in situations where related items form a natural list of possibilities.

rather than inside a method. you'd get the string 'truck' rather than the underlying value: To access the underlying value of the enumeration constant.truck. So. For example.ToString()). and only assign values to some items . as placing one inside a method causes compilation errors. You can also start an enumeration list at whatever value you'd like. int x = (int) toys. the default is to increment by 1. see "Type Conversion" later in this chapter. Note: The enum declaration should appear as part of a class definition. if the toys enumeration were modified as follows: . For example: enum toys {train = 12. If you wish to explicitly assign values to the enumeration items. the variable x now has a value of 2.Show(toys.By the which case. For more about casting. if you attempted to display the value of toys. dinosaur = 35.truck. under this scenario. you can do so in the enum declaration. truck = 42}.truck using a statement along the lines of: MessageBox. you would have to cast it to integer type.

(Stylistically. or 1 as in some versions of VB. System.String type. excavator = 41. string.2 shows the complete list of C# predefined. types (also sometimes called primitive types). and enumerations. So we had better get C# and types under our belt right away to put an end to the continuing possibility of circular definitions. Unsigned 16 . truck }.truck a value of 42.Byte System. and decimal . dinosaur.Char Bytes in Description Memory 1 1 Unsigned 8 .Sbyte System.qualified name of the underlying type to refer to the type. or intrinsic. Table 6.Int16 System. the keyword int refers to the provide examples of constants.Boolean 1 .Uint16 1 2 2 Signed 8 .int.767).535).bit integer value (32.which is why the topic of types has already come up numerous times in this discussion of basic C# syntax.2: Predefined C# Types Keyword .dinosaur would have a value of 13 and toys. variables.Int32 type. Boolean type: either true or false.enum toys { train = 12.bit integer value (0 to 65. and not an integer value of 0.bit integer value (128 to 127). These predefined types are declared using a keyword that functions as an alias or shorthand for the type provided by the system. and the keyword string refers to the System.) Table 6. sbyte short ushort System. For example. you should use the keyword rather than the fully . So far in this chapter I've used a few types . Types Everything in C# is a type .bit integer value (0 to 255). Signed 16 . then toys.NET Type byte char bool System.768 to32. Visual Basic users should note that the bool type can contain only true or false. 1. Unicode character.

A sequence of Unicode characters. . Unsigned 32 .147.' In Table 6.Uint32 System.967.647 to 2. meaning that a pointer to the data is stored rather than the data itself.294. In Chapter 5. As you've probably gathered in the previous chapters of this book. while the other types in the table are value types (meaning that the actual value of the type is stored in memory).bit integer value (0 to 4.Single System.bit integer value (2.Int32 System.Object uint float double System.bit integer. Besides classes.Int64 System. 'The Life of the Object in C#. other important reference types include arrays. All data types. aliased as object.) Placing double quotes around a string of alphanumeric characters creates a string literal.295).String 8 8 N/A N/A Signed 64 .defined.precision number up to 28 digits and the position of the decimal place. you can use C# to create custom reference types.483. 'Everything Is String Manipulation. Used in financial calculations. predefined and user . You'll find more material about creating your own types using classes and interfaces in Chapter 8. Single . Requires an m or M appended (see example in the"Constants" section earlier in this chapter). decimal System.Decimal 8 long ulong object string System. See the next section and Chapter 9.147.2. the heart of programming in C# is creating your own types by designing classes (which are reference types) that serve as a blueprint for objects.Uint64 System.precision floating point number. and interface types.Chapter 9 . object and string are reference types.483. For some of C#'s sophisticated ways to work with them. inherit from the System.647). you can't build much of a program without using strings.precision floating point number. Unsigned 64 .' String Variables As you probably know. 'Reflecting on Classes. Double .Double 4 4 4 8 Signed 32 .bit integer. In addition to the predefined types. Fixed . delegates.' I showed you how to create your own classes and a class library and provided a delegate example.Object System. (Strings are so important that I've devoted a whole chapter .

characters encoded using UTF . The Unicode formats provide a unique number for every character. no matter what the platform. and no matter what the language. You can find out more information about Unicode at www."I am a string!" Note: Obviously. you can declare an uninitialized variable of type string. So the string literal "I am a string!" is not the equivalent of "Iamastring!".3: You can learn how to work with string variables. and here's another that declares a string variable and assigns a literal string value to it: string sloganOfTheDay = "Today is the first day of the rest of your life. You'll find out more about working with System. the keyword string is actually a kind of alias to the System. but for now.unicode. within string As I mentioned before.2.3). String variables are declared using the keyword string. As usual. You've already seen many examples of string variables in this book. string or otherwise.String class. Unicode Character Encoding System As I noted in Table 6. you might want to have a look at the members of the System. However. which .as opposed to everywhere else in C# where it does not. and later assign a value to it programmatically.16 (Unicode transformation format. strings in .actually.String object.bit encoding).". white space does have a meaning .NET are made up of Unicode characters . Figure 6. definite assignment requires that you never actually use an unassigned variable. so by declaring a variable of type string you are creating an instance of a System.String class using the Object Browser (Figure 6. 16 . no matter what the program.String objects in Chapter 9.

NET is. all variables have a type that must be declared. the compiler 'guesses' what the programmer most likely meant (which can occasionally introduce errors). you must pay close attention in your code every time a value of one type is converted to another. you'd run VB . Alternatively. In a strongly typed language. you can skip this section with 'type safety'). indeed. weak type environments. as explained in Chapter 5. Note: To view the System. Much of the time. It also prevents possible program errors that can occur in weakly typed environments when the compiler finds the wrong kind of value in a type.and may. The trade . you can use the Find Symbol dialog. by definition. C# Is a Strongly Typed Language You've probably already heard the phrase 'C# is a strongly typed language' . Tip: To make VB . For another. In addition. For one thing. so this and the next section are especially for those of you in that boat. already know what it means (in which case.defined types).NET with the compiler option Strict set to On. working in a strongly typed language means that you need to be very clear about the type of information that will be stored in a variable.are actually objects based on the System. where it may not be required).and the other predefined C# types .in the Object Browser. or are user . first expand the mscorlib assembly node.String class. using the Object Browser. you must explicitly declare the type of all variables (which is good programming practice even in weakly typed environments. Note that VB6 and earlier versions of Visual Basic had no way to enforce strong typing .NET strongly typed in much the way that C# . Strong typing enforces programming discipline and clarity about the contents of for the benefits of strong typing is more work up front. As a practical matter.String type . Another way of thinking of this is that weak typing allows a programmer to be lazy .in a possibly dubious type conversion.and were. However. or by adding the statement Option Strict On at the beginning of each code module. the compiler verifies the type consistency of expressions (and expressions are always of a type defined by the C# language. and then expand the System namespace node. you must provide explicit guidance to the compiler using casting or a conversion method about the type conversion you'd like (see . this whole area of strong typing and type conversions is one of the most frustrating things for programmers coming to C# from a weakly typed environment such as Visual Basic. accomplished either in the IDE Options dialog.

ToInt16(rb. the statement mbb = (MessageBoxButtons) Convert. and can involve multiple conversions within a single statement. Type conversion can get pretty convoluted. the integer argument X is automatically converted to a string type.the 'Explicit Conversion' section later for information about how to do this). Next. in the 'RadioButtons and Message Boxes' section of Chapter 3 involves three conversions that had to be specified by the programmer: • • • A cast to type MessageBoxButtons A conversion to type short using the ToInt16 method of the System.5 Dim X As Integer = 2 X = X + theFloat MessageBox.Show(X) the program will run without syntax errors.Convert object A conversion to type string using the ToString method inherited by all objects from System. and the value 6 is displayed: .NET (with Option Strict disengaged). If you run the following code in VB .Tag. The value of theFloat will be rounded up and off to 4 when it is added and assigned to the integer X. Dim theFloat As Double = 3.ToString()).Object A simple example is probably the best way for getting a feel for the difference between working in a weakly and strongly typed environment. in the message box statement. For example.

. 3. The comparable code in C#. X = X + (int) theFloat. int X = 2. This code compiles and runs just fine without any syntax errors. X = X + pretty weird (8 would be a better result).ToInt16 method: double theFloat = 3. int X = 2.5 to an integer value (e.5.Show(X. The (int) cast has simply taken the whole . 4).g.5. MessageBox.related syntax errors: You can correct the C# code snippet by casting the double . truncating theFloat to 3 and displaying 5 as the result.up.which is what would happen in VB . However.5 and 2 and getting 6 as the integer result is not unreasonable.ToString()). To perform the round .interestingly . int X = 2.type variable theFloat to int and using the ToString method to display the contents of the variable X in the message box: double theFloat = 3. adding 2.Show(X).5. but it is also the possible source of the introduction of errors in more complex programs if you are not counting on the round . simply will not compile due to several conversion .5.ToInt16(theFloat). produces different results than the VB code. double theFloat = 3. and 2 and getting 9 .off operation that you'd normally expect when converting 3. you need to use the explicit Convert. X = X + theFloat. Adding 3.5. but .number portion of theFloat variable.Tip: This is convenient if it is what you want to have happen.

As the example at the end of the previous section shows. In other words.time errors. while explicit conversions are needed if either: • • run . look up 'User . if you explicitly convert using casting.because that's not the case. . (The example showed that casting a double to integer produced a different round . C# will implicitly convert for you without any special syntax. or. I hope I haven't implied that all conversions must be explicit in C# . some data might be lost during conversion.Defined Conversions Tutorial' in online help. Type Conversion All this leads us to the topic of type conversion. It isn't rocket science.Show(X.3 shows the implicit conversions that are available for simple types.time exigencies determine whether the conversion will succeed. there can be subtle differences between various ways of converting. you will save yourself a great deal of grief.) There are three general ways to convert from one type to another: • • • Implicit conversion Explicit conversion via casting Use of a conversion method Note: You can define implicit and explicit conversions for types you create result than using an explicit conversion function on the double. Implicit Conversion Table 6. you are responsible for making sure that the results are what you anticipate and don't lead to any unexpected run .ToString()). Implicit conversions must be guaranteed to succeed and not lose information. and if you pay attention to it in the beginning.MessageBox. It's your job as a programmer in C# to stage manage conversions from one type to another. For more information. Provided that there is no way that the conversion will lose information.

double. or decimal long. There are no implicit conversions allowed between the floating . or decimal short. double. long. double. double. int X = 2. long. int. For example. or decimal ushort. int. float.3: Implicit Conversions for Simple C# Types Type sbyte byte short ushort int uint long char float ulong In addition: • • • There are no allowable implicit conversions from the bool. uint. F = X. uint. double. double. or decimal types. or decimal int. Here's another example that implicitly converts an int to a long in a method invocation (the method takes a long as its argument): . double F. no special syntax is required to perform implicit conversion. float. float. or decimal int. ulong. long. int. or decimal implicitly (and successfully) converts an int value to type double. float.Table 6. ulong. long. including assignment statements and method invocations. double. or decimal float. or decimal double float. // implicit conversion Legal Implicit Conversion To short. float. double. There are no implicit conversions allowed to the char type. uint. ulong. or decimal long. float. which can take place in several situations. double. As its name implies. ushort. float. double. long.point types and the decimal type. ulong.

Show(doubleIt(X). Implicit conversions are also possible for more complex reference types.ToString()). you should be asking the same question as with simple type conversions: Can I guarantee the success of the operation without data loss? In addition. MessageBox. . to attempt to cause an implicit conversion won't occur when the long stores a bigger value than the int can store. } . I showed you that an int value could be implicitly converted to a long value implicitly without any additional C# syntax. In other words.. Generally. int doubleIt(int inNum) { return inNum * . the reverse is not true: a long value cannot be implicitly converted to type int.time error.. any reference type can be implicitly converted to object.3.type implicit conversions. Explicit Conversion In the previous example. which converts the integral return value of the doubleIt method to a string so that it can be displayed by the MessageBox. int X = 2. when you eyeball two reference types to see whether you can do an implicit conversion. As you'd probably suppose. long X = 2. the conversion isn't guaranteed safe.. If you change the code in the example around. search for the topic 'Implicit Reference Conversions' in online help. For more information about implicit reference conversions. causing a run .Show method. and you can see in Table 6. And. some rules do apply to reference . and there is no way the compiler can know that a long . } . see the "Conversion Functions" section.long doubleIt(long inNum) { return inNum * 2. For example. any derived class can be implicitly converted to the class it was derived from. longs have a much larger range of possible values than ints. // displays 4 Note: For a discussion of the ToString method. It is easy to see why this should be the case..

long X = 2. you'd use an explicit conversion by placing a cast operator in front of the expression to be converted. and you'll get a syntax error: However..knowing programmer.ToString()).Show(doubleIt(X). For example... to convert the long variable X to an integer variable Y: int Y = (int) X. // now it displays 4 Note: that you don't really need a separate statement for the performance of the cast. int Y = (int) X. which expects an int argument. } . The doubleIt method. You might know perfectly well that the variable holding the long value in the program will never hold a big enough number at the point it is converted to int to cause a problem. .. long X = 2. it won't compile. MessageBox. receives it in the form of a cast from long to int: .ToString()). it will compile and run fine now: int doubleIt(int inNum) { return inNum * 2.Show(doubleIt((int)X).Show(doubleIt(Y). it can be done at the same time as the method invocation. A cast operator is the name of the type being converted to inside parentheses. it might be perfectly reasonable on the part of you. the all .MessageBox. In this case. to want to convert the long value to an int.ToString()). Using this cast in the example that gave us a syntax error. // cast the long value to an int MessageBox.

uint. ushort. short. ushort. byte. If an explicit reference conversion fails.4 shows the explicit numeric conversions that you can do using casting. you may have to test these conversions on a case . uint. byte. or short sbyte. int. short. uint. char. ushort. uint. int to long). int. For example. byte. ulong. short. long. int. Some rules do apply. byte. long. ushort. or char sbyte. Casts can be done between more complex reference types as well as simple numeric types. long. or char sbyte or char basis. ushort. uint.4: Permissible Explicit Numeric Conversions Type sbyte byte short ushort int uint long ulong char float double decimal Legal Explicit Conversion To byte. float. ulong. ulong. ulong. short. or char sbyte. byte. byte. For example. short.) . ulong. ulong. float. char. ushort. ulong. int. short. a System. In actual practice. short.Table 6. byte. ushort. or char sbyte. ushort. byte. char. uint. in the section 'Determining Which Object Fired an Event. byte. I knew that this cast would not . or decimal sbyte.InvalidCastException is thrown. object can be cast to any reference type. and a base class can be explicitly converted to a derived class. (For more information about exceptions. byte. uint. or decimal sbyte. or char sbyte.' I cast the sender parameter of an event procedure to the type Control: Control ctrl = (Control) sender. ushort. or double Note You can explicitly cast one type to another even if the conversion could be handled by implicit conversion (for example. see the "Exceptions" section toward the end of this chapter. int. int. in Chapter 3. short. long. Table 6. uint. or char sbyte. of course. because each object stored in the sender parameter had to be a control and derived from the Control class. or char sbyte.

Show("i is not a string"). The as operator works like a cast. For example. .for example. Note: The null keyword represents a null reference. are operators. the following code snippet will store a null reference in the variable str (with the resulting display that "i is not a string"): object i = 42. You probably have a pretty good notion of what most of the operators in C# are likely to be (for more information. } else { MessageBox. } Had the variable i contained string data. I've already used many of them in this book . one that does not refer to any object. However. it returns null rather than throwing an exception. see the "Operators" section later in this chapter). Casts. the simple assignment operator (=). then the string data 'hello' would have been displayed in the message box after the object type i was converted to the string type str. string str = i as string. except that if it can't perform the indicated conversion. if (str == null){ MessageBox.Show(str). So it seems appropriate to discuss the as operator here. which we've just discussed. such as object i = "hello".The as Operator Operators aren't really discussed until later in this chapter.

"Building a Better Windows Interface. If you look up the System. MessageBoxButtons.ToString()." I showed you how to retrieve an item's text from a ListBox by index. somewhat trivially.Items [Convert. For example. and you'll probably find yourself using these methods quite a bit.Text)]. for example "4". you'll see that there are really a huge number of these conversion methods. .Convert class can be used to convert a base data type to another base data type.OK.Show("False"). in Chapter 4. a Convert class method exists to convert every base type to every other base type. you can use it to display yet another 'False' string! In theory. As part of that demonstration.ToBoolean("False") == false){ MessageBox. the user entered the index to be retrieved as a text string.4. } else { MessageBox.Conversion Methods The shared public members of the System. as shown in Figure 6.Show("True").ToBoolean method converts the string "False" to the bool value false: if (Convert. the Convert. } If you run this code snippet. MessageBoxIcon. "Here is your text".Information). needed to be converted to an integral value so that the item's text could be displayed: MessageBox. That string.Show (checkedListBox1. For example.ToInt16(txtIndex.Convert class in the Object Browser.

Items.DateTime to or from anything other than string always produces an exception (and no conversion takes place). calling any of the methods that convert the reference type System..Object.Figure 6. I've already used this method quite a bit in this book (including several times in this chapter). The ToString Method Another very useful conversion method is System. an overflow exception is thrown (and no conversion takes place) if you try to stuff too large a value into a type that cannot store it. in the demonstration of reflection in Chapter 5. Note: If a method always throws an exception when invoked. .ToString.Add(method.ToString()). such as ToDateTime(long). For example.4: If you look in the Object Browser. this is noted in the summary in the Descriptions pane of the Object Browser. I used the ToString method to display method information about objects that were being reflected: foreach (MethodInfo method in theMethods){ lstMethods.. all other types inherit it. You should know that the Convert methods throw an exception (and don't do any conversion) when meaningful results cannot be obtained. you'll see that the Convert class has a great many conversion methods. For example. In addition. The good news about the ToString method is that since it is a member of the object type. Why include these methods. which throw an exception whenever invoked and never convert anything? Beats the heck out of me. Whatever your object is. it has a ToString method that delivers a string.

Show(theNumber. followed by the contents of the form's Text property (its caption).ToString()). you'll get the fully qualified form name. For example. the ToString method of an object returns a string that represents the object. you can use the ToString method to display the first four places of the expansion of Π: double theNumber = 3. By definition. but you won't know until you try.a common task in displaying numbers.1415. ToString is implemented so that it returns something reasonably useful.One thing that ToString is always good for is converting numbers to their string representation . if you invoke a Form's ToString method. The problem is that it is up to the implementer of a class to decide what is returned by objects based on the class. Text:". A moment ago. . The bad news is that it may not always be the string you want or need. For example. I told you that the good news is that the ToString of any object will always deliver a string.5. Most of the time. as shown in Figure 6. followed by the literal ". MessageBox. Figure 6. among other things.5: The ToString method of a Form provides quite a bit of information.

in Chapter 1.but the point stands that with complex objects you need to be careful about exactly what ToString returns. Self . See Chapter 9 for information about adding ToString methods to your own classes. of course. // This is another comment. (This is called C .Now. Clear code is largely self .documenting. Here are two examples: // I am a comment! double theNumber = 3. most likely what you really wanted was the unqualified name of the form . Comments should be used in the context of code that is crafted for clarity.called C++ style. 'Creating a Web Service' . For example: /* I am a comment! */ /* I am another comment! */ Self-Documenting Code The content of a comment is. C# also supports comments that begin with /* and end with */..and it is true that you could parse this out of the string returned by the ToString method . which can span multiple lines.but commenting is such an important part of producing good code that it is worth reminding you about them.1415. Note: When it comes time to create your own classes. you should understand that you are responsible for implementing the ToString method in a way that returns a useful value. Commenting Code I described the mechanisms for commenting. Everything on the line following the two forward slash marks is considered a comment and is ignored by the compiler. more important than its form. C# code early in this book..documenting code: .) Everything within these delimiters is a comment. or commenting. Two forward slash marks (//) designate a comment in the so .

you might want to add a comment to the code explaining what it is doing (so that you don't have to figure it out again next time you look at the code). Makes sure that method identifiers express what the method does.such as a class indicating the author. Uses method parameters that clearly convey expected inputs and outputs. and purpose of the type. In another example. XML Documentation . It is also a good general practice to add a comment at the beginning of each type . revision history.• • • • • • Pays attention to the structural and conceptual design of a program and the objects used in it. Uses identifiers intelligently. with notes about anything unusual or not likely to be clear on casual inspection. date. Uses white space and statement layout to promote clarity of intention.documenting. What About Comments? So if the code is clearly written and it's so gosh darn self . Uses class identifiers to convey the nature of objects that can be instantiated based on the class.for example. if you have a complicated double or triple cast and conversion. Doesn't try to make objects (or methods) serve multiple purposes. • • • • So. Uses expressions and statements that are written for clarity of purpose. when should comments be used? Comments should be used to clarify anything that is not otherwise obvious . the expected value range of a method argument. Uses variable and constant identifiers that express the nature of the variable or value being stored. Makes sure that flow control statements clearly express what they are doing.

5. C# lines beginning with three slashes (///) are XML documentation. C# has a full complement of operators in various other words. You should also know that many operators can be overloaded in userdefined types . see Chapter 8).) For more information.As I explained in Chapter 1. the meaning of the operators can be customized when userdefined types. see 'XML Comments' in Chapter 1. Table 6. a special form of commenting. most of which are shown in Table 6. such as classes. a project most likely never gets documented. Operators As you'd expect. The great advantage of using XML documentation is that it is easy to document projects while you work-but harder if someone has to come back later and do it. are involved in operations (for more on overloading. see the 'Tags for Documentation Comments' topic in online help. (The upshot most of the time is that if you don't document it while you do it. For a complete list of the XML tags that can be used with the documentation facility. XML documentation is information contained between tags that can be used to automatically create a set of documentation for a program.5: C# Operators Arithmetic + * / Addition Subtraction Multiplication Division Logical (Boolean and Bitwise) % & Modulus AND . This documentation can be used online in a special viewer provided by Visual Studio or to create a separate documentation file.

Note that this operator.| ^ ! ~ && || OR Exclusive OR NOT Bitwise complement Conditional AND (only evaluates its second operand if necessary) Conditional OR (only evaluates its second operand if necessary) String Concatenation + Concatenates two strings. should not be confused with the assignment operator (=). != < > <= >= . Inequality. Greater than or equal. Less than or equal. which compares two operands. Decrement ++ -Increments operand by 1 (see following text). Less than. Increment. Decrements operand by 1 (see following text). Greater than. Comparison == Equality.

Indexing [] Array indexing (square brackets are also used to specify attributes). Subtraction assignment. see Chapter 7.Assignment = Assigns the value of the right operand to the left operand. Not to be confused with the equality comparison operator (==). Division assignment.' Casting () See 'Explicit Conversion' earlier in this chapter. used to access members of a type. as See 'The as Operator' earlier in this chapter. Member Access . For more about indexing. Multiplication assignment. AND assignment. For example. Modulus assignment. also called the dot operator. OR assignment. Indexers. and Collections. 'Arrays.Text can be used to get or set the Text property of Form1. Form1. The member access operator. Conditional ?: Conditional operator (see following text). Exclusive OR assignment. += -= *= /= %= &= |= ^= Addition assignment. .

also called the ternary operator. For an example that uses the is operator. When placed after the operand (a postfix increment operator). The conditional operator (?:).) Removes a delegate. of a value type. When the increment operator is placed before the operand (a prefix increment operator). This can be a little confusing until you get used to it. The decrement operator works in similar fashion. but probably is a better way of doing .Delegate Addition and Removal + Adds a delegate. Returns the type of an object as a System. the result of the operation is the value of the operand before it has been incremented. is assigned the value 'Condition False' because the expression 1 == 0 always evaluates to false. can be cast to the specified type without throwing an exception. thus causing the second choice to be selected. and = for assignment. A little more precisely. see "RadioButtons and Message Boxes" in Chapter 3. For example. in bytes. - Object Creation new Creates an instance of an object. returns one of two values depending on the value of a conditional expression. The is operator checks to see whether the run . In contrast. the variable whichOne in the expression string whichOne = (1 == 0) ? "Condition True" : "Condition False". Tip: VB programmers are used to one operator symbol (=) performing both assignment and comparison. the result of the operation is the value of the operand after it has been incremented. (Delegates are pointers to methods such as events. which must be a reference type. C# uses different symbols for the two different operators: == for comparison.time type of an object is of a given type. Type Information is sizeof typeof Type comparison (see following text). See 'Adding an Event' in Chapter 5 for an example ofhow to use a delegate. it evaluates true if the test expression. Returns the size.Type object.

However. For example.NET Common Language Runtime (CLR).except in certain specialized situations. When an operand occurs between two operators of equal precedence. . all within "unsafe" code . So have some caution using short-circuit evaluation to make sure that you aren't introducing an unexpected side effect by not evaluating an operand. or the order in which a series of operators are evaluated. and it's obvious why they are unsafe. This means that they do not evaluate the second part of an expression if there is no need to. Code using them cannot be run as "safe" by the . it is unlikely that you will want to use them. I'd hate to see you writing code that uses evaluations performed in a logical operation to perform some other action needed for program success. Short-Circuit Evaluation The conditional AND and OR operators (&& and ||) perform what is sometimes called shortcircuit evaluation. in C#. reference. Generally. As a matter of good programming practice. if you have the expression a && b. Obviously. b will not be evaluated. and a has evaluated to false. This kind of tricky code can be fun to write. there is some performance benefit to not having to look at the second operand. and obtain memory addresses. that has been marked with the unsafe keyword. and can control pointers to memory.>. Although you should know that they exist. If you have direct access to memory. If you don't try to get too fancy for your own good. this contains the seeds of a hidden pitfall if the evaluation of an expression contained in b is required for successful execution. corruption can always occur. (In the classic example. Note: The indirection and address operators (*. and deference pointers. operations are performed from left to right (except in the case of the assignment and conditional operators.things in the long run because using a single operator for a single purpose has less potential for confusion. such as a method. but it violates the precepts of writing clear code and keeping objects and methods from doing more than one thing.that is. you should not have any use for these operators . and &) are used to declare. [ ].) Since b never gets evaluated. the program will fail. such as interoperating with COM objects. code within a context.6 shows operator precedence. you shouldn't have problems with short-circuit evaluations. b is a function that performs other tasks before returning its Boolean result. Table 6. . which are evaluated right to left).

!= & ^ | && || ?: =.and which are fairly straightforward in any case . . typeof Cast operator.and use this section to explain some other C# flow control statements. > . . < . !. and switch statements. % +. & on them leads to unmanageable "spaghetti" code.. A goto statement causes an immediate. order indicated by parentheses. x .. as ==. ~. x++. if…else.Table 6-6: Operator Precedence (in Descending Order of Category Precedence) Category Primary Unary Multiplicative Additive Relational and type testing Equality Logical AND Logical exclusive OR Logical OR Conditional AND Conditional OR Conditional Assignment Operators Member access operator (. %=. <= . /.instead. Order indicated by parentheses takes precedence over operator driven order. *=. goto statements are useful in a special situation . for. So it's worth having a look at the ancient. +=. use parentheses to make the evaluation order explicit and clear. /=. Flow to with goto The lowly goto statement is alive and well in C#. over . +. is. >=. I showed you how to work with break. goto statements are easy to use and direct.=. |= As a matter of good programming practice and to make code clearer to read.with switch statements. the goto. which is an identifier followed by a . >>=. Heaven forbid I should repeat myself! I'll assume that you know how to work with the flow control statements that have already been demonstrated . one that surely gets no respect. In C#. <<=. ++x... . you should not rely on the order of operator precedence if this might be confusing .x *. as is well known. new. however.). except that of the member access operator. unconditional jump to a label. ^=. indexing. foreach. Flow Control Statements In Chapters 3 and 4. decrepit granddaddy of all flow control statements.

done: if (msg != "") MessageBox. The following code in a Button click event procedure uses goto statements and labels to display an appropriate message box if the user enters "John" or "Ringo. For example: IamAlabel: To put this in the context of a short program.colon.OK). goto done. even in this simple .EventArgs e) { string msg = "".6." as shown in Figure 6. suppose the user gets to input a favorite member of the Beatles musical group in a TextBox named txtBeatle. john: msg = "I like John best. A couple of things are worth noting about the code snippet shown above.Text == "John") goto john.Text == "Ringo") goto ringo. "Beatle choice". System.6: Flow control can be achieved using goto statements and labels. if (txtBeatle. and otherwise displays no message: private void btnClickMe_Click(object sender. too!". MessageBoxButtons. goto done. } Figure 6. ringo: msg = "Are you a drummer?". if (txtBeatle.Show(msg. First. goto done.

goto case "John". too!". Previously in this book. Let's say you want to add another possibility to this switch statement. one can see why using goto statements produces fragmented. break. a goto case statement .can be added at the end of the "Mick" case.". MessageBoxButtons. case "Mick": MessageBox. break.even when this wasn't strictly necessary because I only had one statement to execute. the if statements in this example are the simplest form possible."Beatle choice". case "Ringo": . I've used curly braces following the evaluation expression in the if statement. break. Second. Using goto in a switch Of course.Text) { case "John": msg = "I like John many statements as you'd like . For this situation.Text) { case "John": msg = "I like John the "John" case . } if (msg != "") MessageBox. which allows you to include a statement block . you can easily simplify this code by using a switch statement rather than goto statements and labels: string msg = "". switch (txtBeatle.OK). too!". namely the expression to be evaluated followed by a single statement. for people who enter 'Mick' but really mean they like John. confusing code. case "Ringo": msg = "Are you a drummer?". Users who enter "Mick" will be told they really like John and then redirected so they also get the "John" message: switch (txtBeatle. MessageBoxButtons.example.Show(msg.Show("People who like Mick really like John.OK). "Beatle choice".

.Text = caption. but I haven't yet demonstrated while and do…while statements . In contrast. As in most other languages that have a while statement.ToString(). do…while Statements The do…while statement works like the while statement. Statements in a do…while loop will get executed at least onceeven if the condition evaluates to false at the let's take a quick look. if the condition is false. Most of the time. the code within a while statement block is executed as long as the Boolean test at the beginning of the statement evaluates to true. For example. the following while loop displays the integers from 1 to 9 in the title bar of a Form: int i = 1. but there are some times when one works better than the other. } this. i++. I've shown you examples of looping using for and foreach.msg = "Are you a drummer?". the statements in a while loop never get executed at all because the condition is evaluated at the start. break. the same results can be accomplished using either loop syntax. except that the evaluation takes place at the end of the loop. while (i < 10){ caption = caption + " + " + i. execution jumps to the statement immediately following the while statement block. As soon as the test evaluates to false. string caption = "While". } while Statements Looping is an important flow control element of most programs.

} while (A < 50). B = C. Note: If the condition in this loop were impossible. a poor person's lightweight alternative to a class. Like classes. these values are stored in memory allocated for the value-type variable within a structure known as the program's stack. More precisely. and no class or struct can derive from a simple value types . within their own memory allocation.defined type. see Chapter 10. Unlike classes. you might want use a while statement to test for the existence of the data . or data. string caption = "Fibonacci: ". and Reference-Type Variables Value-type variables hold their values. do { caption = caption + A. Structs are value that if the data doesn't exist. as I'll explain in Chapter 8. int B = 1. Note: A class can also be marked so that it cannot be inherited from. A = B. this.Text = caption.Object. Structs A struct is a simple user . which would not be the case in a while loop with the condition at the top.. if you are reading and operating on data. C = A + B. and don't know whether there is any data. by using the sealed keyword. methods. structs derive from System.they can be used without the new keyword. not reference types." Here's a do…while statement that displays the first 9 numbers in the Fibonacci series in the title bar of a form: int A = 1. For more on reading files. e.Note: For example. structs can contain properties. no statements get executed.perhaps testing for an end of file marker . and fields. "Working with Streams and Files. like all types in C#. A < 0. structs do not support inheritance. Value-Type Variables. but they cannot inherit from any other class (or struct). and . and can be accessed from within the program as long as they are within . int C. Technically.ToString() + " ". it would still print the first number in the series. Memory.

MessageBoxButtons. initialized. public Employee (string fullName. which are 'pointers' to objects stored in memory on the heap. Here's one way the Employee struct might be declared. this. MessageBoxIcon.fullName = "Ken Hopper". // Display it string str = HopperK.SSN = 000112222. .rank = "Master Sergeant". In contrast.fullName + " has the rank of " + HopperK. using get and set accessors. // Initialize HopperK.rank + "!".scope. within structs. and used: // Declare an instance Employee HopperK.OK. } } Note: You can create properties. MessageBox. HopperK.Information).fullName = fullName. long SSN) { this.SSN = SSN.Show(str. public long SSN. "Structs Forever!". string rank. rank. Here's a simple example of a struct that might be used to store information about an employee: public struct Employee { public string fullName. Reference-type variables contain instances of classes. reference-type variables-such as classes and strings-are implemented using a global memory structure called the run-time heap. this. HopperK.rank = rank.

If execution cannot continue. and initialize it in one fell swoop: Employee DavisJ = new Employee("Julian Davis".time errors. Exceptions An exception is an unexpected condition in a program. For example.Alternatively. "City Hall". industrial . a reasonably comprehensible message should explain that the program is terminating. explain why. In real . For example. these things happen. Or a file can't be written to disk because the disk drive is full. which in C# is done with exceptions. because it's down. an exception may occur if you attempt to connect to the database but can't.strength programs must anticipate run . and the program can continue execution. you could create the instance of the struct by invoking its practice. these and other errors must be handled. it should. 123456789). and if appropriate. What does it mean to 'handle' an exception (or error)? These are the basics: • • • Your program should not crash under any circumstances. a network resource might not be available because the network is down. The C# Exception object is used to store information about errors and abnormal events. In the release version of a program. If the situation can be recovered from. With the best code in the world. .

the statement will catch any kind of exception. as I'll show you in a moment. Note: Not only is finally optional. In any case. you could add the following code in the button's Click event to catch different kinds of problems: private void btnExceptions_Click(object sender. The program statements in the try block are the body of the program that is being monitored for errors. Using try…catch…finally Statements When an exception does occur.• • • Data should not be lost due to an unplanned failure. As you would expect. as I demonstrated in Chapter 3.ToInt16(txtIn. information necessary for debugging the problem should be saved. as indicated in the catch statement's argument . in which case it is referred to as a general catch clause. either to a file or the system event log. code in the optional finally block is executed.Show.Text).EventArgs e) { int i.or. For example. (It's easy to simultaneously send the user a message and write to the system log with MessageBox. System. whether an error has been caught or not. so is catch. d = 42 / i. try { i = Convert. you can have a try without a finally and also a try…finally construction with no catch block. . double d.) Understanding Structured Exception Handling Structured exception handling is recognized as the best way for a programming language to deal with common errors. if you have a form with a TextBox control and a Button control. The program should shut down gently. if without an argument. In other words. Statements in a catch block are executed in response to a particular exception being thrown. you can use try…catch…finally statements to handle it.

Error). } catch (FormatException){ MessageBox. the catch filter will catch all errors. "Exceptions". A . "Exceptions".OK.Error). MessageBoxIcon. } } In the example.ToInt16. It's possible to have the first catch filter without any conditions: catch statement block You could also just catch the general Exception type: catch (Exception excep) In these cases.OK. Figure 6.Exception (left) and FormatException (right) are caught.7. however. As you can also see in the figure. it is a better idea to use specific catch clauses to handle certain types of exceptions." Often. catch deals with the situation if the user enters zero . go here and take care of it. MessageBoxButtons.processing mechanism along the lines of "if any error happens. In some situations.} catch (DivideByZeroException){ MessageBox. a FormatException catch block can be used to handle the exception that occurs when the user enters something that can't be converted to integer by Convert. shown in Figure 6.Show("Please enter a number!".Show("You are naughty to even think of dividing by zero!".7: The DivideByZero . MessageBoxIcon.which would cause a division by zero . you can use this as a centralized error .

Show("Please enter a number!". Listing 6.1.Show(excep.1. then the specific exception blocks are never processed. MessageBoxIcon. MessageBoxButtons. "Exceptions". MessageBoxIcon. d = 42 / and put the general catch clause before the specific catch exceptions. double d. System.1 shows the exception example with an added. "Exceptions". you can trigger an error that is not handled by one of the specific catch handlers .EventArgs e) { int i. generic catch clause could deal with all exceptions that don't need special handling .ToInt16 (txtIn.or that you didn't think of when you wrote the code. general catch clause at the end.Message).8. . Listing 6.for example. } } Warning: If you reverse the order shown in Listing 6.OK. } catch (DivideByZeroException){ MessageBox. MessageBoxButtons.Error).OK.Show("You are naughty to even think of dividing by zero!".Text). as shown in Figure 6. } catch (FormatException){ MessageBox. try { i = Convert.Error).1: Handling Exceptions with try…catch private void btnExceptions_Click(object sender. } catch (Exception excep) { MessageBox. If you run the code shown in Listing 6. an overflow exception if the user enters a number larger than will fit in the data type.

and line number that threw the exception. This makes the finally clause the place to put clean .9: The ToString method of the Exception object contains a great deal of information about a thrown exception. is that finally clause out of its mind. handled or unhandled. including the procedure.1 to show the information returned by the ToString method: catch (Exception excep) { MessageBox. By the way. it is handled by the general catch clause at the end of the catch blocks.Show(excep.9. Boy.ToString()). if thrown. Figure 6.8: Since the OverflowException is not handled by a specific catch clause. or what? You can't stop it from being executed. } it would appear as shown in Figure 6. regardless of whether exceptions are thrown or.up code that you want executed no matter what happens. method. you could make use of its ToString method. instead of displaying the Message property of the Exception object.Figure 6. If you changed the final catch clause in Listing 6. . which contains a great deal of information.

OK. you'll want to use finally blocks to close open files and database connections. and generally to make sure that all resources used by a program are released.Error).Show("Please enter an integer!". } catch (FormatException){ MessageBox. double d.Show("You are naughty to even think of dividing by zero!".EventArgs e) { int i.Show(excep. } finally { MessageBox.ToString()). MessageBoxButtons.Listing 6. d = 42 / i. MessageBoxButtons. MessageBoxIcon. System. MessageBoxButtons. } catch (Exception excep) { MessageBox.Show(excep.Error). you'll see that the message box invoked in the finally clause is always displayed whether or not the exceptions are handled: Listing 6. "Exceptions".Show("You can't stop me from being executed!". When you run the program.OK.2: Exception Handling with a finally Block Added private void btnExceptions_Click(object sender. try { i = Convert.2 shows the exception handling demonstration with a finally clause added. . "Exceptions".Text). } catch (DivideByZeroException){ MessageBox.ToInt16(txtIn. MessageBoxIcon. } } Note: In the real world. MessageBox.Message). "Exceptions".OK.Information). MessageBoxIcon.

MessageBoxButtons. a Visual Studio exception message is displayed. MessageBoxIcon. d = 42 / i. If you run the stand . the .OK. If Continue is clicked. Of somewhat more interest is the behavior of the stand . System. .What happens if you leave off the catch blocks . Then.alone executable (because exception handling is meant to deal with problems that occur in the run . the finally block code executes.ToInt16(txtIn. "Exceptions". } } If the click event procedure runs without generating exceptions.Information). not at design time). the code in the finally clause is executed and the message box displayed. Within the development environment.time environment. the code in the finally block executes as you'd expect and the message box displays. If Break is clicked. double d.2 to remove the catch blocks: private void btnExceptions_Click(object sender.10 is displayed.Show("You can't stop me from being executed!". try { i = Convert. } finally { MessageBox. the program terminates. if an exception had been thrown. and then the application shuts down. What happens next depends on whether you are running in debug mode in the development environment or whether the compiled executable was launched.EventArgs e) { int i.NET Framework dialog shown in Figure 6.alone executable.resulting in a try…finally statement? We can find out by modifying the code shown in Listing 6.Text).

closed open files. It's not good practice to throw instances of System. Not bad. use the throw keyword with a new instantiation of the Exception class. Throwing custom exceptions should not be done to facilitate normal program communication.Figure 6. . or one of the classes derived from Exception (the Exception object and classes are described in the next section). and then this dialog is displayed. your application should throw only ApplicationException objects. you may want to subclass ApplicationException to add functionality to the exception thrown.Exception. In the real world. ApplicationException is the subclass of Exception used for exceptions thrown by an application. To throw an exception. which occur when a program works properly but produces wrong results. Throwing Exceptions Throwing an exception means creating your own exception under certain conditions.and the finally code did execute before it was displayed. the finally block executes. really. Note: As a good programming practice. as a user interface for all unhandled exceptions . Throwing an exception is what your application should do in many circumstances if it asked to do something impossible.10: When run outside the development environment. so in the real world you could have saved data. if an exception is thrown. or those that inherit ApplicationException. so it's a good one to throw. the application reopens. but it may make sense as part of a scheme for dealing with logical errors.10. and so forth! If the user clicks Continue in the dialog shown in Figure 6.

Listing 6. including throwing and catching the exception related to cockroaches.Show("You have roaches in your program!". there are probably more problems lurking (like roaches). that the user types the phrase 'cockroaches' in the TextBox. } else { .A string representing the Exception.Message == "AttilaTheHun"){ . Here's how to throw a 'roaches' exception: if (txtIn..and software development . MessageBoxIcon. in the example I've used so far to show you exceptions.Message == "roaches"){ MessageBox. There is something in business . This exception would probably be caught within a catch (Exception excep) block using a conditional: catch (Exception excep) { if (excep.Message property is used to instantiate the new exception. as in this example: throw(new ApplicationException("AttilaTheHun")). "Exceptions". which can then be used to catch it. Listing 6..3: Throwing and Catching an Exception .known as the 'cockroach' theory: if one problem becomes apparent..Text == "cockroaches"){ throw(new ApplicationException("roaches")). Let's look at an example of throwing and catching an exception in practice..3 shows the complete code for the example.Error). MessageBoxButtons. Suppose. } The next step is to catch the exception to handle the situation and make sure the roaches check in but don't check out: catch (Exception excep) { if (excep.OK. We take it this means there are more roaches in the system and decide to throw an exception.

MessageBoxButtons. the exception is thrown. } else { MessageBox.11)! . d = 42 / i. MessageBox. "Exceptions". MessageBoxButtons.OK.Show(excep.ToInt16(txtIn. 'cockroaches' entered in the TextBox.Information).Text).Error).Error).private void btnExceptions_Click(object sender.Show("Please enter an integer!". and the button clicked. } catch (Exception excep) { if (excep. MessageBoxIcon.OK.Message).OK. MessageBoxIcon. } catch (FormatException) { MessageBox.Text == "cockroaches") { throw(new ApplicationException("roaches")). } catch (DivideByZeroException) { MessageBox. "Exceptions". MessageBoxIcon. try { if (txtIn.ToString()).Show(excep.Show("You are naughty to even think of dividing by zero!".Show("You have roaches in your program!".OK.Error). double d. MessageBoxButtons. and the bad news is out (Figure 6. } i = Convert.Message == "roaches") { MessageBox.EventArgs e) { int i. System. MessageBoxButtons.Show("You can't stop me from being executed!". "Exceptions". MessageBoxIcon. } } When the program is run. } } finally { MessageBox. "Exceptions".

because they can be used for more than this. This means that .7 shows some of the commonly used properties and methods of the Exception object. (An important exception to this is SqlException. It's worth thinking a little about the nature of an exception object. no matter how much information you add. Table 6. A string that contains the stack trace immediately before the exception was .Figure 6.7: Commonly Used Properties and Methods of the Exception Object Property or Method HelpLink Message StackTrace Purpose A link to the help file associated with the exception. Gets a message that describes the current exception. Table and large .the presence of the exception signals the error like a canary falling ill in a mine. but it should still make sense to deal with your exception as a generic exception (meaning. you should still implement a meaningful "message" property so that programmers can throw and catch it in the normal fashion). Exception Objects So far in this discussion. you certainly can.) The implication is that if you have a compelling reason to add additional information to the exception class. The first thing you should know about the Exception object and the classes that inherit from it is that (for the most part) the subclasses do not vary from Exception by implementing additional members or functionality.the only difference between the parent and child classes is the name of the class.11: This 'cockroach' exception illustrates that you can throw and catch custom exceptions. exceptions have been used only as sentinels . which is thrown when SQL Server returns a rather specific warning or error.

The best place to learn about individual exceptions and their class relationships is the Object Browser (details of which were explained in Chapter 5). TargetSite ToString The method that threw the current exception. Conclusion This chapter has covered a great deal of ground and explained a great deal of the syntax that you need to successfully program in C#. Perhaps she picked up the magic book belonging to Prospero and began to create new worlds and visions… which is my hope for what you will do with the rudiments of C# syntax presented in this chapter . As you would expect. there are a great many subclasses of SystemExceptions.' Of course.thrown. but others are located further down the namespace tree (for example. and SystemExceptions are created by the run . with this material under your belt. IOException is a member of System.IO rather than System directly). some of which you've seen in the examples earlier in this section (for example. FormatException).time (CLR) and operating environment. the joke in The Tempest is that she had seen few people of any kind. the Exception class has two subclasses: ApplicationException and SystemException. One wonders whether they soon became commonplace to her. A string that contains the name of the exception and a great deal of other information. Most of these exception classes are the members of the System namespace. In theory. ApplicationExceptions are created by your application. you can build wonderful things! I started this chapter with Miranda's wonder at the 'goodly creatures' in her 'brave new world. such as the error message and the stack trace. Great flights of fancy and soundly engineered heroic structures cannot be created without good foundations. In general.

. such as those based on the System. Collections . the items in a ListBox are an instance of a collection class. This chapter covers both aspects of dealing with arrays.) If you don't know how to deal with multiple items in arrays (and other classes designed for use with multiples.and the structures based on collections in C#. the techniques for working with this collection is basically similar to the techniques explained in this chapter.are an alternative mechanism for grouping and coping with multiple objects. It's important to pick the right underlying structure for dealing with groups of objects. 'Building a Better Windows Interface. collections. (In fact. then your programs will never scale . and you also need to know how to work with the structure you've selected.or be of much use when dealing in an automated fashion with the large amount of data presented by the real world. with the object being retrieved using an index value.' you learned about working with the Items collection that is used with ListBoxes. ArrayLists. It's worth recalling the material in Chapter 4. programming usually involves groups of objects. Arrays are specifically designed to store groups of objects. Indexers. In Chapter 4. Also. and related classes.Collection classes).Arrays. such as queues. and much more . and Collections Overview • Creating an array • Arrays of structs • Multidimensional and jagged arrays • Changing the lower bound of a C# array • Creating a class with an indexer • Working with collection classes • Pushing and popping stacks • Queues • Dynamic resizing with ArrayList • Implementing a key/value lookup In the real world. because I assume in this chapter that you know how to work with ListBoxes and the ListBox items collection. as you may have already suspected.

Returns the index of the first occurrence of a value in a one . GetLength GetLowerBound Gets the number of elements in a specified dimension of the array. You should know that . Note that this method allows you to specify a lower bound for the array that is non .String . .. Most Array methods are shown in Table 7.dimensional array (or in a portion of the array).g.just as the string keyword is used to create an object of type System.2.dimensional sorted array. or to a null reference. an array is an object that is used to store objects of the same type and provides access to the objects using an index. Gets the lower bound of the specified dimension in the array. not using CreateInstance the lower bound of each dimension will be 0.dimensional array to another onedimensional array.methods and properties . Sets a range of elements in an array to 0. CreateInstance A static method that explicitly instantiates and initializes a new array instance. Returns the index of the last occurrence of a value in a one .type array by calling the default constructor of the value type.Array class. depending on the type of the array elements. and Array properties are shown in Table 7. Table 7. Gets the value of the specified element in the array. Initializes every element of a value . GetUpperBound GetValue IndexOf Initialize LastIndexOf Reverse Gets the upper bound of the specified dimension in the array. Note that if the array has been created using normal syntaxe. Reverses the order of the elements in a one .1: Array Methods Method BinarySearch Clear Copy CopyTo Meaning Searches a one .dimensional array (or in a portion of the array).the syntax and expressions used to create arrays actually create an object based on the System.1.Arrays In C#.dimensional array (or in a portion of the array).zero (see example later in this chapter). to false. Copies all the elements of a one . This means that you can use the members . starting at the specified destination array index in the new array.provided by the Array class when you work with arrays. Copies a range of elements from one array to another.

SetValue Sort IsFixedSize IsReadOnly

Sets the specified element in the current array to the specified value. Sorts the elements in a one - dimensional array. A Boolean value indicating whether the array has a fixed size. A Boolean value indicating whether the array is read - only.

IsSynchronized A Boolean value indicating whether the array is thread - safe. Length Rank SyncRoot The total number of elements in all the dimensions of an array. The number of dimensions of an array. An object that can be used to synchronize access to the array.

Arrays in C# are, for the most part, zero - indexed - meaning that the array indices start at 0 (but see the example later in this chapter that shows how to start an array at another index). One - dimensional arrays can be thought of as a table with one column that can be accessed using an index. Multidimensional arrays use multiple indices to access their values, so a two - dimensional array can be pictured as a table with rows and columns. In a jagged array - also called an array of arrays each 'row' in the array is itself an array, with a potentially different size than the arrays making up the other rows.

Boxing and Unboxing You may have observed that types of a class that are derived from a class can be assigned to an array of that class, even though, as I mentioned above, an array is used to store objects of 'the same type.' Specifically, objects of any type can be stored in an array of objects (since all types are derived from the object type).

For example, the following is legal code, and creates an array of objects that are assigned three different types:

int theInt; string theString; Button1 button1; object [] stuff = new object [3]; stuff [0] = theInt; stuff [1] = theString; stuff [2] = button1;

What has actually happened is that the various types have been implicitly converted to type object. (If you look at the members of each element of the stuff [] array, you'll find the members of an

object type.) However, the extended information relating to the derived type has been preserved. This is called "boxing." To reverse the process, and 'unbox' the types stored as objects, you need to explicitly cast the element of the object array to the original type. For example:

int newInt = (int) stuff [0]; string newString = (string) stuff [1]; Button button2 = (Button) stuff [2];

Creating an Array
Let's start with one - dimensional arrays. The process of creating an array is a three - step dance (although these steps can be combined, as we'll see in a moment): • • • The array must be declared. Next, it is instantiated. Finally, it is initialized with values.

To declare an array, follow a type with square brackets and continue with the array's identifier. For example, you could declare an integer array numbers and a string array names as follows:
int [] numbers; // declares integer array string [] names; // declares string array

To instantiate the array, as you'd expect, the new keyword is used. The statement
numbers = new int [3];

instantiates a new three - element, zero - based array with the previously declared variable numbers. (The elements are numbers[0], numbers[1], and numbers[2].) The two statements can be combined into one, so that you can instantiate while you declare:
int [] numbers = new int[3];

At this point, you should know that the three elements of the integer array numbers have been initialized to 0. (Arrays of reference - type elements are initialized to a null reference.) Note: You can use a constant or variable rather than a literal to size the dimensions of an array. As it turns out, you can initialize the array at the same time as you declare and instantiate it, by placing the initial values within curly braces. Here are one - step examples that declare, instantiate, and initialize an integer array and a string array:
int [] numbers = new int[3] {3,1,4}; string [] names = new string[3] {"Tom", "Dick", "Harry"};

There are a couple of shorthand ways to say the same thing. If you are initializing an array, you can leave off the dimension, in which case it is created with the number of elements specified by the initialization. So these statements create three - element arrays just like the preceding ones:
int [] numbers = new int[] {3,1,4}; string [] names = new string[] {"Tom", "Dick", "Harry"};

If you really prefer to be terse, you can also leave off the new part of the statement (once again, assuming you've provided initial values). The compiler is smart enough to know that it is implied. So here's the shortest way to declare, instantiate, and initialize these two arrays:
int [] numbers = {3,1,4}; string [] names = {"Tom", "Dick", "Harry"}

Moving on, let's try a little example of creating and using an array. Let's suppose we want an array to store the first seven numbers in the Fibonacci series, which comes up in art, nature, mathematics, and mysticism. Here's the shorthand way to create that array and stuff the right values into it:
int [] fibArray = new int[7] {1,1,2,3,5,8,13};

Let's say, instead, that we are fond of iteration. As you probably know, the first two elements of the Fibonacci series are 1; after that, the element n in the series is equal to the sum of the elements (n - 1) and (n - 2). First, we can declare and instantiate the array with seven zero - based elements:
int [] fibArray = new int[7];

Next, we can assign the first two values in the series.
fibArray[0] = fibArray[1] = 1;

Finally, we can use iteration to assign the rest of the values in the array:
for (int i = 2; i < 7; ++i) fibArray[i] = fibArray[i - 1] + fibArray[i - 2];

You can use a message box to display an element of the array to make sure that this has all worked correctly, as shown in Listing 7.1. Listing 7.1: Creating an Integer Array and Displaying an Element
private void btnCreate_Click(object sender, System.EventArgs e) { // int [] fibArray = new int[7] {1,1,2,3,5,8,13}; int [] fibArray = new int[7]; fibArray[0] = fibArray[1] = 1; for (int i = 2; i < 7; ++i) fibArray[i] = fibArray[i - 1] + fibArray[i - 2]; string fifthFib = fibArray[4].ToString(); MessageBox.Show("The fifth number in the Fibonacci series is " + fifthFib, "Arrays", MessageBoxButtons.OK, MessageBoxIcon.Information); }

foreach Statement
The foreach statement is a simple way to iterate through the elements of an array. If we continue with our array of Fibonacci numbers, it's easy to use a foreach statement
foreach (int fib in fibArray) {

lstFib.Items.Add(fib.ToString()); }

to cycle through the Fibonacci array and, one by one, add the string representation of each element to a ListBox (named lstFib). The complete revised click event procedure that creates the array and then uses foreach to cycle through it, adding the elements to the ListBox, is shown in Listing 7.2. If you run the code and then click the button, the Fibonacci numbers will appear in the ListBox as shown in Figure 7.1.

Figure 7.1: The foreach statement can be used to display array elements in a ListBox.

Listing 7.2: Adding the Integer Array to a ListBox
private void btnCreate_Click(object sender, System.EventArgs e) { int [] fibArray = new int[7]; fibArray[0] = fibArray[1] = 1; for (int i = 2; i < 7; ++i) fibArray[i] = fibArray[i - 1] + fibArray[i - 2]; string fifthFib = fibArray[4].ToString(); foreach (int fib in fibArray) { lstFib.Items.Add(fib.ToString()); } }

By the way, if you'd stored the Fibonacci numbers as elements in a string array in the first place (rather than as integers), you could add the elements in the array to the ListBox with a single AddRange

method call. Here's the declaration and instantiation of the Fibonacci array as string:
string [] fibArray = new string[7];

followed by the assignment of the first two numbers, as strings, of course, in the series:
fibArray[0] = fibArray[1] = "1";

To pull off the iteration, we have to get a little tricky in our conversions. First, the n - 1 and n - 2 elements are each converted to integer. The integers are added together, and the result converted back to string:
for (int i = 2; i < 7; ++i) { fibArray[i] = (Convert.ToInt16(fibArray[i - 1]) + Convert.ToInt16(fibArray[i - 2])).ToString(); }

Finally, the payoff - it takes only a single statement to fill the ListBox:
lstFib.Items.AddRange (fibArray);

Listing 7.3 shows the process of filling a string array with strings representing the Fibonacci series and adding them to a ListBox. Listing 7.3: Using AddRange to Add a String Array to a ListBox
string [] fibArray = new string[7]; fibArray[0] = fibArray[1] = "1"; for (int i = 2; i < 7; ++i) { fibArray[i] = (Convert.ToInt16(fibArray[i - 1]) + Convert.ToInt16(fibArray[i - 2])).ToString(); } lstFib.Items.AddRange (fibArray);

Arrays of Structs
Who says the elements of your array have to be simple value types? Often, it makes sense to define classes or structs that are used as the template for each element of an array. As an example, let's go back to the Employee struct defined in Chapter 6, "Zen and Now: The C# Language":

public struct Employee { public string fullName, rank; public long SSN; public Employee(string fullName, string rank, long SSN) { this.fullName = fullName; this.rank = rank; this.SSN = SSN; } }

It's easy to create instances based on this struct. For example:
Employee DavisN = new Employee("Nicholas Davis", "Opera Singer", 12345678);

Next, we can declare, instantiate, and initialize a three - element array with Employee structs for elements (assuming all three struct elements are defined):
Employee [] theRoster = {HopperK, DavisN, DavisJ};

As I explained earlier in this chapter, this statement is shorthand for the more formal
Employee [] theRoster = new Employee [3] {HopperK, DavisN, DavisJ};

Next, if you'd like, you can display some of the information stored in a struct element:
MessageBox.Show(theRoster[1].fullName + " is an " + theRoster[1].rank + ".", "Arrays", MessageBoxButtons.OK, MessageBoxIcon.Information);

Finally, it's easy to use a foreach statement to iterate through the array of structs and add specific field information to a ListBox:
foreach (Employee emp in theRoster) { lstRoster.Items.Add(emp.fullName);


If you run the code shown in Listing 7.4, the contents of the fullname field of each struct in the array will be added to a ListBox (Figure 7.2).

Figure 7.2: You can create arrays of structs, and display struct elements.

Listing 7.4: Creating and Displaying an Array of Structs
public struct Employee { public string fullName, rank; public long SSN; public Employee (string fullName, string rank, long SSN) { this.fullName = fullName; this.rank = rank; this.SSN = SSN; } } private void btnCreate_Click(object sender, System.EventArgs e) { Employee DavisJ = new Employee("Julian Davis", "City Hall", 12345678); Employee DavisN = new Employee("Nicholas Davis", "Opera Singer", 12345678); Employee HopperK = new Employee("Kenneth Hopper", "Proprietor", Employee [] theRoster = {HopperK, DavisN, DavisJ}; MessageBox.Show(theRoster[1].fullName + " is an " + theRoster[1].rank +".", "Arrays", MessageBoxButtons.OK, MessageBoxIcon.Information); foreach (Employee emp in theRoster) { 12345678);

lstRoster.Items.Add(emp.fullName); } }

n-Dimensional Arrays
n - dimensional, or multidimensional, arrays are easy to declare and use. They work just like one dimensional arrays, except that a comma is placed within the square brackets between the array dimensions. It's easy to see why you might want to use a two - dimensional array to represent the 'board' of a game like checkers or chess. n - dimensional arrays become handy when more information is needed to adequately model a situation. For example, a three - dimensional array might be used to store stock prices and volume over time. Here are the declarations for a two - dimensional integer array and a three - dimensional string array:
int [,] numbers; string [,,] words;

Let's have a look at an example. First, declare and instantiate a two - dimensional integer array the2d, with five "rows" and three "columns":
const int rows = 5; const int cols = 3; int [,] the2d = new int [rows, cols];

Next, populate the array by assigning as a value to each element its row times its column:
for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { the2d[i,j] = i * j; } }

Next, iterate through both dimensions of the array. For each element, create a string consisting of its coordinates in the array followed by its value, and add it to a ListBox:
for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) {

string theItem = "the2d [" + i.ToString() + "," + j.ToString() + "] is " + the2d[i,j].ToString() + "."; lstMulti.Items.Add(theItem); } }

If you run the code (see Listing 7.5), you'll see that the contents of the array are displayed in the ListBox (Figure 7.3).

Figure 7.3: It's easy to declare and initialize multidimensional arrays.

Listing 7.5: Creating and Displaying a Two-Dimensional Array
private void btnMulti_Click(object sender, System.EventArgs e) { const int rows = 5; const int cols = 3; // declare 5X3 array int [,] the2d = new int [rows, cols]; // populate the array for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { the2d[i,j] = i*j; }

} // display it for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { string theItem = "the2d [" + i.ToString() + "," + j.ToString() + "] is " + the2d[i,j].ToString() + "."; lstMulti.Items.Add(theItem); } } }

Arrays of Arrays
An array of arrays (also called a jagged array because of its 'unevenness' compared to a standard n dimensional array) is an array where each row is a one - dimensional array. Jagged arrays can be declared and instantiated in a single statement - using side - by - side square bracesbut there is no way to initialize the elements of the jagged array in the same statement. For example, here's how you might declare a two - dimensional jagged string array, with the first dimension having seven rows and the second dimension varying - considerably - in the number of elements:
const int rows = 7; string [] [] jaggedA = new string [rows] []; jaggedA[0] = new string [2]; jaggedA[1] = new string [3]; jaggedA[2] = new string [1]; jaggedA[3] = new string [4]; jaggedA[4] = new string [40]; jaggedA[5] = new string [2]; jaggedA[6] = new string [86];

Next, individual elements could be assigned values. For example:
jaggedA [1] [2] = "jagged";

Some of the jagged one - dimensional arrays within arrays are filled using iteration:
for (int dash = 0; dash < 86; dash++)

Listing 7. i < 2. string [] [] jaggedA = new string [rows] []. Populating. Figure 7.4). jaggedA[3] = new string [4]. nested iteration is not easy). a graph depiction of 'jaggedness' will appear in the ListBox (Figure 7. System. Next.6: Declaring. . Listing 7.4: When the column elements of a jagged string array are displayed in a ListBox. jaggedA[2] = new string [1]. } lstMulti.6 shows the rather messy code that does this for the entire seven rows (messy because with each 'column' array a different size. rows of unequal length are created.jaggedA [6] [dash] = "-".Items. for (int i = 0. If you run the code. and Displaying a Jagged String Array private void btnJagged_Click(object sender.EventArgs e) { // declare a jagged array with 7 rows const int rows = 7. i++) { column = column + " " + jaggedA [0] [i] + " ". // give it some column arrays jaggedA[0] = new string [2]. jaggedA[1] = new string [3]. each 'column' can be displayed as a single concatenated item in a ListBox: string column = "".Add(column).

i < 2. jaggedA[5] = new string [2]. // fill the final three columns with dashes (-) for (int dash = 0. dash < 86. jaggedA [1] [0] = "a". for (int i = 0. } lstMulti.Items. column = "".". // populate it jaggedA [0] [0] = "This". jaggedA [1] [1] = "very". for (int dash = 0. jaggedA [3] [3] = "uneven.Items. jaggedA [2] [0] = "array. dash++) jaggedA [5] [dash] = "-". i++) { column = column + " " + jaggedA [1] [i] + " ". jaggedA [3] [1] = "looks". for (int i = 0. for (int i = 0. dash < 40. jaggedA [3] [2] = "extremely".Items. jaggedA [0] [1] = "is".Add(column). dash++) jaggedA [6] [dash] = "-". i++) { column = column + " " + jaggedA [2] [i] + " ". } lstMulti. } lstMulti. dash < 2. .". dash++) jaggedA [4] [dash] = "-". // display it string column = "".Add(column). for (int dash = 0.Add(column). column = "". i < 1. jaggedA [1] [2] = "jagged".jaggedA[4] = new string [40]. i++) { column = column + " " + jaggedA [0] [i] + " ". i < 3. jaggedA[6] = new string [86]. jaggedA [3] [0] = "It".

but you could use multidimensional arrays to create an array with different . i++) { column = column + jaggedA [6] [i]. for (int i = 0. for (int i = 0. for (int i = 0.column = "".dimensional arrays to create an array with six elements where the index of the first element is five . } lstMulti. First. i++) { column = column + jaggedA [5] [i]. i < 40. i < 2.that we've come to expect when working with arrays. column = "". } lstMulti.Items.Items. column = "". In this example. you need to create two integer arrays.such as square bracket notation .Add(column). } lstMulti. i++) { column = column + jaggedA [4] [i].Add(column).Items. for (int i = 0. column = "". } lstMulti. i < 4. I've used one . } Creating a Non-zero Lower Bound I teased you towards the beginning of the chapter by saying I would show you how to create an array with a non . The first array is used to store in its elements the size of each dimension you wish to create (the number of elements indicates the number of dimensions).Add(column). i < 86.Items. because arrays created in the way shown in this example do not have many of the conveniences . The second array stores the lower bound for each lower bounds very often in lower bound. Here goes! Warning: You probably won't want to use non .Add(column). i++) { column = column + " " + jaggedA [3] [i] + " ".

ToString()). The GetLowerBound method.GetLowerBound(0).numbers of elements and different lower bounds for different dimensions. i++) { theArray. in this case. for an integer array. The first argument of the method is the System. The CreateInstance method is static. Here are statements that create the two arrays: int [] theLengths = new int [1] {6}. it does its own creating of instances.ToString()).SetValue(i.Show(theArray. MessageBox. shows that the lower bound is what it is supposed to be. theBounds).i). indicating that an object of the Array class is not instantiated to use it .GetValue(7). only dimension of the array.Type of the array to be created. which is derived using the typeof operator . the name of the method implies.lower bound array in the variable theArray.CreateInstance(typeof(int).ToString()). int [] theBounds = new int [1] {5}.GetValue(2). The second and third arguments are the arrays we previously created.Array class is used to create a non . if you try to invoke an element using an index that would normally be in . theLengths.Show(theArray. i < 11. the CreateInstance method of the System. Next.bounds for a six element array.Show(theArray. 5: MessageBox. To test this. } We can then use the GetValue method to retrieve and display a value by its index: MessageBox. which represent the size and lower bound of each dimension: Array theArray = Array.for example. let's assign values to each of the putative elements of the array: for (int i = 5. typeof(int).and. used with an argument of 0 to retrieve the first . .zero .

and Testing an Array with a Specified (Non-zero) Lower Bound private void btnNonZero_Click(object sender. i++) { theArray. they neither need nor have a name.SetValue(i. } MessageBox.Show(theArray.EventArgs e) { int [] theLengths = new int [1] {6}. providing that the index passed to the indexer is within range of the array. for (int i = 5. Populating. Listing 7. which is private to the class. System.bound array is shown in Listing 7.7. Since indexers use the square bracket array notation. MessageBox. theLengths. Listing out .i). The class encapsulates a 42 .ToString()).GetLowerBound(0). Array theArray = Array.8 shows a class that contains an indexer. MessageBox.7: Creating.of . and the indexer simply provides a means of saving or retrieving array values.ToString()).Show(theArray. int [] theBounds = new int [1] {5}.Show(theArray. The code for generating and testing the non . } Indexers An indexer is a class member that allows an object to be referenced in the same way as an array using square brackets ([]).8: Creating a Class with an Indexer class IAmAnIndexerClass { private int [] theArray = new int[42]. i < 11.CreateInstance(typeof(int).lower .ToString()). //declare the indexer public int this [int ndx] { .element integer array. Listing 7. theBounds).GetValue(2).zero .GetValue(7).bounds exception is fired.

Cursor = Cursors.SybexC2Service theService = new theService. you might want to turn the mouse pointer to an hourglass so that the user knows to expect a delay: this.8.' Before invoking the web service. an object of the type of the class must be instantiated: IAmAnIndexerClass iaaio = new IAmAnIndexerClass().. you'll need to add a web reference to the SybexC2 service as explained in Chapter 2. the value of the element is left at the default. To use the IAmAnIndexerClass class. otherwise.WaitCursors.just like in a property statement. If the index of the element is a prime. As a first step. you'll see get and set accessors within the indexer . } If you look at Listing 7. I'll use the IAmAnIndexerClass indexer to access iaaio .short for "I am an indexer object" . The service needs to be named and instantiated as theService: theService. Each "element" in iaaio will be examined using the IsPrime web service developed in the beginning of this book.get { if (ndx < 0 || ndx >= 42) return 0.SybexC2Service(). } set { if (!(ndx < 0 || ndx >= 42)) theArray [ndx] = value.. then that prime is saved to the element. } } } Here's the indexer declaration: public int this [int ndx] { . which is 0. else return theArray [ndx]. As a demonstration.using array syntax. 'Consuming the Service on the Web. .

Listing 7. the second argument sent to the IsPrime method represents a delay in seconds.9. Finally. the mouse pointer should be returned to the normal default: this. System. . as shown here: The code for instantiating an object based on the class and accessing its members using the indexer is shown in Listing 7. we can do a bit of displaying to make sure this came out right.EventArgs e) { IAmAnIndexerClass iaaio = new IAmAnIndexerClass().9: Instantiating an Object Based on the Indexer Class and Accessing Its Members Using Index Syntax private void btnIndexer_Click(object sender.Next. Since 23 is a prime and 24 is not.Cursor = Cursors. i < 42 .WaitCursors. Once the calls to the web service have completed. i++) { if (theService.SybexC2Service theService = new theService. the value of iaaio[23] should be 23 and the value of iaaio[24] should be 0.SybexC2Service().IsPrime(i. we can easily check whether a given integer is prime.Cursor = Cursors. use the iaaio indexer to store the value: for (int i = 0.Default. this. } Note: As you may remember from Chapter 2. this is irrelevant to the current task.0)) iaaio [i] = i. theService. and if it is.

IsPrime(i. structures that are based on classes other than System. } Collection Classes In quite a few cases. Queue. i++) { if (theService.3 describes some of the most useful collection classes. MessageBoxButtons. i < 42 .Collections. } this.Collections namespace (some of the classes that are based on Dictionary structures are located in the System.0)) iaaio [i] = i.ToString() + " and iaaio [24] is " + iaaio [24].Information). As you can see in Figure 7. We'll have a look at working with objects based on some of these classes . In the meantime.5.5: You can use the Object Browser to inspect the collection classes that are the members of System. you should know that these classes appear for the most part in the System.for (int i = 0. and SortedList .Default.Specialized namespace). Stack. You should know that each one of these .ArrayList.Collections.ToString(). MessageBox.Cursor = Cursors.Array will work better to organize groups of items for specific purposes. MessageBoxIcon. Figure 7.Collections.OK. you can find out a great deal about these classes by pointing the Object Browser at System. Table 7.Show("iaaio [23] is " + iaaio [23]. "Indexers".in the remainder of this chapter.

familiar structure whose size is dynamically altered as items are added and removed (see example later in this chapter). and can contain abstract (or nonimplemented) membersfor acollection. Used to create a first in. first out collection of objects (see example later in thischapter). 'ArrayList Members' or 'Queue Members'). and indexers of the named interface. it tells any object that uses the class that it will support the methods. This means that objects based on classes that implement the interface all work in the same. but if you are programming an object based on one of these classes. Collection Interfaces An interface provides a binding contract with any class that uses the members specified in the interface. first out collection of objects (see example later in this chapter). start with a capital I .style collection of key/pairs. CollectionBase Provides the abstract base classmeaning the class cannot be instantiated. Internally.1 and 7.2.NET Framework. comfortable. when a class implements an interface. Hashtable Queue SortedList Used to create a collection of key/value pairs that are organized based on the hash code of the key. properties. the syntax of an interface looks like the signatures for a bunch of methods. DictionaryBase Provides the abstract base class for a dictionary . In other words.) Table 7. without the implementation specifics for these members. . I discuss interfaces further in Chapter 9. Stack You can find out a lot about collection classes and class members using the Object Browser (and auto completion in the Code Editor). IEnumerator.for example.3: Useful Collection Classes Class ArrayList Description Used to create an array .classes has quite a few members (properties and methods) that you will need to know about to successfully use the class. only inherited. Used to create a collection of key/value pairs that are sorted by the keys and are accessible by key and by index so it combines the features of an array with those of a dictionary (see example later in this chapter). etc. by convention. Interfaces in the . Used to create a last in. events. you really should review the full list of the (copious) properties and methods by looking up the class name followed by the word Members in online help (for example. properties. (Most of these classes have members comparable in extent and utility to the Array members shown in Tables 7.

The current mouse pointer can always be pushed on the stack. Table 7."Everything Is String Manipulation. using a custom hash function. enumerators. The most recent thing to go on the stack (in stack terminology. Represents a collection of objects that can be individually accessed by index. if you need to implement a custom data structure. Stacks A stack is a last in. it goes away.' which returns the object on top of the stack (or queue) without removing it from the stack.Stack class. but why go to the trouble of doing this if the functionality you need is already built into the Stack class? The essential difference between stacks and queues on the one hand. Note: In addition to pushing and popping. the last pointer placed on it becomes the current mouse pointer." Table 7.Collections. which supports a simple iteration over a collection. is that when you pop an item off the stack or queue. Supplies a hash code for an object. When the stack is popped. In addition. first out (LIFO) collection of items. . Here's how it will work: the user will be able to change the mouse pointer to a random pointer (referred to as the "current" mouse pointer). stacks (and queues) support 'peeking. I'll show you how this works in the Queue demonstration.4 shows some of the interfaces implemented by the collection classes. IEnumerable IEnumerator IHashCodeProvider IList Exposes the enumerator. An array can fairly easily be used to simulate a stack (or a queue). and arrays and lists on the other. IDictionaryEnumerator Enumerates the elements of a dictionary. let's write an application that puts the current form of the mouse pointer (or cursor) on a stack. Exposes a method that compares two objects. Arrays don't normally work this way. and synchronization methods for all collections. 'pushed') is also the first to come off the stack (called 'popped'). Supports a simple iteration over a collection. you should plan to implement at least some of these interfaces. Represents a collection of key/value pairs.4: Selected Collection Interfaces Interface ICollection IComparer IDictionary Description Defines size. To demonstrate the System.

placed on a stack. Our application will need: • A mechanism for choosing a new mouse pointer. . First let's look at Listing 7. which shows the click event that randomly selects a new cursor and assigns it to the form. by the Cursors enumeration value Default (for example. with one button for each task.6: The current cursor can be changed. A way to push the stack to store the current mouse pointer.Cursor = Cursors. a new Random object is created using a default seed value.Before we get started.Default). A way to pop the stack to make the mouse pointer on the top of the stack the current cursor. and loads it with all possible values of the Cursors enumeration. Next. naturally enough.10. • • Figure 7. This will be accomplished by randomly choosing a new enumeration value from the Cursors enumeration list.6 shows the interface that will be used to accomplish this. and popped off the stack. you should know that the mouse pointer is set using a form's Cursor property. Form1. the default mouse pointer being indicated. This code declares an array of type Cursor named acursor. Figure 7.

Cursors.PanNW.Hand. is then assigned to the form's current cursor.Random class generates pseudo .PanWest. Cursors.PanNorth. Cursors.Help. Cursors. Cursors. Cursors. Cursors.Push(this. Cursors. Cursors.Cross. Cursors.Next(27)]. Cursors.No. System.UpArrow.SizeNS. Cursors.Cursor = cur.WaitCursor }.EventArgs e) { curStack.Arrow. Random rnd = new Random(). System. First. a new Stack object needs to be instantiated. which is of type Cursor. Cursor cur = acursor[rnd. Cursors. Cursors.EventArgs e) { Cursor [] acursor = { Cursors.10: Changing the Cursor to a Random Cursor private void btnChange_Click(object sender. this.IBeam.PanSW. this click event code pushes the current form mouse pointer onto the stack: private void btnPush_Click(object sender.VSplit. With a Random object in place. Cursors.SizeNESW. } It's actually really easy to push a cursor on the stack. Cursors. using the variable curStack: Stack curStack = new Stack().SizeAll. Cursors. Cursors. Cursors. Cursors.HSplit.NoMoveVert. so the acursor array index goes between 0 and 27). Cursors. The selected element.random numbers and should not be relied on to return truly random numbers in applications that involve things like cryptography.SizeWE.PanEast. Cursors. Next.NoMoveHoriz. Cursors.AppStarting. Listing 7.Default.PanNE. its Next method is used to generate a random integer between 0 and 27 that is used to select an element (there are 28 members of the Cursors enumeration.Cursor). Cursors.PanSouth. Cursors.NoMove2D. Cursors. } .Warning: The System.SizeNWSE.PanSE. Cursors.

by using the curStack.Pop(). If you run the demonstration application. "Too tired to pop!". else MessageBox. System. } The code for pushing and popping the stack is shown in Listing error message will be displayed: Listing 7.11: Pushing and Popping a Cursor on a Stack . If you've popped everything off the stack .OK. . You need to make sure that something is actually on the stack .Count > 0) this.Popping the stack is only slightly trickier. MessageBoxIcon.Count property . "Too tired to pop!".EventArgs e) { if (curStack. System.OK.Pop(). MessageBoxButtons.or there isn't anything on it to begin with . } private void btnPop_Click(object sender.Show("Nothing on the stack to pop!".Exclamation). else MessageBox.before you try to pop: private void btnPop_Click(object sender. place the current mouse pointer on the stack.11.Cursor = (Cursor) curStack. Stack curStack = new Stack().EventArgs e) { curStack. System.Push(this.Cursor = (Cursor) curStack. MessageBoxButtons.EventArgs e) { if (curStack.Exclamation). you'll see that you can change the mouse pointer for a random new selection.Cursor)... MessageBoxIcon.Count > 0) this. private void btnPush_Click(object sender.Show("Nothing on the stack to pop!". and assign the mouse pointer on the top of the stack to the form.

The user can use TextBoxes and Buttons to put a string on the queue (Enqueue). the Dequeue method returns (and removes) the item that is at the front of the queue. As an example. which is updated every time an item is put on or taken off of the queue.Add(s)..} ..' The idea is that if you are the first one waiting for the ticket booth to open. as the British say.Text = "". txtIn. take the front string off the queue (Dequeue). although (as you'd suspect) a check needs to be added to see that there is actually something on the queue: . let's set up a queue of strings.Text). foreach (string s in theQueue) { lstQ.Items. System.Items. except that the objects collected by them are first in. The contents of the queue are shown in this ListBox. you should be the first one able to buy tickets. I've added a ListBox named lstQ and set its Enabled property to False in the Properties window. First. or just have a quiet look at the front string (Peek). The Enqueue method puts an item on the queue. first out (FIFO). To make this application a little clearer to follow.Enqueue(txtIn. a Queue object needs to be declared and instantiated: Queue theQueue = new Queue(). 'on queue. Queues Queues are just like stacks.EventArgs e) { theQueue. lstQ. } } Dequeuing is pretty much the same thing. Here's the code to enqueue a string: private void btnEnqueue_Click(object sender. and the Peek method looks at the item at the front of the queue (without removing it from the queue).Clear(). The metaphor is waiting in line or.

Peek(). 'Able' 'Was' 'I' 'Ere' 'I' 'Saw' 'Elba'. "You peeked!". "No more waiting in line!". After you've entered the last word in your string. MessageBoxButtons.Clear().for example. but not from object to string.Exclamation).Exclamation).Add(s). since items on the queue are maintained as type object. MessageBoxButtons. } } else MessageBox. enter a text string word by word in the Text In box .Exclamation).private void btnDequeue_Click(object sender.EventArgs e) { if (theQueue. MessageBox. } You should also note that the string value could be enqueued directly. Peeking works like dequeuing: private void btnPeek_Click(object sender.Items. Tip: You can implicitly cast from string to object.Count > 0) { txtOut. MessageBoxIcon. MessageBoxIcon.7). foreach (string s in theQueue) { lstQ.OK.Show("Nothing on the queue to peek at!".EventArgs e) { if (theQueue.OK. it needs to be cast to string.Count > 0) { string str = (string) theQueue. Next.Dequeue().OK. lstQ. you can peek to make sure the first word is at the head of the queue (Figure 7. MessageBoxButtons. System. .Show("Nothing on the queue to dequeue!". "You peeked!". but when it is dequeued. } It's time to compile and run the project. MessageBoxIcon. } else MessageBox. System.Items.Text = (string) theQueue.Show("'" + str + "' is at the head of the queue!".

pretty soon there will be nothing left 'in line' to dequeue (Figure 7.Figure 7. peeking discloses that 'Was' is at the front of the queue. dequeue the first item ('Able'). If you peek now.7: Peeking retrieves the object at the front of the queue without removing it from the queue. Next. you'll see that 'Was' is now at the front of the queue (Figure 7.9) all the words must have bought their tickets! . Of course. if you keep dequeuing. Figure 7.8).8: With 'Able' dequeued.

12.Count > 0) txtOut.OK. foreach (string s in theQueue) { lstQ.EventArgs e) { if (theQueue.. System. lstQ. MessageBoxIcon. System. and peeking is shown in Listing 7.Enqueue(txtIn. else MessageBox.Dequeue(). private void btnEnqueue_Click(object sender.Clear().Items. System. . "No more waiting in line!".Exclamation). the code for queuing.EventArgs e) { if (theQueue. We'll be discussing queues further in Chapter 11 in the context of messaging.Text).Text = "". } private void btnPeek_Click(object sender. MessageBoxButtons. enqueuing. For now. txtIn.Show("Nothing on the queue to dequeue!".Text = (string) theQueue. Listing 7..Items.Add(s).EventArgs e) { theQueue. and Peeking . Queue theQueue = new Queue().Peek(). } } private void btnDequeue_Click(object sender.12: Queuing.9: It's important to check that something is on the queue before attempting to dequeue (or peek).Figure 7. Dequeuing.Count > 0) { string str = (string) theQueue.

10 shows the user interface (in design mode) that we'll use for this purpose. This ErrorProvider . and .MessageBox. the ErrorProvider sits on the 'tray' at the bottom of a form.OK.Show("'" + str + "' is at the head of the queue!".in a (hopefully) profitable detour from the main topic of this section . MessageBoxIcon. "You peeked!".how to display a single element by index and how to display all ArrayList elements.OK. "You peeked!". except that it's dynamically resized depending on how many elements are actually stored in it.. Note: that an ErrorProvider component has been added from the Toolbox to the 'tray' at the bottom of the form. } else MessageBox. Tip: VB6 programmers will welcome this functionality as comparable to the ReDim statement. } .. MessageBoxButtons. Figure 7. MessageBoxButtons. The demonstration application in this section shows you how to add and remove objects from an ArrayList.more or less for purposes of verifying that the structure is working .Exclamation). ArrayList The ArrayList works like an array.10: In design mode.Show("Nothing on the queue to peek at!". MessageBoxIcon. Figure 7.Exclamation).

You'll find the code for adding a text element to the ArrayList.ComponentModel. the TextBox Text property is set in code to 0. Next.11). Figure 7.SetError(txtIndex. and the error message specified in Listing 7. Listing 7. presumably an innocuous value.Text). "Requires an integer value!"). } } If the user tries to enter nonnumeric data in this TextBox and then navigates away from the field. an ArrayList object is instantiated: ArrayList al = new ArrayList().Text = "0".13: Using an ErrorProvider to Validate Numeric Input private void txtIndex_Validating(object sender. The routine for working with the ArrayList should seem pretty familiar by now.11: If the user input in the TextBox fails the validation because it is a noninteger value . txtIndex. and displaying all elements along with an ArrayList count. error message is displayed. add the code shown in Listing 7.CancelEventArgs e) { try { int x = Int32. removing an element by index.. System. To set this up. a red warning icon appears. in Listing .SetError(txtIndex. } catch { errorProvider1.13 is displayed (see Figure 7.will be used to check that user input in the Index TextBox really is a number.13 to the Validating event of the TextBox to check. First. displaying an element by index. "").Parse(txtIndex.

14. } private void btnDisplay_Click(object sender. if (txtIndex. } private void btnRemove_Click(object sender. System. System. txtIn.Show("Please try to keep within range!".". MessageBoxIcon.ToInt32(txtIndex.Count) { MessageBox.ToInt32(txtIndex. txtIndex. else i = Convert. private void btnAdd_Click(object sender.Count) { al.Clear(). if (txtIndex.ToString() + ".. .Clear(). ArrayList al = new ArrayList(). "ArrayList Demo". else i = Convert. Removing.EventArgs e) { int i.Add(txtIn. txtIndex.Text).Information).RemoveAt(i). } else MessageBox.Information).Text). MessageBoxButtons.Text == "") i = 0.Text). MessageBoxButtons.OK.14: Adding.EventArgs e) { al.EventArgs e) { int i. "ArrayList Demo". MessageBoxIcon.Show("Element " + txtIndex.Clear()..Text + " of the ArrayList is " + al[i]. System. Listing 7. } else MessageBox.Show("Please try to keep within range!". and Displaying the Items in an ArrayList . "ArrayList Demo". if (i >= 0 && i < al.Text == "") i = 0. if (i >= 0 && i < al.OK.7.

.12). lstDisplay.Count.Items.Add(al[i]).EventArgs e) { for (int i = 0. such as HashTable and SortedList in the System.Collections namespace and HybridDictionary. ListDictionary.12: It's easy to dynamically resize ArrayLists. MessageBoxIcon. Figure 7. There are several dictionary classes that can be used.OK. } .Add("ARRAY COUNT IS: " + al. } private void btnDisAll_Click(object sender.Specialized namespace.the key is used to access the value. and StringDictionary in the System. System.MessageBoxButtons.Collections.ToString()). i++) lstDisplay. Run the application and play with it to verify that the ArrayList is resizing dynamically (Figure 7.Information). . i < al. Dictionaries Dictionaries are structures that implement the IDictionary interface providing for a collection of keys and values ..Items.Count.

AliceBlue. add a new Form to the project by selecting Project > Add Windows Form. private System. I'll show you how to retrieve these values using indices as well as key/value pairs. in the original form and at the form class level.which happens to be the first value in the Color enumeration .AllowFullOpen = true. and clicking Open. follows: private void btnColor_Click(object sender.The SortedList Class One of the more useful classes that implements IDictionary is SortedList.AliceBlue .AnyColor = true.BackColor = theColor.which also sets the background color of the button that invokes it . btnColor. a Color variable initialized to the quaintly named Color. The user interface consists of a TextBox for the user to enter the text. theColor = colorDialog1.ShowDialog(). SortedList is actually kind of a cross .14 at the end of this section).breed because it implements both dictionary key/value access and also array style access by index. The demonstration example will show you how to save a text string and a color to a SortedList using key/value pairing. The text and color will then be retrieved from the SortedList and used to populate the Text and BackColor properties. Next. selecting Windows Form in the Add New Item dialog. SortedList sl = new SortedList(). another button to save the choices to the SortedList.and the SortedList: Form2 Form2 = new Form2().for the user to select a color. a button that opens the Color common dialog .Color.Windows. then instantiate the new Form. of a form. declare a Form variable.13 and 7. a ColorDialog variable. and a third button to retrieve the values and use them to populate the properties of a new form (this user interface is shown in Figures 7.ColorDialog colorDialog1. System.explained in Chapter 4 . } Save the keys and values to the SortedList: . As a preliminary.EventArgs e) { colorDialog1. respectively. colorDialog1. Implement the Color common dialog .Forms. Color theColor = Color.

Text = sl["Text"]. anyhow. } The only thing remaining is to show the new form. if you wanted to use an index.13). Actually. .EventArgs e) { Form2.GetByIndex(0). Form2. It is time to run the project. retrieve the values. Form2.Show(). Here's how the procedure looks using the dictionary functionality of the SortedList: private void btnGet_Click(object sender. sl.once again .Text).private void btnSave_Click(object sender.ToString(). the text value that has been saved as an object must be reconverted to a string. Here's how this would look if one were using the index features of the SortedList: private void btnGet_Click(object has been stored as simply an object. } Note: once again that conversion and casting from object to string and Color is required. Form2.Show().BackColor = (Color) sl["BackColor"].EventArgs e) { sl. System. I think it's much more fun to use keys and values than an index. and click the Choose Color button to open the Color dialog (Figure 7. System. and. Similarly. Form2.txtText. Enter a text string in the TextBox.EventArgs e) { Form2.Text = sl.Add("Text".BackColor = (Color) sl. } Note: that the BackColor has to be cast to (Color) since . you'd have used an array in the first place. and use them to set the Form2 properties. System. theColor).ToString().GetByIndex(1).Add("BackColor".

Finally. Color theColor = Color. private System.EventArgs e) { colorDialog1. these choices are then saved with appropriate keys to a SortedList.14)..13: The user enters text and a color via the common dialog.Figure 7.ColorDialog colorDialog1.Forms.AnyColor = true.. Figure 7.AliceBlue.AllowFullOpen = true. Form2 Form2 = new Form2(). colorDialog1. Listing 7. private void btnColor_Click(object sender. Next.14: User selections are retrieved by key from the SortedList and applied to a new form. click the third button to display the new form with the properties retrieved from the SortedList (Figure 7. SortedList sl = new SortedList(). save the changes to the SortedList. System.15: Using a SortedList to Store and Retrieve Text and a Color by Key .Windows. .

colorDialog1.ShowDialog(); theColor = colorDialog1.Color; btnColor.BackColor = theColor; } private void btnSave_Click(object sender, System.EventArgs e) { sl.Add("Text", txtText.Text); sl.Add("BackColor", theColor); } private void btnGet_Click(object sender, System.EventArgs e) { Form2.Show(); // Form2.Text = sl.GetByIndex(1).ToString(); Form2.Text = sl["Text"].ToString(); // Form2.BackColor = (Color) sl.GetByIndex(0); Form2.BackColor = (Color) sl["BackColor"]; } ...

This chapter explained how to work with arrays and other structures, such as collections, stacks, and queues, for storing groups of objects. This is not the most exciting topic in the universe, but it has great utility. Almost all programs use these structures. Choosing the proper structure for manipulation of your data, and implementing it correctly, will go a long way towards assuring the soundness of your projects. Let's move to something that is truly exciting, and the heart of programming in C#: objectoriented programming

The Life of the Object in C#
• Guns, Germs, and Steel • The Facade pattern • Working with classes, class members, and access modifiers • Instance vs. static members • Constructors • Working with instances of forms • Working with methods, including overriding virtual methods • Five steps to creating an event • Overloading methods and operators • Polymorphism • Creating a custom collection Is object - oriented programming 'much ado about nothing'? (Please don't

hate me because I dare to ask the unthinkable.) In the words of Bertrand Meyer, a distinguished OOP evangelist and creator of the Eiffel language, 'Object technology is at its core the combination of four ideas: a structuring method, a reliability discipline, an epistemological principle, and a classification technique.' In my opinion, these four ideas boil down as follows:
• The 'structuring method' is the concept of classes used as the blueprint for objects. • The 'reliability discipline' is the concept of a contract between objects. • The 'epistemological principle' is the idea of describing classes by their interfaces and members. • The 'classification technique' creates a taxonomy for a given domain by following the chain of class

inheritance within the domain. I believe that object - oriented programming is much, much more than a fad and is here to stay as an important part of the way software developers work. But it's also not suitable as the solution to every problem. (Remember the saying that, to a hammer, everything looks like a nail.) In non - OOP procedural languages - such as FORTRAN and early versions of Visual Basic - one can create structures that simulate an object - oriented programming environment. Oddly enough, in a completely object - oriented environment such as C#, sometimes one wants to do the reverse: create procedural code and ignore (as much as possible) C#'s classes.

Object - oriented programming in C# has two faces: inward and outward. The inward face is the C# language and the .NET class libraries: no matter what you do, you are writing code that is placed in the context of an OOP system, so you may as well make good use of it as much as you can. The outward face is projects that you create, in which you can use the taxonomy, structures, and mechanisms of C# as models for how your classes should be structured and what your code should look like (that is, if you are not creating a single procedural piece of code with a click event). The chapter details the mechanics of creating OOP applications in C#.

Guns, Germs, and Steel
Jared Diamond's Pulitzer Prize - winning book Guns, Germs, and Steel (W.W. Norton, 1996) is an attempt to answer the broad question of why some human societies achieve material success and others do not. Diamond's answer - in short - is that the fate of human societies depends on the availability of various kinds of resources far more than on any cultural or racial differences between peoples. He summarizes his own book as follows: History followed different courses for different peoples because of differences among peoples' environments, not because of biological differences among peoples themselves. In his book, Diamond has created a wonderfully deterministic model for predicting the success of human populations. As I explain in more detail below, according to this model, the extent that certain environmental resources are present determine whether a given society will succeed and expand - if they are not, the society will never achieve the critical mass required for success and will be easy prey for assimilation by a more successful society. At this point, you, dear reader, may well be wondering what a section on the historical causes of the fate of human societies is doing in a programming book. The answer is that I have adopted - and greatly simplified - the model proposed in Guns, Germs, and Steel to create the running example of object oriented programming used in this chapter. In this example, 'tribes' are created with an initial population and resource characteristics. We can then see whether over time these tribes succeed (turning first into city - states and then large civilizations), become static (reaching an equilibrium situation in which the population neither expands nor contracts), or wither (and become extinct). We can also see what happens when two tribes clash. For example, our model should be able to predict the outcome of the one - sided sixteenth - century conflict between the Spaniards and the Incas, which is used as a case study in Guns, Germs, and Steel. In Diamond's book, numerous factors (including population density, raw materials, climate, topography,

and the presence of certain kinds of plants and animals) are said to determine whether a tribe will graduate from the hunter - gatherer stage. In my 'back of the envelope' model, I've used just two of Diamond's factors as a proxy for all of them: the number of species of large - seeded grasses and of animal species suitable for domestication. The next step in the book's model is to observe that if a society does not become large and dense, it will never develop the germs (and resistance to those germs) that kill off less dense, less resistant populations that the society comes into contact with. In my simplified model, complications such as density and proximity to disease - carrying animals are ignored; once a tribe hits 100,000 people, it starts producing germs (and resistance) in proportion to its growth. An index of bacteria density represents this in my application - even though, in the real world, much of the harm to indigenous peoples was done via viral disease (rather than bacterial) and via single diseases such as smallpox rather than a high overall count. Finally, successful tribes must develop technology and commerce. For this to happen, there must be a leisure class - people who don't have to spend all their time hunting, gathering, or farming for food. (This implies that the number of calories expended in getting and preparing the food is less than the calories obtained in eating the food.) In my simplified model, once a tribe hits 1,000,000 people, and provided it has a growth rate of greater than 50% every twenty years, we assume it is creating technology and commerce (in proportion to its rate of growth). Note: Not all the code for the Guns, Germs, and Steel model can fit in the text of this chapter. You can download the complete code for the project from the Sybex website.

The User Interface
The primary user interface for the Guns, Germs, and Steel application is based on a single MDI form, shown in Figure 8.1. Each culture created by the user is shown in a child window in the client area of the form. (MDI applications are explained in Chapter 4, 'Building a Better Windows Interface.')

Figure 8.1: Statistics on cultures created by the user are displayed within the client area of the primary MDI form.

The mechanism for simulating advancing time is provided by a Timer component (which was explained in Chapter 4.) The panel along the bottom of the form shown in Figure 8.1 allows the user to 'slow time down' or 'speed time up' using a TrackBar control. To achieve the effect I wanted, I set the TrackBar Minimum property to 1, its Maximum to 100, and its Value to 90. I then added code to the TrackBar ValueChanged event to change the value of the Timer Interval property:
private void trackBar1_ValueChanged(object sender, System.EventArgs e){ timer1.Interval = Math.Abs(trackBar1.Value - 101) * 100; }

Note: The little formula in the TrackBar ValueChanged event produces a range of values between 1,000 and 10,000 (one to ten seconds) for the Timer Interval property. The buttons beneath the TrackBar allow the user to
• Start and stop the global timer • Reset the application by closing all child forms and de - referencing the items in the collection they are

based upon
• Cause a clash between two cultures • Add a tribe

Tribes are the first stage of cultures in this model - they may, with luck and perseverance, grow up to be city - states or civilizations. Figure 8.2 shows the interface used to initialize a tribe by assigning it a name, an initial population, and colors for screen display, and indicating the resources available to it.

Figure 8.2: Tribes are started with an initial population and access to the specified plant and animal resources.

The Application Architecture
Successful tribes grow to become city - states, and city - states that are lucky and play their cards right become civilizations (think classical Rome). This progression is modeled in the application with a base Tribe class. The CityState class inherits from the Tribe class, and the Civilization class inherits from the CityState class because each inherited class adds to the functionality of its base. This relationship is shown in the Class View window in Figure 8.3.

Figure 8.3: The Class View window shows that Civilization inherits from CityState, which inherits from Tribe, adding new (or overridden) members as it inherits.

An encapsulating class, GroupOfPeople, serves as a kind of traffic director among the three classes in the Tribe inheritance chain. The GroupOfPeople class:
• Instantiates objects of the classes it encapsulates • Directs to the appropriate class in the inheritance chain based on the current characteristics of a people • Fires events as appropriate, such as when a tribe has become extinct, or when a civilization reaches its

Malthusian limits (and must reach for the stars)
• Implements 'clashes' between different peoples through the use of overloaded comparison • Manages the display of a peoples' current characteristics

By the way, GroupOfPeople, as an encapsulating class, is an example of the "Facade" design pattern - a design pattern being an abstraction that identifies the key aspects of a common design structure that is useful for creating a reusable object - oriented design. According to Gamma, Helm, Johnson, and Vlissides, sometimes affectionately referred to as the "Gang of Four" and authors of the well - known Design Patterns: Elements of Reusable Object - Oriented Software (Addison - Wesley, 1994): The intent of the Facade pattern is to provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher - level interface that makes the subsystem easier to use.

In other words, the programmer - or the MDI parent form, which contains the bulk of the userdriven program logic - does not have to worry about Tribe, CityState, or Civilization objects, or managing the display based on these objects. GroupOfPeople objects - which are items of a collection named PeoplesCollection - handle it all. This general architecture is shown in Figure 8.4.

Figure 8.4: A collection is formed of GroupOfPeople objects, which, in turn, manage subsystem functionality.

Within the primary application form (the MDI form), the user can start and stop the Timer, and speed up or slow down how fast time appears to go by. In addition, code that uses the members of instantiated GroupOfPeople objects accomplishes the following tasks:
• Add a new GroupOfPeople (which starts as a tribe). • Respond to the Timer's Elapsed event (which signifies that a generation, defined as twenty years,

has gone by).
• Respond to the events fired by objects in the PeoplesCollection collection of GroupOfPeople. • Determine the results of a clash between two GroupOfPeople objects - using the overloaded

comparison operators provided by the object - and publish the results. This application was built using several OOP design features, but, of course, not all OOP facilities found their way into it. It's self - evident that trade - offs are involved in constructing a chapter like this one

around a large single example; on the whole, I think the pluses outweigh the minuses. I found that the bulk of the work was creating a single instance that worked the way I wanted it to. After that, thanks to C#'s object - oriented context, I was able to quickly generalize my single instance into as many objects as I'd like, and easily engineer interactions between different instances. Readers should be able to interpolate these techniques and use them in their own applications.

Creating a Class
The class is the basic unit, or blueprint, for object construction in C# .NET. All objects in C# - and everything is an object, including variables, types, forms, and so on - are based on classes and their members. The members of an object correspond to the members of the class upon which it is based. Inheritance is an important mechanism for extending the usefulness of a class. A derived class is one that has inherited from a base class. Creating and invoking a simple class with a field, a property, a method, and an event as members was explained in the 'Creating a Class Library' section of Chapter 5, 'Reflecting on Classes.' You'll find the material in Chapter 5 a good introduction to the information presented here. A class is created by first declaring it - using the class keyword - and then defining its members.

Declaring a Class
The basic form of a class declaration is to provide an access level (as described later in this section), followed by the keyword class, followed by an identifier (the class name). By convention, class names start with an initial capital. Members of the class follow the declaration, between curly braces. For example:
public class Tribe { // Class body goes here }

Note: You can also include attributes in the class declaration. An attribute is metadata associated with an element of a program. For more information about attributes, search for the topic 'Attributes Tutorial' in online help. To create a class that inherits from another base class, at the end of the class declaration place the colon operator (:), followed by the base class name. For example, the CityState class inherits from

the Tribe class, so it is declared as follows:
public class CityState : Tribe { // Class body goes here }

Tip: The Visual Basic .NET equivalent of the : operator is the Inherits keyword.

Class Modifiers
The public keyword in the class declaration is an access modifier. Access modifiers are used to control access to classes and class members (see "Member Access" later in this chapter). The access modifiers allowed for a class are listed in Table 8.1, and general modifiers are shown in Table 8.2. Table 8.1: Class Access Modifiers Modifier internal public Description Only accessible to classes within the same assembly (this is the default access level). Accessible to classes outside the assembly. Most classes are declared with public access.

Note: Nested classes - classes within classes - are actually class members, and can be marked with the full set of class member (rather than class) access keywords, including protected and private. Those modifiers are discussed later in this chapter. Table 8.2: Class General Modifiers Modifier abstract new sealed Description Contains no implementation and is unusable as is; derived classes implement the members of an abstract class. Explicitly hides a member inherited from a base class. Infrequently used in a class declaration, since it must be used with a nested class. Cannot be inherited.

Classes can be marked internal, in which case they are only visible within an assembly, or public meaning they can be accessed externally to the assembly that they are in. If you leave the access modifier off, the class will default to internal. However, in practice most classes are declared public.

Classes can also be marked using the abstract modifier, which means that the class can only be used as the base class for other classes (put another way, an abstract class must be inherited from), or sealed, meaning that the class cannot be used as a base class (put another way, a sealed class cannot be inherited from). It is perfectly reasonable to combine an access modifier with general modifiers in a class declaration. For example, the Dinosaur class declaration used in an example later in this chapter is
public abstract class Dinosaur

Using the Class Template
If you open the Add New Item dialog (shown in Figure 8.5), select the Class template, give the class a name, and click Open, Visual Studio will create for you the default class module shown in Listing 8.1.

Figure 8.5: Adding a class module with the Add New Item dialog

Listing 8.1: Default Class Module
using System; namespace SybexC21 { /// <summary> /// Summary description for myClass. /// </summary>

for example. you'll find you get much more 'bang for your buck' from the Class Wizard than from the Class Template described in the previous section. but the overall answer is. . The default class code gives you some comments. Class modules are good places to start class libraries. it makes it very easy to find classes in your source code. not much. right .click a namespace and select Add > Add Class from the context menu. The first panel of the wizard. a business object class along with the collection class that groups them together.6. Significantly. will open. shown in Figure 8. does the default class produced by this class template buy you? I'll answer this question in a moment. and inclusion in the namespace of the current assembly.such as form modules. it does provide the framework for a class constructor (see "Constructors" later in this chapter). you must have the Class View window open. obviously it is not much work to create your own declaration from scratch in an empty Code File module (also available via the Add New Item dialog). The C# Class Wizard The C# Class Wizard is a visual interface that gives you a jump start in creating a class definition. a using System directive. If you'd like aid from the IDE in creating a class. To start the Class Wizard.public class MyClass { public MyClass() { // // TODO: Add constructor logic here // } } } What. If you put each class in its own module file (. It is also common and effective organization to group related classes in one module . It does not provide a base class for the newly created class.cs file) and name each file with the class name. exactly. But it is also perfectly reasonable to add short classes to existing modules . it can aid the clarity of the overall program architecture to put each substantive class in a module by itself. And. In addition. In Class View. as a matter of style.

such as its name and accessibility level. Class Members C# classes can contain the following ten kinds of members (in alphabetic order): • Classes (in other words.3 shows the access modifiers that can be used in a class member declaration. also opened via a link. the Inheritance panel. lets you select a namespace and the base class from which the new class will be derived. 'Everything Is String Manipulation'). The Base Class panel. classes can be nested within classes) • Constructors • Delegates • Destructors • Enumerations • Events • Fields • Indexers • Methods • Properties Member Access Table 8. Finally. opened by clicking the link in the left pane. The access .6: The C# Class Wizard provides a flexible and thorough mechanism for the creation of a class module. The first panel lets you set general class elements. lets you select multiple interfaces that the new class inherits (interfaces are discussed in Chapter 9.Figure 8.

MessageBox. } . as a matter of good programming style. At the risk of getting a little bit ahead of ourselves. Suppose you have a class named Surprise. it defaults to private access. } } You might then attempt to instantiate an object based on Surprise and access hiddenValley. where it is 'visible' and can be used. the protected string hiddenValley: namespace SybexC22 { public class Surprise { public Surprise() { } protected string hiddenValley = "How green was it?". System. However.Show (surprise. declare them using the private access other words. with a single member. Table 8.hiddenValley).3: Class Member Access Modifiers Modifier internal private protected internal public Description Accessible to class methods within the same assembly Only available to members of the same class (this is the default access level) Accessible to members of the same class and of classes derived fromthe current class protected Accessible to both assembly class methods and derived class members Accessible to the methods of any class If you leave the access modifier off a class member. perhaps in a click event like so: private void btnGet_Click(object sender. let's make the concept of protected access a little more concrete. you should explicitly mark the access level of your class members: if they are local in scope to the class.modifier controls the scope of the member .EventArgs e) { Surprise surprise = new Surprise().

MessageBoxButtons. "Got it".2. } Running this code shows that the value of the protected field can be displayed via a method of a derived class (Figure 8. in the example named Get . MessageBoxIcon.EventArgs e) { UnWrap unWrap = new UnWrap(). .OK.Surprise. and hiddenValley returned via the GetString method: private void btnGet_Click(object sender. MessageBox.Exclamation).7).GetSurprise(). } } } UnWrap has access to the protected members of its base class (Surprise). you can use Surprise as the base class for a new class. due to the inaccessibility of the hiddenValley member: However. Using this access.If you attempt to run this. you will get a compile . The two classes and the click event are shown in Listing 8. UnWrap: namespace SybexC22 { public class UnWrap : Surprise { public UnWrap() { } public string GetSurprise(){ return hiddenValley.Show (unWrap. System. UnWrap can make hiddenValley available in a public method. UnWrap can then be instantiated.time syntax error.

} } } // in class Form1 private void btnGet_Click(object sender. MessageBoxIcon. Listing 8. MessageBox.OK.Show (unWrap. } . "Got it". MessageBoxButtons. System.7: A protected member can be accessed via public methods of a derived class.EventArgs e) { UnWrap unWrap = new UnWrap().Figure 8.2: A Derived Class to Access a Protected Member of Its Base Class namespace SybexC22 { public class Surprise { public Surprise() { } protected string hiddenValley = "How green was it?". } } namespace SybexC22 { public class UnWrap : Surprise { public UnWrap() { } public string GetSurprise(){ return hiddenValley.Exclamation).GetSurprise().

The field is modifiable by something external to the current program (such as the operating system. select a class in Class View. To open a member wizard. A submenu will the class. class member declarations can be modified with other keywords. provide an interface that helps you to add fields.' The marked method is implemented outside the C# code. For more information. or a program operating on another thread). Static Members' later in this chapter.4: Class Member Modifiers Modifier const extern override readonly static unsafe virtual Meaning The field or variable cannot be modified.4. The member is operating in an unsafe context (as is required for explicit pointer operations). which are shown in Table 8. then select Add. . The member belongs to a type itself rather than an instance of the type. For more information. See 'Polymorphism and Overriding' later in this chapter. look up 'extern' in online help. as shown in Figure 8. and indexers to a class. The field can be assigned a value only in its declaration or in its class constructor. methods. I showed you how to use the C# Class Wizard to add a class definition. also accessible from the Class View window.Member Modifiers In addition to the access modifiers. Member wizards. 'Zen and Now: The C# Language. Right . volatile Using the Member Wizards A little earlier in this chapter. See 'Polymorphism and Overriding' later in this chapter. See the section 'Constants' in Chapter 6. look up 'unsafe' in online help. See'Instance vs. hardware. properties. The member provides a new implementation of a virtual member inherited from a base class. Table 8. For more information. probably in an imported library. look up 'volatile' in online help. The implementation of the member (or property accessor) can be changed by an overriding member in a derived class.

and indexers to a class. From the submenu. the following code is added to the class: public bool GetAddresses(int lbound. select the kind of member you would like to add.9 shows the Add Method Wizard being used to create the declaration for a method. properties.Figure 8. } I show the generated code here essentially to make the point that running the member wizards can be . Figure 8. Figure 8. string flag) { return true. fields.9 is created. int ubound.9: The Add Method Wizard adds a method to a class.8: Member wizards help you add methods. When the method shown in the wizard interface in Figure 8.

but will not really save you much work as opposed to creating members by hand. Tip: The Visual Basic equivalent of the this keyword is the me keyword. are static. (I almost said "current instance of an object based on a class.helpful. To make a member static. In contrast. 'Arrays. and Collections') It's also good practice to use this.since all objects are based on classes. You access a static member by simply qualifying it with the name of the class it belongs to. static members cannot be accessed through an instanceso you cannot use the this keyword to invoke a static method. see 'Whose Form Is It?' later in this chapter) • With indexers (for an example.member syntax when referring to object instance membersbecause the syntax makes it clear exactly what you are referring to even when the member is not hidden by a local variable or parameter. this references each instance of a class from within the instance (see the next section of this chapter for an explanation of static versus instance members of a class). particularly when you are getting started with C#." but in C# the "based on a class" clause is redundant . Fields. members of a class are instance members. The this Keyword The keyword this means the current instance of an object.Format. and Properties' later in this chapter) • To pass as a parameter a reference to the current object (for some examples. Indexers. Static Members Class members are either instance or static. it must be marked with the static keyword as a modifier following the access modifier. In C#. By default. Instance vs. see 'Class Variables. There are three ways you might use this: • To refer to instance members that would otherwise be hidden by parameters of a method or property (for an example. see 'Indexers' in Chapter 7. For example. such as String.) In other words. static members of a class are associated with the class itself rather than an instance of a type based on the class. many members of the String class. meaning you do not have to create an instance of . Instance members require that a reference to an object based on the class be created in memory before the member can be used. This process is called instantiation.

gop. within the Button's click event method. the instantiation takes place after the user clicks the Add a Tribe button on the program's main form.NET equivalent to static is the Shared keyword.CreateNewTribe (dlg). is declared at the form class level..Format method.. gop = new GroupOfPeople(). } .EventArgs e) { frmSettings dlg = new frmSettings(). . .String to use the System. System. gop. As you'll recall. Here's the excerpted code: . the variable that holds the instance of GroupOfPeople.or combination of traffic cop and gateway . .. This is done so that the variable holding the instance will be available to other members of the form class. However.String. the GroupOfPeople class is used as the "facade" .for managing the growth and display of cultures added by the user... not within the Button's click method.. private void btnAddaTribe_Click(object sender.. GroupOfPeople gop. In the application. } Now let's take a look at instantiating a GroupOfPeople object and invoking the CreateNewTribe method. with the new keyword. and Steel project. Germs.. Here's the declaration for CreateNewTribe: public void CreateNewTribe(frmSettings dlg) { .. as you probably are aware.) Tip: The VB ..System. (The String class is discussed in detail in Chapter 9. Instance Member Example Let's take an example of using an instance member from the Guns. This is done. One of the instance members of GroupOfPeople is the method CreateNewTribe.

GroupOfPeople (the class) and gop (the instance variable). have declared and instantiated it in one statement: GroupOfPeople gop = new GroupOfPeople(). Static Members Example In C#.3: A Class Used for Global Variables and Constants namespace SybexC21 { public class Globals { // Global Constants and Variables public static long globalTime = 0000. and Steel application. all code is within classes that are declared as explained earlier in this chapter. . This leads to the question of where to put declarations for variables and constants that you will need in multiple class instances. Note: It is conventional to name classes starting with an uppercase letter and to name the variables that hold instances of the class starting with a lowercase letter . rather than the two actually used: GroupOfPeople gop.for example. they cannot be . they do not need to be . Listing 8. mark constants you will need globally as public. Germs. . Note: Constants are considered static members. gop = new GroupOfPeople(). Therefore..marked with the static keyword.. of course. A good answer is to mark global variables public as well as static.Had I not needed to keep the declaration of the GroupOfPeople variable at the form class level . Listing 8. There is no such thing as a global module of code.for that matter. public const int totalAnimals = 5.I that it would be accessible to all members of the form class .3 shows the class used for that purpose in the Guns. and place them all in one class devoted just for that purpose.

} } Here's how I used the globalTime variable and the generation constant with the main form class of the program to increment the time by a generation: Globals. static members cannot directly access instance members of a class . every C# application requires a Main method as an entry point . Earlier in this chapter I reproduced the default code generated by the Class template (back in Listing 8. } Unlike in C++.globalTime += Globals.generation.the place where the program starts execution. To define a class constructor in C#. Every C# class must have one or more constructors . The Main Method Is Static As you may have noticed. The Main method is always static.hence the need to instantiate Form1 using the new keyword: Constructors A constructor is a method that is used to initialize a class instance.or rely on the default constructor provided by the Common Language Runtime (CLR) environment.Run(new Form1()). the important parts looked like this: public class MyClass { public MyClass() . If the class were named MyClass.1). A constructor is invoked when a class is instantiated. Here's the Main method added by default to Form1 when you open a new C# Windows application: static void Main() { Application.public const int totalPlants = 7. public const int generation = 20. declare a method within the class whose name is the same as the class that it constructs.

(Having multiple methods with the same name and different signatures is called overloading.the compiler and the CLR take care of that for you. strings to the empty string. Most of the time. or don't add implementation code within the constructor declaration. string theText) { . Germs.but takes no other action.NET (by declaring a New subroutine within the VB class). All you have to do is assign appropriate values for members in the constructor. provided that the signature . This default constructor creates an object and initializes members innocuously . As a matter of fact. discussed later in this chapter.4 shows three constructors for the Guns.{ } } The method with the same name as the class. Listing 8.of each constructor method is different. Tip: Constructors are defined using a rather different syntax in VB . depending on circumstances. and Steel Tribe class.meaning method type and the types of its arguments . you can have more than one constructor for a given class. public MyClass() is the framework for a class constructor. Listing 8. and so on .numeric members to 0. within the MyClass class declaration. you'll want to define your own constructors so that instances of the class are initialized to appropriate values.4: Three Tribe Class Constructors public class Tribe { public Tribe() { } public Tribe(string theName. the CLR provides a default constructor for you. If you don't declare a class constructor.) The point of having multiple constructors for a class is that you can use whichever constructor is right. But don't worry! You don't have to actually take care of creating an object by allocating a reference in memory .

Color. 1064. Class Variables. } .Text = theText. Color theTrCol. System.down arrows display the signature of each possible constructor. this. The other two constructors are invoked if the instantiation matches their signature.TextColor = theTextCol.TribalColor = theTrCol.Color.) Like other instance class members. A variable declared .numGenerations = numGenerations. this.Drawing. as one types in the code that instantiates a class in the Code Editor. 7..Gold. long theStartYear. this. this. For example.meaning that they are declared at the class level. int numPlants. Fields. 5.Name = theName.Text = theText. long numGenerations) { this. int numAnimals. System.StartYear = theStartYear this. and Properties Class fields are simply public variables belonging to a class . string theText. fields can be declared with access modifiers. you could instantiate a Tribe object like this: Tribe tr = new Tribe("Gondor". (A variable declared inside a method is always private to the method and cannot be declared using the public keyword. 1020).SpeciesAnimals = numAnimals. this. this.4 is what is invoked if you instantiate with a statement like Tribe tr = new Tribe(). It's really neat that. this.Green. } The first empty (default) constructor shown in Listing 8.SpeciesPlants = numPlants. "Numenorians". to invoke the third constructor shown.. drop .also referred to as member variables . } public Tribe(string theName. Color theTextCol.this.Drawing.Name = theName.

As you've previously seen.also known as class fields. classes are implemented using the special methods known as get and set accessors. Note: By convention. properties can be accessed externally to a class in the same way as a class field. Properties are more elegant versions of class variables. the private variables that track property values are named with an m_ prefix. while the lines public bool isAlive = false. Here's a simple example from the GroupOfPeople class: private long m_Population = 0. } set { m_Population = value. public string Name. . m_Population. For example. all declare public instance variables . while a public member variable (a field) can be accessed outside the instance. declare private instance variables. However.using private can only be accessed within an instance and is intended to be used internally. The set accessor uses the value keyword to assign a value when the property is set. private Civilization civil. public long Population { get { return m_Population. the lines private Tribe tr. } } The get accessor of the property uses the return keyword to return a value when the property is accessed. internally. private CityState cs. is used to keep track of the property's value between use of the accessors. public frmTribe frmTribe. in the GroupOfPeople class. An internal class variable.

in whose instance am I.oriented programming mechanism. In this way. by encapsulating the implementation details.The property mechanism can be used to validate values.. In VB6.only (by omitting the get accessor). calculate changes to values. .NET should pay special attention to this section.g. e. and Steel application. you can invoke a form member as though the form were a static class without formally creating a class instance so inter .totalAnimals) m_SpeciesAnimals = value. Germs.only (by omitting the set accessor) or write . all code in C# is within classes. .issue. in the Tribe class in the Guns. the SpeciesAnimals property validates a value passed to it to make sure that it does not exceed the allowable value set in a global constant field before updating the property value by setting the private member variable: private int m_SpeciesAnimals = 0. anyway? These turn out to be not entirely rhetorical questions. public int SpeciesAnimals { get { return m_SpeciesAnimals. communicating between instances of different form class objects is something that must be done in any multiform Windows application. and access internal object state information. they serve as an effective object . } } Whose Form Is It? And. } set { if (value <= Globals. Since Windows forms are wholly encapsulated as individual classes..form communication is a non . Furthermore. Properties can be made read . For example. VB6 programmers who are new to .: Application. As we've seen in this chapter. Note: This issue was essentially papered over in VB6 (and earlier versions of VB). applications start running by invoking an object instance of a class. These facts lead to the natural question of how to communicate references and values between class instances.Run(new Form1()).

we don't have to fully qualify the Form type.since these variables can be accessed without instantiation from any class.communication. Creating a class module for static public variables and constants was explained earlier in this chapter in the 'Static Members Example. It also violates the encapsulation features of good OOP design. Using a Property to Pass a Form Instance Properties are ideal for holding a reference to a class instance in a second form class. To see how this works. Before we get started.Windows.) In the project named Form1 and Form2. public Form OtherForm { get { return m_OtherForm. but since the System. Next. use the Properties window to change the value of the Text property of Form1 to something like "The first form is very happy!". you now have two forms . Form1 (and Form2) inherit from the general Form class. add a property named OtherForm of type Form to the Form2 class: private Form m_OtherForm = null.and classes . } set { m_OtherForm = value. If you didn't change their default names. there are better options for managing Windows forms applications intra .Form.One way of dealing with this is to use public static variables to pass references and values back and forth . (Extensive use of static public variables in this way is unnecessarily consumptive of resources.Forms namespace is included by default in the Form2 class in a using directive. use the Add New Item dialog to add a new form to a Windows application project. } } Note: The OtherForm property is actually of type System.' However. this is probably not the best way to communicate between class instances.Forms. We'll be accessing this value from the Form2 instance in our demonstration. so . Let's look at a couple of possibilities.Windows. for several reasons.

Use the this keyword to store a reference to the current instance of Form1 in Form2's OtherForm property: form2. by changing the Text in the caption bar of the Form2 instance to include the Text value in the Form1 instance: private void btnWho_Click(object sender.10: A reference to the instance of the Form1 class can be stored in a Form2 class instance property. The complete code in this example is shown in Listing 8.Text = "Form1 was called and said.OtherForm = this. Figure 8.10). you'll see that methods belonging to the second form instance can. as the base type. Finally.EventArgs e) { this. display the second form.Show(). use the Show method form2 instance of the Form2 class to display the second form: form2. and click the button on it. Next. } If you run this code. access the instance members of the first form (Figure 8. System. Back in form2. \" " + OtherForm. indeed.5. add code to a Button click event that declares and instantiates an instance of the Form2 class named form2: Form2 form2 = new Form2().Form. back in Form1. .or to any class derived from Form. will do to store a reference to either derived class . we can add a click procedure that demonstrates that we can access the instance members of the object based on Form1.Text + "\"".

. \" " + OtherForm..EventArgs e) { this. } set { m_OtherForm = value.. The TextBox will be used for text that will be retrieved in a TextBox on Form1 when the user presses OK. private Form m_OtherForm = null. System. } . Here's how this might work. } .Show(). Using a Reference to a Form Instance A different approach is to use a reference to another form class instance in the current instance.OtherForm = this.EventArgs e) { Form2 form2 = new Form2(). // Form2 .Listing 8.Text + "\"". it will be called Form3). Go back to the example shown in the previous section.5: Using a Property to Pass a Reference to a Form Instance // Form1 .. In Form3.. add OK and Cancel buttons and a TextBox.. form2. and use the Add New Item dialog to add a new form class to the project (by default..Text = "Form1 was called and said. Add a declaration for a public field to hold the string at the class level: . } } private void btnWho_Click(object sender. public Form OtherForm { get { return m_OtherForm. private void btnShow_Click(object sender.. form2. System.

} In Form1.OK. System.public string theText. } form3. a user cannot switch to another form in the application and that the code below the ShowDialog statement doesn't get executed until the form is closed. and that when the first form is minimized. System.theText. close the instance: private void btnOK_Click(object sender.Dispose().EventArgs e) { theText = this. Finally. this. Warning: It's a good idea to include a call to the Dispose method of the modal form instance.ShowDialog(this) == DialogResult.txtSendBack.txtGiveHere. it means that the second form always appears "on top" of the first form. .Text = form3.OK){ this. so is the second form. meaning that when the form is open. (With a regular Show method. Here's the click event code that declares and instantiates an instance of Form3 and retrieves the text when the user clicks OK: private void btnShow3_Click(object sender. the code below the statement runs immediately. set the object instance's DialogResult property to the enumeration value DialogResult. When one form is an owner of a second form. } The ShowDialog Method The ShowDialog method of a form instance displays the form modally. In the OK Button's click event. and wire the logic. assign the TextBox's Text property to the field. Next.Close(). add a Button to display the instance.EventArgs e) { Form3 form3 = new Form3(). the current instance becomes the owner of the Form3 instance.DialogResult = DialogResult.) By including the current form instance as a parameter (with the this keyword) in the ShowDialog method call. this.OK. if (form3.Text. add a TextBox to receive the text from the Form3 instance.

} . to make sure that it gets run.11: If you enter text in the TextBox in the instance of Form3 (left) and click OK. if (form3.6: Using a Reference to a Form Instance // Form1 . // Form3 .. Figure 8. Run the project and click Show Form3. private void btnShow3_Click(object sender. the text will appear in the TextBox of the original form (right). System. as explained in Chapter 6.OK){ this. enter some text in the TextBox in Form3 and click OK (Figure 8.6 shows the code for this example.txtGiveHere.Text = form3.theText. Listing 8. it probably makes sense to include the Dispose method in a Finally block. In production code. it is actually hidden. } you need to call its Dispose method to mark it for collection.ShowDialog(this) == DialogResult. When a modal form is closed via the button with the X at the top . The text that you entered in the TextBox in Form3 is now displayed in the original form.11)..Dispose()...right of the form window.NET Framework where you need to be somewhat concerned about explicit de . not disposed of (marked as garbage) . Next.This is the rare case in the .allocation of resources. Listing 8.EventArgs e) { Form3 form3 = new Form3().

OK){ .Close().Text.EventArgs e) { theText = this. System. } Back in the Form1 code. . Within the modal dialog. a modal dialog is opened from a click event in the application's primary pass form instances to instance methods. this. Germs. m_Text = txtText. as explained earlier in this chapter: private void btnOK_Click(object sender..EventArgs e) { frmSettings dlg = new frmSettings().. if (dlg...DialogResult = DialogResult. I'll show you two examples from the Guns. In the first example. using the technique just explained: private void btnAddaTribe_Click(object sender..Close().Close().and very useful . .EventArgs e) { m_Name = txtName. and Steel application.EventArgs e) { this.ShowDialog(this) == DialogResult.. this.CreateNewTribe (dlg).OK. System. private void btnOK_Click(object sender. in the OK button's click event before the DialogResult property is set..DialogResult = DialogResult.. In this section.Text. System.OK. } .Text. the user's choices are stored in member fields and properties. the instance of frmSettings is passed to the CreateNewTribe instance method of the GroupOfPeople class: gop. System. Passing a Form Instance to a Method It's also easy . this.txtSendBack. public string theText. } private void btnCancel_Click(object sender.. this.

. this. And then I could go merrily about populating the form: frmTribe.CreateForm (this).Windows.BackColor = tr. this.Text = tr.tribeText. . the MDI child form: frmTribe = new frmTribe().) For this to work. it had to be done from an instance method within that class).Form parentForm) Within the method.tribeName. the instance of frmSettings is used to populate member values: public void CreateNewTribe(frmSettings dlg) { tr = new Tribe(). . I created a new instance of frmTribe. So I passed the current instance to the instance method: gop.. I needed to be able to assign the reference to the instance of the MDI parent form (Form1) to the MdiParent property of each child form. The CreateForm method was declared with a Form as its argument in a parameter named parentForm: public void CreateForm (System.Within the CreateNewTribe method. (MDI applications are explained in Chapter 4.Text = dlg.Forms. But this couldn't be done from Form1 itself because of the facade functionality of the GroupOfPeople class (in fact. } The second example centers around the fact that I used MDI child forms to display the changing values of population and so forth for each culture.TribalColor.Name = tr.MdiParent = parentForm.Name = dlg. The newly instantiated frmTribe's MdiParent property was assigned to the instance value of the main form passed into the method: frmTribe.

.. gop.ShowDialog(this) == DialogResult.. } . this. .tribeText..Close(). private void btnOK_Click(object sender. .Name = dlg.. this.CreateForm (this). .OK.TextColor. public void CreateNewTribe(frmSettings dlg) { tr = new Tribe(). System..CreateNewTribe (dlg).OK){ gop = new GroupOfPeople().EventArgs e) { frmSettings dlg = new frmSettings(). this. System.DialogResult = DialogResult.Text. . m_Text = txtText.tribeName.... if (dlg..EventArgs e) { m_Name = txtName. this.ForeColor = tr.Dispose().Name = tr.Text = dlg.. // GroupOfPeople .7 shows these two examples of passing form instances to methods. } ... Listing 8.. Listing 8.. } . // frmSettings . } dlg.Text...7: Passing Form Instances to Methods // Form1 . gop..frmTribe.Text = tr. private void btnAddaTribe_Click(object sender. GroupOfPeople gop.

a method declaration specifies the return type of the method. For example..TribalColor.TextColor. it is said to return void. Methods A method is.public void CreateForm (System.. (Of course. By default.MdiParent = parentForm. out. frmTribe. If the method does not return a value. ref. In fact. the method refers to the same variable that was passed into the method. you can use an object reference passed to a method within the method to change the members of the referenced object.ForeColor = tr.) .7 public void CreateNewTribe(frmSettings dlg) makes it clear that no return value is expected from the method. . followed by the method's identifier. method parameters do not behave this way.Form parentForm){ frmTribe = new frmTribe().4. Any changes made to the parameter within the method will be reflected in that variable when control returns to the calling method. it doesn't need to be marked with out or ref as a value type would.BackColor = tr.. As you've already seen numerous times in this book. frmTribe. in Table 8. } . Methods are usually declared using one of the member access modifiers described earlier.Windows. the class member that used to be called a function (if it returned a value) or a procedure (if it did not). the declaration of the CreateNewTribe method shown in Listing 8.. Changes made inside a method to a parameter declared without these keywords are not made to the variable when control is passed back to the calling method. of course. a method is sometimes referred to as a member function.3.Forms. frmTribe. Using the void keyword rather than a return type specifies this. followed by the method's typed parameter list within parentheses (the parentheses are required whether or not there are any parameters). in today's world. Since the object reference is already a reference. and can also be marked with one of the modifiers described in Table 8. and Arrays as Parameters If a method parameter is marked with the out or ref keyword.

i <theArray.Text += theArray[i]. that is sufficient for the method to be considered overloaded.12: for (int i=0. the array need not be initialized when the method is called: string [] theArray. and the method that calls it. } Figure 8. Note: If one version of a method has an out or ref parameter and another does not. FillArray (out theArray). .12: Arrays passed to a method using out or ref retain value changes to the array elements in the calling method. The contents of the array can then be displayed in a TextBox. the following method fills a string array marked with out: private void FillArray (out string [] theArray){ theArray = new string[3] {"Madam ". When the array parameter is marked with out or ref. } Since out rather than ref was used. Listing 8.Length.The difference between out and ref is that an argument passed to a ref parameter must be definitely assigned. as shown in Figure 8. Arrays can be used as method parameters. while that is not the case for out parameters. " Adam"}.8 shows the method with the array parameter marked with out. i++){ txtGiveHere. this is a useful way to encapsulate actions taken on arrays. like any other type. For example. and passed as arguments to methods. "I'm".

you can have more than one declaration for a given method name. i++){ txtGiveHere.and leads to a' version.Length. the more parameters that an overloaded method specifies. In the more complex version. To take a rather trivial example. in the 'Constructors' section. the calling code need only pass the text of the message box as an argument. System. More complex overloaded versions of the method take more arguments and give the method invoker more control over what the method does. 'fast . string capText.8: Passing an Array to a Method Using an out Parameter private void FillArray (out string [] theArray){ theArray = new string[3] {"Madam ". let's create two overloaded versions of a method that builds a message box. and the types of the parameters of each same . " Adam"}. (The same idea was discussed earlier in this chapter relating to class constructors.Text += theArray[i].Listing 8. i < theArray. It's often the case that the most basic version of a method doesn't require much in the way of arguments .Show (inText. prefabricated version of the action that the method takes. MessageBoxButtons. } } Overloading Methods Within a class. In other words. for (int i = 0.named method.OK. are different. // Initialization not needed FillArray (out theArray).EventArgs e) { string [] theArray. } private void btnArray_Click(object sender. "I'm". and icon: public void DisplayBox (string inText){ MessageBox.) The point of having multiple overloads for a method is that the programmer using the method (who may be the same programmer who wrote the overloads) can use whichever version is right for a particular circumstance. MessageBoxIcon. buttons. the more detail a developer using the overloaded method can specify. } public void DisplayBox (string inText.Exclamation). provided that the method return type. . the calling code gets to specify not only the text but also the title. "Hello!". In the prefabricated.

MessageBoxIcon."). MessageBoxButtons. overload.YesNoCancel.9 shows the overloaded versions of the method and how to invoke them from a click event. the simplest version of an overloaded method serves as a kind of prefab version (left).DisplayBox("Prefab dialog box. overload. mbb. MessageBoxIcon mbi){ MessageBox. Listing 8... Figure 8.DisplayBox("Not the prefab version. capText.MessageBoxButtons mbb. ."). Listing 8.Question).YesNoCancel.Show (inText..". as indicated by the "1 of 2" markings in the Code Editor and the arrows that allow you to toggle between the overloaded versions of the method: Invoking the two overloaded versions of the method overload. System. mbi). both overloaded signatures are provided by the Code Editor's auto .". while a more complex version of the overloaded method allows the programmer to set more options (right).DisplayBox("Prefab dialog box. "A rose is a rose is a rose".13: In some cases.completion feature.13). shows both a prefab version of the message box and one that can be custom designed (Figure 8.. } When the programmer starts typing in the code to invoke the DisplayBox method from another class. overload.9: Invoking Overloading Methods // Invoking the two method overloads private void btnOverload_Click(object sender.DisplayBox("Not the prefab version. MessageBoxButtons. "A rose is a rose is a rose".EventArgs e) { Overload overload = new Overload().

} public void DisplayBox (string inText. Five Steps to Creating an Event Before we get to the examples. namespace SybexC22 { public class Overload { public Overload() { } public void DisplayBox (string inText){ MessageBox. I showed you how to add a delegate .Show (inText. MessageBoxButtons mbb.and an event to a class. } } } Delegates and Events In the 'Creating a Class Library' section of Chapter 5. here are the five steps involved in creating. and wiring an event: 1.Forms. MessageBoxIcon mbi){ MessageBox. mbb. "Hello!". } // The class containing the overloaded method using System. MessageBoxButtons. A delegate is a type whose name and parameters constitute a method signature.OK. capText. firing. The first step is to create a delegate that has the right signature in its parameters.Question). A delegate instance references a method that matches the signature in its name and parameters.MessageBoxIcon. mbi).Exclamation). .Show (inText.Windows.a kind of pointer to an event method . Let's go over a couple of other examples here as well as the general process for creating an event. MessageBoxIcon. string capText.

System. The fifth. 3.EventArgs e). or 'raised.2.can alternatively be accomplished using the Events interface in the Properties window. The event is actually fired.wiring the event to the event handler method . Too!'. These are the OnExtinction event. I'll show you how these two events are implemented. An event is declared in the firing class by applying the event keyword to a delegate that modifies an instance member method name that is invoked with the same parameter types as the delegate. Finally. the firing class instance must be told that the event handler method in the receiving class should be notified when the event is fired. Another class is 'wired' to receive the event by adding a method to the class that matches the parameter types contained in the delegate's parameters. step is to let the instance firing the event know that the instance event handler should be notified when the event is fired. Step One: Creating the Delegates In the GroupOfPeople class. the two delegates are declared: public delegate void ExtinctionEventHandler (object sender. Note: The final step . The third step is to write the code that invokes the event method. The OnExtinction and OnMaxPop Events Instances of the GroupOfPeople class of the Guns. as I explained in Chapter 3. fired when the population of an instance approaches the maximum value that can be stored in the C# long value type. Germs. and the OnMaxPop event. and final. 5.' in a class instance by calling the method. The class instance member that raises the event is assigned (using +=) a new instance of the delegate with the instance event handler as an argument. This is accomplished using the += operator. fired when a GroupOfPeople instance approaches zero population.a method that has parameters that match in type the delegate's. and Steel application fire two events. The fourth step is to write a method in the class that will receive the event . 'Windows Uses Web Services. This will be the event handler. The second step is to use the event keyword to associate the delegate with an event method. . using the five steps outlined in the previous section. 4.

OnExtinction (this. the events are fired by calling OnExtinction and OnMaxPop. public event MaxPopEventHandler OnMaxPop. System.EventArgs e). } OnMaxPop is called. this. since their signature is the same in fact. . in the interest of clarity. with the same arguments as OnExtinction.EventArgs e = new System.MaxValue / 2. The this keyword is used to call OnExtinction with the current instance of the class.. Note: that the parameters of an event method should always include an object representing the instance firing the event (object sender). e). Note: There's no real need to declare both delegates in this example... Step Two: Associating the Delegates with Events The delegates are associated with event methods (OnExtinction and OnMaxPop): public event ExtinctionEventHandler OnExtinction.EventArgs(). .. when the population becomes bigger than the maximum value that can be stored in the long type divided by two. const long veryBig = long. it is good practice to declare a delegate for each custom event. public delegate void MaxPopEventHandler (object sender. along with the new instance of the type System. OnExtinction is called when the population (represented by the internal variable m_population) gets below 100. when appropriate conditions are met. However. Step Three: Firing the Events Still in GroupOfPeople. and a parameter of type System. there's no real need to declare delegates at all. because the signatures already match the built in EventHandler delegate.isAlive = false.EventArgs (you can include other parameters if you need to)..EventArgs stored in the variable e: if (m_Population >= 0 && m_Population < 100 ) { // fire the extinction event m_Population = 0. System.

OnExtinction += new GroupOfPeople.ExtinctionEventHandler (this. gop. Adding Code to the Event Handlers If you don't place any code within the receiving event handlers.GroupOfPeople_OnMaxPop). Step Five: Arranging for Event Notification The last step is to add notifications to the instances of the event methods so that the code placed in the receiving event handlers (whether they are named GroupOfPeople_OnExtinction or Fred) is processed when the instance event method is called: gop = new GroupOfPeople().EventArgs e) { } By convention. this.MaxPopEventHandler (this. then there's no real point in going to all this trouble.. e).EventArgs e = new System. gop.but. Step Four: Adding Event Handlers Event handlers with the same parameter types as the parameters of the delegate (and the event method) are added to the receiving class (Form1). System. and the event would still work. the event handlers are usually named with the firing class name. if (m_Population >= veryBig) { m_Population = long..g. OnMaxPop (this. } Note: The MaxValue field of a numeric type represents the largest value that can be stored in that type.MaxValue . in fact. System. System.GroupOfPeople_OnExtinction).OnMaxPop += new GroupOfPeople. private void GroupOfPeople_OnExtinction (object sender. followed by an underscore.EventArgs e){ } private void GroupOfPeople_OnMaxPop (object sender. such as Fred. . followed by the name of the event method (e..1.isAlive = false..EventArgs(). any valid identifier could be used. GroupOfPeople_OnExtinction) .

14): MessageBox.Information). and marks the form representing the culture (Figure 8. Using the GroupOfPeople instance thus obtained. the code in the OnMaxPop handler displays a message box.OK. } .15): private void GroupOfPeople_OnMaxPop (object sender. Germs. "Guns.14: When the OnExtinction event is fired.Text += " (Extinct)". MessageBoxIcon.Name + " tribe is extinct!".Show ("Sorry. MessageBoxButtons. Figure 8.EventArgs e) { GroupOfPeople aGoP = (GroupOfPeople) sender. aGoP.Text += " (Encounter with Malthus)". System.frmTribe.Information). MessageBox.OK. MessageBoxIcon. a message is displayed and the culture's screen display is marked. MessageBoxButtons.Show ("Time to populate the stars!". aGoP. a message box is displayed and the instance's screen representation is updated (Figure 8. "GO " + aGoP. Similarly.frmTribe.Name + " Civilization" . " + aGoP.The code in the OnExtinction handler casts the sender parameter to a GroupOfPeople: GroupOfPeople aGoP = (GroupOfPeople) sender. and Steel".

public event ExtinctionEventHandler OnExtinction. } .EventArgs e = new System.EventArgs e).EventArgs(). this. if (m_Population <= 100000){ if (m_Population >= 0 && m_Population < 100 ) { // fire the extinction event m_Population = 0. e). the instance culture is marked ("Encounter with Malthus") and a message is displayed. // fire the maximum population event if (m_Population >= veryBig) { m_Population = long. System. Listing 8..MaxValue .10 shows the implementation of the OnExtinction and OnMaxPop events with some of the implementation context. e).15: When the OnMaxPop event is fired for an instance.isAlive = false.MaxValue / 2. public event MaxPopEventHandler OnMaxPop. } } . System.. this.1.10: Implementing the OnExtinction and OnMaxPop Events // In GroupOfPeople (firing class) // Delegate and Event Declarations public delegate void ExtinctionEventHandler(object sender..EventArgs e = new System.isAlive = false. Listing 8.Figure 8. public delegate void MaxPopEventHandler(object sender. System. else { . } . OnMaxPop (this. System. OnExtinction (this..EventArgs e)..EventArgs().. // Fire the events public void ProgressTheTribe() { const long veryBig = long.

Information).frmTribe.ShowDialog(this) == DialogResult. aGoP. MessageBox. } .Name + " Civilization".Information).OK. . "GO " + aGoP. and Steel".. MessageBox.OK){ gop = new GroupOfPeople().EventArgs e) { GroupOfPeople aGoP = (GroupOfPeople) sender. A clash between cultures A and B has three possible outcomes: A assimilates B. B assimilates A.GroupOfPeople_OnExtinction).Text += " (Extinct)".Show ("Sorry. } Overloading Operators Operator overloading means creating custom implementations for standard operators in the context of the instances of a class. Germs.ExtinctionEventHandler (this. if (dlg.Name + " tribe is extinct!".// In Form1 (the "receiving" class) // Arranging for event notification private void btnAddaTribe_Click(object sender. MessageBoxButtons. } private void GroupOfPeople_OnMaxPop (object sender.Text += " (Encounter with Malthus)".. and Steel application.MaxPopEventHandler (this. . System. gop. } // The Event Handlers private void GroupOfPeople_OnExtinction (object sender. In the context of the Guns.GroupOfPeople_OnMaxPop).EventArgs e) { frmSettings dlg = new frmSettings().frmTribe. gop.16. "Guns. " + aGoP. System. MessageBoxButtons. MessageBoxIcon. MessageBoxIcon.OK.Show ("Time to populate the stars!". aGoP.OnMaxPop += new GroupOfPeople. Germs.EventArgs e) { GroupOfPeople aGoP = (GroupOfPeople) sender.OnExtinction += new GroupOfPeople. the user can initiate clashes between cultures (remember that cultures are instances of the GroupOfPeople class). System... The user interface for creating a clash between two cultures is shown in Figure 8. or the confrontation is a draw and things remain at the status quo.

but it seemed more elegant to overload the less than (<) and greater than (>) comparison operators within the GroupOfPeople class.) Since I also implemented the < operator as required. I realized that the conditions for determining the outcome of a given confrontation were fairly complex and involved three measures: population. a stand . e. B > A would be true. That way I could easily determine whether A should assimilate B. as a matter of taste the expressions could be rewritten to use it. bacteria. and technology. Conversely. The C# compiler requires that if you overload one comparison operator in a class.16: The user can initiate clashes between cultures. If = is would be reached when neither A > B nor B > A. In addition. you also overload the opposite one. (!(A > B) && !(B > A)) Note: The careful reader will observe that the expressions used in the last three sentences to determine the outcome of a clash use only the > operator. Since I did not implement an overload of the equality comparison operator.there has to be a big quantitative difference between two cultures to result in the assimilation of one by the other. if B should assimilate A.g.Figure 8.. < must also be. (If > is overloaded. . When I started to consider how best to implement the clash between cultures. relatively equivalent cultures produce no winner when they clash . you must also overload !=. I could have written a method that would have evaluated all these things based on two passed objects of type GroupOfPeople. because the expression A > B would be true.

Bacteria + 100000)){ return true.11 shows the complete static method within the GroupOfPeople class that overloads the operators. GroupOfPeople b){ if (!(a.11: Overloading the < and > Operators in the GroupOfPeople Class public static bool operator > (GroupOfPeople a. Listing 8.Population + 1000000){ return true. } else if (a.Population > b. } else if (!(a.Bacteria + 100000){ return true.Guns + 10000)){ return true.Here are the declarations that tell the compiler that the < and > operators are being overloaded for instances of the class: public static bool operator > (GroupOfPeople a.Population + 1000000)){ return true. } } public static bool operator < (GroupOfPeople a. } else if (a.Guns > b. GroupOfPeople b){ if (a. } else { . GroupOfPeople b){ } Listing 8.Bacteria > b.Guns > b.Population > b. GroupOfPeople b){ } public static bool operator < (GroupOfPeople a. } else { return false.Bacteria > b. } else if (!(a.Guns + 10000){ return true.

or a winner and a loser. a).SelectedItem. if (aGoP.Items. (I've excerpted the message boxes that are displayed under the various conditions.16. using the overloaded > operator. b).lstA.Name == frmClash.ShowDialog(this) == DialogResult.lstA. foreach (GroupOfPeople aGoP in peoples) { if (aGoP. } } if (!(a > b) && ! (b > a)) { // no winner or loser // display appropriate message } else if (a > b){ // a is the winner Assimilate (a.ToString()) a = aGoP. frmClash.Items. the private Assimilate method performs the actual assimilation. Finally. } else { // b > a // b is the winner Assimilate (b.) If there is a winner and a false.SelectedItem.12 populates the form used to originate culture clashes.Add(aGoP. } } The code in Listing 8.lstB. Next. . it determines whether there is a stand . GroupOfPeople b = gop.Name). it determines which cultures were selected for a clash based on the user choices from the form shown in Figure 8.ToString()) b = aGoP.isAlive){ frmClash. Listing 8.12: Using the Overloaded > Operator private void btnClash_Click(object sender.Add(aGoP. } } if (frmClash. frmClash frmClash = new frmClash().Name). System.Name == frmClash.EventArgs e) { GroupOfPeople a = gop.lstB.OK){ foreach (GroupOfPeople aGoP in peoples) { if (aGoP.

Population = 00.Text += " (Assimilated by " + winner.Bacteria = 200. winner.} } private void Assimilate (GroupOfPeople winner. The base .Guns = 0. loser.class method can be invoked at run time and supersedes the base . the class it is in must also be abstract.ProgressTheTribe().17: The Borg has assimilated the Bumblers. Warning: If you attempt to override a base . The override keyword means that a derived . } Figure 8.Population += loser.class member that has not been marked as virtual or abstract.time syntax error.Name + ")". Another way of putting this is that polymorphism is the ability of a program to perform dynamic operations by implementing methods of multiple derived classes through a common base class reference. Figure 8. loser.DrawForm().frmTribe. loser. polymorphism is primarily implemented when members . By the way.17 shows the results of one culture being assimilated by another.class method must be marked as either virtual or a derived class are marked with the override keyword.class method with the same name. loser. loser.Population. in order to mark a member as abstract. . GroupOfPeople loser){ winner. with each object potentially implementing the task in its own way. loser. you will get a compile .isAlive = false.commonly methods . In C#. Polymorphism and Overriding Polymorphism means the ability to make different objects perform the same task.

in Listing 8.13.and that each species of dinosaur implements its GetFood method differently depending on the kind of food it likes to eat. Polymorphism Demonstration Let's have a simple polymorphism dino .which happens to return a string . } } class Apatosaurus : Dinosaur { public override string GetFood (){ return "Apatosaurus eats plants. I mean demo . To start with.". } . I eat everything.13: Dinosaur GetFood Abstract Method and Overridden Methods in Derived Classes public abstract class Dinosaur { public abstract string GetFood(). they consist solely of the declaration). Jared Diamond. In contrast. let's also assume that all dinosaurs are pretty much alike.' Well.class members is that virtual methods are implemented and can optionally be overridden. and that its abstract GetFood method has no implementation. } class Lessemsaurus : Dinosaur { public override string GetFood (){ return "Lessemsaurus bites branches from treetops. abstract methods must be overridden and are not implemented (in the abstract base class. I am always hungry.The difference between virtual and abstract base . in Guns. } } class TRex : Dinosaur { public override string GetFood (){ return "I am Tyrannosaurus Rex. quotes the famous first sentence of Tolstoy's Anna Karenina: 'Happy families are all alike. Germs. that they all have something called the GetFood method .". and Steel. every unhappy family is unhappy in its own way. } } class Allosaurus : Dinosaur { public override string GetFood (){ return "My name is Allosaurus and I eat the context of dinosaurs. you'll see that the base Dinosaur class has been declared abstract.". Listing 8.".oops.

the Civilization class is derived from the CityState class. each returns a different string. jPark[0] = new Lessemsaurus().different polymorphic behavior of the classes derived from Dinosaur can be seen in the text each one adds to the ListBox Items collection.18.but . because of the override modifier marking the GetFood method in each of the derived classes. and Steel application. those versions of the method are invoked.EventArgs e) { Dinosaur [] jPark = new Dinosaur [4]. the method . foreach (Dinosaur dino in jPark){ lstDino.13 implement their overridden GetFood method in their own way . jPark[2] = new Allosaurus().Add(dino. jPark[3] = new TRex(). (After all. Overriding Virtual Methods You may recall that in the Guns. } } Figure 8. which in turns derives from Tribe. as shown in Figure 8. However.GetFood()).14 are of type Dinosaur. Listing 8.} Each of the four classes derived from Dinosaur in Listing dinosaurs is pretty simpleminded!) The objects in the jPark array shown in Listing 8. when the foreach statement is used to loop through the array. Tribe is the base .14: Invoking the GetFood Methods of the Classes Derived from Dinosaur private void btnPoly_Click(object sender. Germs.Items.18: The same . jPark[1] = new Apatosaurus(). (Put the other way round.that is. System.

and Civilization class.SetMeUp(cs). which is the base class for Civilization. } else m_Population = tr. frmTribe.GetNewPopulation().15: Portions of the ProgressTheTribe Method private Tribe tr. this.DrawForm()..class for CityState.15. // tr is instantiated in GetNewTribe public void ProgressTheTribe() { const long veryBig = long. this. if (m_Population <= 100000){ if (m_Population >= 0 && m_Population < 100 ) { // fire the extinction event .DrawForm().GetNewPopulation(). determines which kind of object is currently instantiated.) Instances of the GroupOfPeople class act as the facade for the Tribe.Text = cs.IncrementGenerations().. } else { // initialize city state if (civil == null) { civil = new Civilization().MaxValue / 2. . tr. } else if (m_Population <= 1000000) { // initialize city state if (cs == null){ cs = new CityState(). cs. frmTribe.Bacteria.SetMeUp(tr).Text + " Civilization". CityState. } m_Population = cs.Bacteria = cs. cs. civil. shown excerpted in Listing 8.Text + " City State".Text = civil. Specifically. this.IncrementGenerations(). Listing 8. the ProgressTheTribe method of GroupOfPeople.

a version of GetNewPopulation is called. this.15. this..GetNewPopulation(). } return m_Population.Bacteria = civil. } } If you look at Listing 8.} if (m_Population >= veryBig) { // fire max pop event .17). } The pace picks up in the CityState class (Listing that it can be overridden in its derived classes. civil. .IncrementGenerations(). if (m_Population >= 0 && m_Population < 10) { m_Population = 0. the GetNewPopulation method as originally declared in the Tribe class is virtual (Listing 8. you'll see a recurring theme: at each pass. where the population incremental increase is subject to a multiplier. Listing 8. or a Civilization. this. long increment = (long) (GetGrowthRatePerGeneration() * m_Population). } else m_Population = civil. a CityState.Guns. whether the instance is a Tribe.. As you would likely expect.Guns = civil.16: The Virtual GetNewPopulation Method public virtual long GetNewPopulation () { long increment = (long) (GetGrowthRatePerGeneration() * m_Population).16) .17: The GetNewPopulation Override Method in the CityState Class public override long GetNewPopulation () { const long veryBig = long. Listing 8.MaxValue / 2.DrawForm(). m_Population += increment. and the bacteria measure of disease production and resistance is introduced for the first time.Bacteria.

5){ if (m_Guns < veryBig . Listing 8. you can add features to a derived class that were not present in the original class by using the mechanism of overriding methods marked virtual.ToInt64(m_Guns * growthRate).18). I've had the issue of how to deal with all the instances of GroupOfPeople.2).MaxValue / 2. and Steel application. } As these overrides of the GetNewPopulation method show. return m_Population. } } m_Population += (long) (increment * 1.18: The GetNewPopulation Method in the Civilization Class public override long GetNewPopulation () { const long veryBig = long.1) { m_Bacteria += m_Bacteria. } if (m_Bacteria < veryBig . } m_Population += (long) (increment * 1. Creating a Custom Collection All along in the Guns. things pick up still more. with the introduction of the guns and technology index if the population growth rate is sufficiently high (Listing 8.if (m_Bacteria < veryBig . return m_Population. } if ((growthRate * 2) >= 0. } As the culture starts to really race for the stars in the Civilization class. Germs.2). . long increment = (long) (growthRate * m_Population). These instances need to be easily available for programmatic reference in several circumstances.1) { m_Guns = m_Guns + Convert.1. if (m_Population >= veryBig) { m_Population = long.1) { m_Bacteria += m_Bacteria.MaxValue . return m_Population. float growthRate = GetGrowthRatePerGeneration().

foreach (GroupOfPeople aGoP in peoples) { if (aGoP. namespace SybexC21 { public class PeoplesCollection : ArrayList { public PeoplesCollection() { } public void AddAPeople (GroupOfPeople aGoP) { this.ElapsedEventArgs e) { this. it's now easy to instantiate a collection based on this class: PeoplesCollection peoples = new PeoplesCollection(). We can now use the power of the peoples collection . Listing 8.collection class derived from ArrayList with two member methods: AddAPeople and RemoveAPeople. } public void RemoveAPeople (GroupOfPeople aGoP) { access all the instances of GroupOfPeople.Add(aGoP). For example. making for some fairly compact code.19: A Custom Collection Class Derived from ArrayList using System.but powerful .The obvious answer is create a custom collection for them. Listing 8.Remove(aGoP).isAlive) . using System.which uses the peoples collection to advance each GroupOfPeople instance through time.19 shows a simple . System.20: Using the Collection to Progress Each Instance through Time private void timer1_Elapsed(object sender.lblYears.Collections.which includes the derived members from the ArrayList class .Timers. } } } In the main form of the application.ToString(). Listing 8.globalTime.20 shows the entire code of the Timer's Elapsed event . derived from one of the collection classes explained in Chapter 7.Text = Globals. Listing 8.

and object . I also hope that I've given you some sense of how OOP concepts might be used in real .aGoP. classes. this chapter has touched only the surface of this topic.generation. } Globals.globalTime += Globals. Object and classes are the notes and score that keep the C# music playing! I'm glad I've had the chance to share some of this material with you. } Conclusion Objects. Although in some respects. important material has been explained. . the concepts explained in this chapter are extremely important.oriented programming in C# are fun and heady stuff! In a fully OOP language and environment.ProgressTheTribe().world projects.

and verbatim strings • The StringBuilder class • Implementing the ToString method • String class interfaces. I'll explain what it means to implement an interface and show you what String's implementation of these interfaces means to you. These methods don't require a string instance to work and are discussed later in this chapter in "The . C# String Basics As I explained in Chapter 6.String object. We'll also take a . we do so much of it. In this chapter. In other words.that can be implemented by the Dinosaur classes. using the Dinosaur classes developed at the end of Chapter 8 as an example. The String class implements several interfaces.that everything is string manipulation. on some days. This chapter explains the types and methods related to strings.String class has quite a few important static methods.String type. as programmers. I'll show you how to create a custom interface of your own ICarnivore . In fact. and creating your own interface • Regular expression classes and syntax Why is string manipulation so important? Because.Everything Is String Manipulation Overview • The immutability of the string type • The char type and static char methods • Control characters • Creating strings. which is an alias to the System. In addition to its instance members. C# provides great and powerful facilities for working with and manipulating strings. The last part of this chapter explains the basics of working with regular expressions in the title of this chapter . Fortunately. the System.' a text string is stored in a data type named string. it seems .detour to explore how you can implement an interface such as IComparable in your own classes. so that you can make short work of most common string manipulation tasks in your projects. string methods. Continuing with the interface detour. 'Zen and Now: The C# Language. when you create a string. you instantiate a System. implementing IComparable.hopefully profitable . including IComparable and IEnumerable.

"heLLo". str += " with Dudley this summer. in which a string is simply an array of characters terminated with a zero byte). it's easy enough to use assignments when you need to change the value of a string.only Length property of the string instance to find its length. Well. of course. The StringBuilder class is discussed later in this chapter in more detail. // Doesn't do anything does not change the contents of the variable str. In other words. str = str. ".Text.but it can be assigned.' assigned str is. So. This means that once it has been created.Replace ("l"." It's important to understand that the string type is immutable. For example: string str = "hello". No characters can be added or removed from it. str += " The End". how does one determine the length of a C# string? As you probably know. a string has a specific length . . nor can its length be changed. A C# string is not a terminated array of characters (unlike in the C language. But instantiating and destroying strings every time you need to change or edit one has negative performance consequences. no. in this example. what this set of statements actually does is create a new instance of the variable str each time a value is assigned to str. my friend. but it doesn't do anything (has no effect). my strings change all the time when I do things like: string str = "I am going to have a lot of fun". "L").String Class.. "L"). Once created. although combining it with an assignment would work: string str = "hello". str. you say.which. a string cannot be changed. an instance of str has been created . The value of the 'new.StringBuilder class.and a literal string value assigned to it . Is there any real problem with this? Well. But wait. .Replace ("l". The statement that the type is immutable means that an instance cannot be edited . you should consider working with instances of the System. of course.. If performance is an issue.three times. is fixed (since the type is immutable). you can query the read . which are mutable (can be changed). Using an instance method without an assignment is syntactically legal.

The Char Type
Char is a value type that contains a single Unicode character. While chars have a numeric value from hexadecimal 0x0000 through 0xFFFF, they are not directly numerically usable without explicit casting. Literal values are assigned to char variables using single quotes. The literal can be a simple, single letter, or an escape sequence (for more on escape sequences, see the next section). Here are two examples:
char chr = 'P'; // contains capital P char pi = '\u03A0'; // contains capital Pi

The char data type is related to the string data type: A string can be constructed from and converted into an array of characters - but string and char are two distinct types. Suppose you have a string defined like this:
string str = "rotfl";

You can retrieve a character of the string using the string's indexer. For example, the following statement:
char chr = str[1];

stores the character ‘o' in the variable chr. But you cannot set a character in the string with a statement like:
str[1] = 'a';

because the indexer property of the String class is read - only. (Actually, one would expect this in any case, because the string instance is immutable.) Note: If you use the Object Browser to have a look at the String class, you'll see that its indexer property, declared as this[int], has a get accessor, but no set accessor.

Control Characters
The backslash (\) is a special control character, also called the escape character. The character after the

backslash has a special meaning, as shown in Table 9.1. Table 9.1: Common C# Escaped Characters Character \0 \b \t \r \n \v \f \u, \x \" \' \\ Meaning Null Backspace Tab Carriage return New line Vertical tab Form feed A single Unicode character when followed by a four - digit hexadecimal number Double quote Single quote Backslash

The escape sequences \u or \x are used (interchangeably) to specify a single Unicode character. For example, \x03A9 and \u03A9 both mean the Greek letter capital omega (.). The escape sequences \x03A3 and \u03A3 will appear as the Greek letter capital sigma (Σ), as in this example, in which a button displays a capital sigma as its text when it is clicked:
private void btnDoIt_Click(object sender, System.EventArgs e) { char chr = '\u03A3'; btnDoIt.Text = chr.ToString(); }

Note: You can find a complete list of the Unicode character sets and their four - digit hexadecimal representations at

Char Static Methods
The methods of the Char class are, for the most part, static. These static methods allow you to determine whether a character is a letter, number, white space, etc. Figure 9.1 shows some of these static methods in the Object Browser.

Figure 9.1: The static methods of the Char class-shown here in the Object Browser- allow discovery of the nature of individual characters.

These methods return Boolean values, and each comes in two styles. One style takes a char as an argument. The other takes two arguments: a string, and an index representing the position of the character to be tested in the string. For example, here are the signatures of the static Char methods that test to see whether a character is a number:
bool IsNumber (char chr) bool IsNumber (string str, int iIndex)

As an example, the following click event tests to see whether Unicode (hex) 0x0041 is a letter.
private void btnChar_Click(object sender, System.EventArgs e) { char chr = '\u0041'; // capital Latin A if (Char.IsLetter(chr)){ MessageBox.Show ("\'" + chr.ToString() + "\' is a letter.", "Char re me fa so la", MessageBoxButtons.OK, MessageBoxIcon.Information); } }

Since 0x0041 is a Unicode capital A, the IsLetter method returns true, and the message box is displayed.

Using the other form of the method, one can check to see whether the third character that the user enters in a TextBox is a letter:
string str = txtDoIt.Text; if (Char.IsLetter (str, 2)) { MessageBox.Show ("\'" + str[2].ToString() + "\' is a letter.", "Char re me fa so la", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { MessageBox.Show ("\'" + str[2].ToString() + "\' is NOT a letter.", "Char re me fa so la", MessageBoxButtons.OK, MessageBoxIcon.Information); }

Running this code determines whether the third character entered by the user is a letter, and displays an appropriate message.

Warning: There is no user input validation or exception handling in this code. If there is no third character - because the user has not entered one - this code will cause a run - time exception. Note: The static method invocation Char.IsLetter (str, 2) is equivalent to Char.IsLetter (str[2]).

Note: You may have noticed the rather oddly named static Char IsSurrogate methods. If you are curious about the name, you may like to know that they test a character to see whether it has a Unicode value that is reserved for future expansion of the Unicode system (with values between 0xD800 and 0xDFFF).

Creating Strings
String, how shall I make thee: let me count the ways. There are many ways to create string variables, and no doubt you've used most of them. But let's go over this familiar ground one more time.

String Assignment
First, and most obviously, you can create a string by assigning a literal to a string variable:
string str = "I am a string!";

By the way, you can - of course - declare a string variable without initializing it:
string str;

As I showed you in Chapter 6, attempting to use this uninitialized variable causes a compiler error. What you may not know is that a string variable, since it is a reference type, can be initialized to null:
string str = null;

Initializing a string to null is not the same as initializing it to an empty string:
string str = "";

In the case of the null assignment, no memory has been allocated for the string. An attempt to determine the length of a null string - by using its Length property - causes an exception to be thrown. In contrast, the Length property of an empty string is 0. Note: An alternate way to initialize an empty string is to use the static Empty field of the String class: string str = string.Empty;.

When you pass a method a string literal, you are also creating a string. This is the case for pre - built

methods, such as the Show method of the MessageBox class:
MessageBox.Show ("Thanks for making me a string!");

This is also the case when you send a string literal argument to a user - defined method with a string parameter:
MakeAStr ("'Tis noble to be a string!"); ... private void MakeAStr (string str) { // Do something with str txtDoIt.Text = str; }

It's also the case that many methods create strings as their return value. This is famously so for the System.Object class's ToString method. All classes derive from Object, so all types provide a ToString method, which is intended to return a string that represents the current object. For example, the expression

returns the string '42'. Note: In your own classes, it is your responsibility to provide a ToString method that returns a string that represents the objects based on your classes. The "Implementing the ToString Method" section later in this chapter provides an example. Many other .NET Framework class methods besides ToString return strings. As one good example, have a look at the System.Convert class, which sports 36 different static (overloaded) Convert.ToString methods, each of which returns a string. Some of these are shown in the Object Browser in Figure 9.2.

Figure 9.2: Each of the 36 overloaded Convert.ToString methods returns a string.

String Constructors
The String class itself has eight constructors. If you look in the Object Browser, you'll find that five of these use pointers and are not safe under the Common Language Specification (CLS).

The unsafe constructors are not discussed here (to find out more about them, look up 'String Constructor' in online help). The remaining three safe String overloaded constructor methods are shown in Table 9.2. Table 9.2: Overloaded Safe String Class Constructors Method String (char, int) String (char[ ]) String (char[ ], int, int) Meaning Creates a string consisting of a char repeating int times. Creates a string from a char array. Create a string from a char array with a start position and length.

Let's have a look at an example that uses these constructors. It's easy to use the first form and create a

string consisting of the lowercase letter a 23 times:
string str1 = new String ('a', 23);

To use the second form, we must first create a char array:
char [] achra = new char[6] {'H', 'e', 'l', 'l' ,'o', '!'}; string str2 = new String (achra);

Third time pays for all! First, let's initialize a string:
string str3 = "Beasts of England";

Next, we can use the string's ToCharArray method to turn it into a char array:
char [] achra2 = str3.ToCharArray();

Finally, it's a hop, skip, and a jump to assign the first six elements of the char array to a string:
str3 = new String (achra2,0,6);

Listing 9.1 shows the code that uses the three constructors and displays the results in a ListBox. Listing 9.1: The Safe String Constructors
private void btnStr_Click(object sender, System.EventArgs e) { lstStr.Items.Clear(); string str1 = new String ('a', 23); char [] achra = new char[6] {'H', 'e', 'l', 'l' ,'o', '!'}; string str2 = new String (achra); string str3 = "Beasts of England"; char [] achra2 = str3.ToCharArray(); str3 = new String (achra2,0,6); lstStr.Items.Add (str1); lstStr.Items.Add (str2); lstStr.Items.Add (str3); }

Verbatim Strings
In Chapter 6, I mentioned that you could use the @ symbol to use a keyword as an identifier - if you were so inclined, you could create variables named, for example, @if, @string, and @true. (To repeat what I said back in Chapter 6, just because one can do something, doesn't mean it is a good idea.) In the context of strings, the @ symbol is used to create verbatim string literals. The @ symbol tells the string constructor to use the string literal that follows it "literally" - even if it includes escape characters or spans multiple lines. This comes in handy when working with directory paths (without the @, you would have to double each backslash). For example, the following two strings are equivalent:
string noat = "\\\\BIGSERVER\\C"; string withat = @"\\BIGSERVER\C";

Here's how you could use the @ symbol to take the place of the \r\n escape sequence of control characters, which means "carriage return, new line":
private void btnVer_Click(object sender, System.EventArgs e) { string str = @"It's fun to be split into a number of lines!"; txtDoIt.Text = str; }

If you assign the verbatim string to a TextBox that has its Multiline property set to True, you'll see that the line breaks (and white spaces) are preserved exactly as entered in the verbatim string.

If you are curious, here's how you'd create the same string as a non - verbatim string, using the control characters and string concatenation:
string str = "It's fun\r\n to be\r\n"+ " split into a number\r\n" + " of lines!"; txtDoIt.Text = str;

String Methods
The String class provides many powerful instance and static methods. These methods are described in this section. Most of these methods are overloaded, so there are multiple ways that each can be used.

Instance Methods
Table 9.3 describes many of the instance methods of the String class. Table 9.3: Instance Methods of the String Class Method Clone CompareTo CopyTo EndsWith Equals GetEnumerator IndexOf Insert LastIndexOf What It Does Returns a reference to the instance of the string. Compares this string with another. Copies the specified number of characters from the string instance to a char array. Returns true if the specified string matches the end of the instance string. Determines whether the instance string and a specified string have the same value. Method required to support the IEnumerator interface (see "Interfaces" later in thischapter). Reports the index of the first occurrence of a specified character or string within the instance. Returns a new string with the specified string inserted at the specified position in the current string. Reports the index of the last occurrence of a specified character or string within the

instance. PadLeft Returns a new string with the characters in this instance right - aligned by padding on the left with spaces or a specified character for a specified total length.

PadRight Remove Replace Split StartsWith Substring ToCharArray ToLower ToUpper Trim TrimEnd TrimStart

Returns a new string with the characters in this instance left - aligned by padding on the right with spaces or a specified character for a specified total length. Returns a new string that deletes a specified number of characters from the current instance beginning at a specified position. Returns a new string that replaces all occurrences of a specified character or string in the current instance, with another character or string. Identifies the substrings in this instance that are delimited by one or more characters specified in an array, then places the substrings into a string array. Returns true if the specified string matches the beginning of the instance string. Returns a substring from the instance. Copies the characters in the instance to a character array. Returns a copy of the instance in lowercase. Returns a copy of the instance in uppercase. Returns a copy of the instance with all occurrences of a set of specified characters from the beginning and end removed. Returns a copy of the instance with all occurrences of a set of specified characters at the end removed. Returns a copy of the instance with all occurrences of a set of specified characters from the beginning removed.

Static Methods
Table 9.4 describes many of the static methods of the String class. Table 9.4: Static Methods of the String Class Method Compare What It Does Compares two string objects.

CompareOrdinal Compares two string objects without considering the local national language or culture. Concat Creates a new string by concatenating one or more strings.

designed so that you can create an instance already containing text and . Property sets or retrieves the maximum number of characters that can be stored in the memory allocated for the StringBuilder opposed to the String class . If you have a situation in which you need to perform many string operations . or string representation of an object.for example.5: Key Instance Members of the StringBuilder Class Member Append What It Does Method adds string information to the end of the current StringBuilder instance.Text namespace. Method inserts a string.are mutable. within a large loop . See 'Formatting Overview' in online help for more information about format specifications. it does not have nearly as many members as the String class . or dynamically changeable. Table 9.but it does have enough to get most jobs done.Copy Format Join Creates a new instance of a string by copying an existing instance. AppendFormat Capacity Insert Length Remove Replace Method appends a formatted string to the current instance (see 'Formatting Over view' in online help for more information about format specifications). Property gets or sets the length of the instance.5.from a performance viewpoint it probably makes sense to use StringBuilder instances instead of String instances. Method removes a specified number of characters from the current StringBuilder instance. As you'd .set the Length and Capacity properties.if desired . The StringBuilder Class As I mentioned earlier in this chapter. Method replaces all occurrences of a specified character in the current instance (or part of the current instance) with a specified string. StringBuilder is located in the System . Concatenates a specified string between each element in a string to yield a single concatenated string. There are six different overloads of the StringBuilder constructor. instances of the StringBuilder class . into the current StringBuilder instance at the specified position. Formats a string using a format specification. Overloads make for some flexibility regarding the kinds of objects that can beappended (if not already string. Setting this to a value that is less than the length of the current truncates the instance. then the method converts the object to be appended to a string representation). As you can see in Table 9.

Append (txtSB1.ToString(). . MessageBoxIcon. first adding some text to the TextBox. which is truncated at four characters (Figure 9.StringBuilder theSB = new System. you'll see that the text has been appended to the StringBuilder.Text. "StringBuilder". Figure 9.3).Information). the StringBuilder is converted to a just plain vanilla string and displayed in a message box. and the StringBuilder is then displayed in a multiline TextBox.2: Creating and Truncating a StringBuilder on the Fly private void btnSB_Click(object sender. The Length property is used to truncate the StringBuilder to four characters. the Append method is used to store the contents of a TextBox in the StringBuilder.3: Text added to the TextBox is appended in the StringBuilder instance.Length = 4. the shortest StringBuilder constructor simply creates an instance without storing any text in it. theSB. Listing 9. theSB.Text).Show (theSB. MessageBox. Finally. System. } If you run the code shown in Listing 9. MessageBoxButtons.2. All instances of the first character are replaced in the StringBuilder with the second character. Let's do another StringBuilder example. The user then enters two characters in a fourth TextBox.StringBuilder().3 appends the contents of three TextBoxes into one StringBuilder. Listing 9. Listing 9.Text.2 demonstrates creating a StringBuilder instance on the fly.OK.suspect. Next.EventArgs e) { System.

ToString() returns the string "423".3: Appending and Replacing in a StringBuilder private void btnSB_Click(object sender. what the string will consist of. Figure 9. string str = txtReplace. until you try.4.EventArgs e) { System. so 423. Implementing the ToString Method I can't even start to think how many times in this book so far that I've used an object's ToString method. theSB. entering the strings 'A nose '. It's very handy . System.Append (txtSB2.Text.Replace (str[0].StringBuilder(). theSB.Text = theSB. and 'is a nose' in the TextBoxes. we'd like a number to be converted to a string representation of the number. theSB. The expression false.Text). theSB.ToString() returns the string "False".str[1]). With a capital F.Text.Append (txtSB3.Text = "". txtDoIt. and all classes are ultimately derived from Object. (For example. and replacing the character 'n' with 'r'.dandy. 'is a nose '.Append (txtSB1.) Since Object has a ToString method. Before we proceed to an implementation example.Text).Text). you can be sure that any instance in fact has some kind of ToString method.Listing 9. and you always know you'll get a string returned with the method although it's sometimes not entirely clear.4: The StringBuilder Replace method can be used to replace all instances of a character in a StringBuilder with another character. it differs from the keyword false used to evaluate a C# Boolean .StringBuilder theSB = new System. It's also reasonable to expect a Boolean value to return a string representation of the value it represents.Text. Clearly. let's think for a second about what "meaningful" means. txtDoIt. is shown in Figure 9. the ToString method of a Form instance returns the value of the Form's Text property as well as the instance name. I've repeated the mantra many times that it's up to you to implement a meaningful ToString method in your own classes.ToString(). } The result of running this code.

} set { m_Name = value. let's return for a moment to the abstract Dinosaur class that was used in Chapter 8. and interface inheritance. but it is still acceptable. private bool m_EatMeat. } } .4. What should ToString return for a Button control? One thing seems fairly consistent . as shown in Listing 9. and.4: The Base Dinosaur Class public class Dinosaur { private string m_Name.that when there is a Text or Name property for the class. } I'll be using the Dinosaur class in the next section to demonstrate implementing an interface. while we're at it. } public string Name { get { return m_Name." to demonstrate polymorphism in its derived classes. To construct a ToString implementation example. Here's the entire class definition from Chapter 8: public abstract class Dinosaur { public abstract string GetFood(). Listing 9. Simple value types are easy. "The Life of the Object in C#. so let's redo the base Dinosaur class so that it is no longer abstract. public virtual string GetFood(){ return "You are what you eat!". but more complex objects don't always have a clear string representation. public int Length = 0. let's add a few members. the instance ToString method returns it as part or all of the return value.expression.

Carnivore: " + this.ToString() + " meters". it has a virtual GetFood method that can be overridden. } . } Listing 9. public int Length = 0. a field.Name. } } } The base Dinosaur class shown in Listing 9. retval += ".4 is no longer abstract. GetFood: " + this. It's easy to add a GetString method to the Dinosaur class using the override keyword.5 shows the Dinosaur class with the ToString method added. public virtual string GetFood(){ return "You are what you eat!". retval += ". This GetString method combines the members of the class to return a string. } set { m_EatMeat = value.GetFood(). The EatMeat property is a Boolean used to get or set the fact that the instance is a carnivore. have been implemented. private bool m_EatMeat. instead. Name and EatMeat. and will be invoked in instances of Dinosaur and its derived classes: public override string ToString() { string retval = "Dino Name: " + this. In addition.EatMeat. Listing 9.ToString(). return retval. and two properties. Length: " + this.public bool EatMeat { get { return m_EatMeat.5: Dinosaur Class with ToString Method public class Dinosaur { private string m_Name.Length. retval += ". Length. Also. note that the class has no constructor.

return retval.Length = length. retval += ". GetFood: " + this.ToString() + " meters". retval += ". } } public bool EatMeat { get { return m_EatMeat. Carnivore: " + this. Listing 9. this. and Name property. Length: " + this.6: Classes Derived from Dinosaur class Lessemsaurus : Dinosaur { public Lessemsaurus (string theName. } set { m_EatMeat = value. Listing 9. } set { m_Name = value.Name = theName. each derived class overrides the virtual Dinosaur GetFood method.GetFood(). we need to derive some classes from Dinosaur and instantiate some objects based on the derived classes. int length){ this. } } public override string ToString() { string retval = "Dino Name: " + this. this.Name. Each class has a constructor that requires values for the instance Length field. retval += ".public string Name { get { return m_Name. } } Before we can actually take our ToString method out for a spin. .EatMeat. meaning that it cannot be set as part of instantiation. Note that the EatMeat property is set in the constructor for each class. when an instance is created.6 shows five classes derived from Dinosaur.EatMeat = false.ToString().Length. In addition.

Name = theName. this.EatMeat = false.". this.".EatMeat = false. } public override string GetFood (){ return "Diplodocus likes his greens. this. } public override string GetFood (){ return "My name is Allosaurus and I eat Apatosaurus. } } class Diplodocus : Dinosaur { public Diplodocus (string theName.". } } class Allosaurus : Dinosaur { public Allosaurus (string theName. int length){ this. } public override string GetFood (){ return "Apatosaurus eats plants.Name = theName. . this. int length){ this. int length){ this.Name = theName. int length){ this.Length = length. this.".Length = length.Name = theName.EatMeat = true. this. this. } } class Apatosaurus : Dinosaur { public Apatosaurus (string theName.Length = length.Length = length. } } class TRex : Dinosaur { public TRex (string theName.} public override string GetFood (){ return "Lessemsaurus bites branches from treetops.

System.SelectedItems. jPark[3] = new TRex("tRex".and it's about time. 12). jPark[0] = new Lessemsaurus("lessaemsaurus". As shown in Listing 9. foreach (Dinosaur dino in jPark){ lstDino. before all those dinosaurs go extinct ." + dino.Items.implement a click event that places the ToString return value from a dinosaur selected in the ListBox into a multiline TextBox (Listing 9.EventArgs e) { lstDino. 7). 34). I eat everything.Text = "". in a Form class.8: Displaying the ToString Return Value for the Selected Dinosaur Object private void btnToStr_Click(object sender.Count > 0) { if (lstDino. } } Next.Items. } } Finally . and in a Button click event instantiate five new objects based on the classes derived from Dinosaur. Listing 9. jPark[4] = new Diplodocus("doc".EatMeat = true.Name). add a concatenated string containing each object's GetFood method and name to a ListBox's Items collection. private void btnPoly_Click(object sender.EventArgs e) { if (lstDino.8).Add(dino.GetFood() + " -. System.Count > 0){ txtToStr. 14). 9). jPark[1] = new Apatosaurus("apatosaurus". .this.". } public override string GetFood (){ return "I am Tyrannosaurus Rex. jPark = new Dinosaur [5].7: Instantiating Objects Based on Classes Derived from Dinosaur Dinosaur [] jPark. declare an array of type Dinosaur named jPark[ ].Items.Clear().7. jPark[2] = new Allosaurus("allosaurus". Listing 9. I am always hungry.

you know that it implements certain members. If a class implements an interface. it means that it . Figure 9. the return value of its ToString method . C# classes can only inherit from one base class. However.SelectedIndex]. and you know what to expect about how those members work.5 shows the ToString value for the instantiation of the TRex class. What it boils down to is that an interface is a kind of contract.txtToStr.will be shown. implement multiple interfaces. Figure 9.5: It's up to you to implement a meaningful ToString method for your classes (the ToString return value for a TRex instance is shown).) In this respect.and its derived classes .must have implementations for the members whose signatures are included in the implementation specification.based on the ToString method placed in the base Dinosaur class . (In contrast. Interfaces An interface is like an abstract base class: both do not spell out implementation details and are used by derived classes. } } } If you run this project and select an item representing an instantiation of a class derived from Dinosaur in the ListBox. a class can derive from a base class and can also implement an interface or. This means that if you know that a class implements an interface. in fact. interfaces are more general . Interface implementations help to ensure consistency. .than class inheritance.Text = jPark [lstDino.ToString().and more useful . Studies have shown that software written with class libraries that implement interfaces tends to have fewer defects.

the creators of the . and IEnumerable. String Interfaces The String class implements four interfaces: ICloneable. Interface implementation also provides a good way for a team leader to deliver project specifications: the project lead writes the interfaces. The fact that String implements IEnumerable implies that you can use the foreach syntax to enumerate the characters in a string.Interfaces work well in situations in which one set of programmers has created a class library (for example. IConvertible.6). Note: The Object Browser will also tell you the members that the interface 'contract' requires to be implemented. Figure 9. You can determine the interfaces implemented by this class (or any other . followed by another capital letter. and define those members for you.NET Framework) and another set will be using the classes (you and me). and specifies mandatory implementation within classes. an interface identifier always starts with a capital I. Each of these interfaces is documented in online help.NET class) by looking up the class's documentation in online help or by having a look at the class in the Object Browser (Figure 9. IComparable and IEnumerable are the most important. By convention. IComparable. using a statement like this: .6: You can find the interfaces implemented by the String class by using the Object Browser. Of the String class interface implementations.

The long and the short of it is that Dinosaur instances will be sorted in arrays based on their Name property. Implementing IComparable in Your Own Class Let's go ahead and implement IComparable in the Dinosaur class (it will then be implemented in all the classes derived from Dinosaur).. } Implementing the IComparable interface means that the class must contain a CompareTo method that compares the current instance with another object of the same type. In the meantime. You can see the code for this method in Listing 9.Name. Once this method is implemented. Next. objects of the class work with the BinarySearch and Sort methods of the Array class.CompareTo (dino. I'm going to take a shortcut: the Dinosaur class CompareTo will be implemented as a string CompareTo on the Name property of each dinosaur instance created from Dinosaur and its derived classes. Listing 9.foreach (char chr in str) { . you will get a compile . public int CompareTo (object obj) { Dinosaur dino = (Dinosaur) obj. zero if the two instances are the same.. return this. The first step is to add the interface name following the inheritance operator in the Dinosaur class declaration: public class Dinosaur : Icomparable Note: If you don't actually add the members specified in an interface to a class (or derived class) that implements the interface.Name). . and greater than zero if the current instance is greater that its comparison instance.. CompareTo has a fairly complex int return type that must be less than zero if the current instance is less than its comparison instance. we need to add a CompareTo method to the Dinosaur class.CompareTo Method" in online help.9. You can read more about the implementation details by looking up "IComparable..9: Implementing IComparable in the Dinosaur Class public class Dinosaur : IComparable { .time error.

jPark[4] = new Diplodocus("doc".EventArgs e) { lstDino. jPark[1] = new Apatosaurus("apatosaurus".} } To try this out.GetFood() + " -. because that's the way they were sorted in the jPark Array.10.Sort (jPark).Sort after the Dinosaur objects have been instantiated in the jPark array: Array. 7). System. we can add a call to the static method Array. foreach (Dinosaur dino in jPark){ lstDino. jPark[3] = new TRex("tRex". jPark[0] = new Lessemsaurus("lessaemsaurus". Listing 9. 12). 14).Name). . jPark[2] = new Allosaurus("allosaurus"." + dino. Array. 9). you'll see that the Dinosaur instances have been added to the ListBox alphabetically by name (Figure 9.10: Sorting the Dinosaurs private void btnPoly_Click(object sender.Sort (jPark).Add(dino. jPark = new Dinosaur [5]. } } When you run this code.Clear(). The code that uses the IComparable implementation to sort the objects created from classes derived from Dinosaur is shown in Listing 9.Items. 34).Items.7).

7 would be first. set. Listing 9. Icarnivore Next. let's implement our own interface. Since it is ours. or if we both are carnivores and I am bigger than you: public bool CanIEatU (object obj){ Dinosaur dino = (Dinosaur) obj.11: The ICarnivore Interface interface ICarnivore { bool EatMeat {get. then the ListBox would be sorted by the text strings in the Items collection. we need to add ICarnivore to the inheritance clause of the Dinosaur class declaration: public class Dinosaur : IComparable. which determines whether one dinosaur instance can eat another. ("Apatosaurus" in Figure 9. The logic of this method is that I can eat you if I am a carnivore and you are not. Note: It's worth saying explicitly that the interface definition only defines the members required for the interface.EatMeat == false) . we need to create an implementation of CanIEatU for the Dinosaur base class. we get to name it and to define its members. and "My name" would be last.Figure 9. not the Dinosaurs' Name properties.) Implementing the ICarnivore Interface Kicking this all up a notch.11 shows the interface definition. Note: If the ListBox Sorted property were set to True. There is no implementation code in the interface definition. } To implement the interface. Let's make an ICarnivore interface that consists of the Boolean property EatMeat (already implemented in the Dinosaur class) and a Boolean method CanIEatU. if (this.} bool CanIEatU (object obj). Listing 9.7: You can tell that the classes derived from Dinosaur have implemented IComparable because they are ordered by dinosaur name.

public virtual string GetFood(){ return "You are what you eat!".EatMeat == true && dino.) Listing 9. } set { m_Name = value.EatMeat == true { if (this. public int Length = 0.12: The Complete Dinosaur Class (CanIEatU Method at the End) public class Dinosaur : IComparable. ICarnivore { private string m_Name.12 shows the complete Dinosaur class with the ToString method.EatMeat == true && dino. else if (this. (You'll find the CanIEatU method towards the bottom of the listing. and the CanIEatU implementation of ICarnivore. } } Listing 9. the CompareTo method.return false. } public string Name { get { return m_Name. } set { .EatMeat == false) return true.Length) return true. } } public bool EatMeat { get { return m_EatMeat. else // this. else return false. private bool m_EatMeat.Length > dino.

EatMeat == false && dino.Name. else // this. } public int CompareTo (object obj) { Dinosaur dino = (Dinosaur) obj.GetFood().ToString(). . Carnivore: " + this.Name). } } public override string ToString() { string retval = "Dino Name: " + this.Length) return true.m_EatMeat = value. TRex chomper = new TRex ("Chomper".EatMeat == false) return false.EatMeat == true) return false. } public bool CanIEatU (object obj){ Dinosaur dino = (Dinosaur) obj. GetFood: " + this.Items.EatMeat == false && dino. retval += ". else return false. 14). retval += ".EventArgs e) { lstDino. Length: " + this.Length.EatMeat == true && dino. else if (this.EatMeat == true { if (this. if (this.Length > dino.EatMeat == false) return true.CompareTo (dino.EatMeat. and then uses the members of the instances to display an appropriate message: private void btnICarnivore_Click(object sender. return retval. } } } You can now alter the project to add a click event that instantiates two dinosaur objects. retval += ". System.Clear(). else if (this. return this.ToString() + " meters".EatMeat == true && dino.Name. uses the CanIEatU method to determine who is lunch (and who is not).

Add(chomper. lstDino.ToString()).Name + " the " + chomper.GetType().Items.Add("has eaten").GetType(). .CanIEatU (doc)){ lstDino. Figure 9.Diplodocus doc = new Diplodocus ("Doc". 9).Add("for lunch. lstDino. } } Figure 9.Items. You should also note that the ICarnivore interface and implementation members appear in the Class View window when you examine one of the classes derived from Dinosaur (Figure 9.Items.Name + " the " + doc. if (chomper. lstDino.Items.8: The ICarnivore interface determines who has eaten whom.Add(doc.8 shows the results of running this code displayed in the ListBox at the top of the form.").ToString()).9) and also in the Object Browser (Figure 9.10).

such as 'Montezuma'.Figure 9. For one thing.for example. But the principles are the same when they are used in more substantive ways . The simplest solution to many programming problems .strength software. user input validation. the regular expression is a literal string of characters . In the simplest example. The regular expression fails to match a string that does not contain it. Figure 9. including string manipulation.9: The ICarnivore interface (and the members of the TRex class) are shown in Class View. Typically. This simple regular expression pattern matches against a string that contains it . While this example seems simple enough.unless the dinosaurs come back. you are unlikely to need to implement Dinosaur and derived classes .10: The ICarnivore interface and the classes derived from Dinosaur appear in the Object Browser. 'Allosaurus'.and you should know about interfaces and interface inheritance. you should not underestimate the importance of regular expressions. which provide access to powerful algorithms that are the natural and easy answer to many programming problems. and searching and replacing. A regular expression is used to define a pattern of characters. it is as important as class inheritance for creating industrial .as an important mechanism requiring adherence to standards .particularly those .and an ICarnivore interface . This discussion of interfaces and interface implementation has necessarily been somewhat superficial. arguably. 'saur'. the pattern represented by the regular expression is matched against a string. These patterns can be used for many purposes. Regular Expressions One of the most powerful tools in the string manipulator's arsenal is the regular expression.for example.

11 shows the regular expression classes in this namespace in the Object Browser. The Regular Expression Classes The regular expression classes are located in the System.RegularExpressions.often involves regular expressions. like so: using System.using regular expressions in C# is a subject that would easily fill an entire book . which means that you should add a using directive to the beginning of any module that involves regular expressions. • Show you how to set up a regular expression test bed. . • Explain the language of regular expression patterns.involving strings .which implies that in the remainder of this chapter I can only scratch the surface of the topic. Figure 9.NET regular expression classes.NET Framework's powerful regular expression syntax is based on the regexp syntax used in Perl5.NET regular expression classes interrelate. and become a 'regex' master in your own right.Text. Note: The . I'll: • Tell you about the .RegularExpressions namespace. This is an ambitious agenda . • Provide an example using regular expressions that shows how the . so that you can experiment with C# and regular expressions. In this section. I invite you to use this material as a jumping off place.Text.

The Matches method of the Regex class returns a MatchCollection object. Escape and Unescape) that can be used with other classes. It also provides a few static methods (for example.Figure 9. shown in the Object Browser. . The Match method of the Regex class returns a Match object. which contains a collection of Capture objects. Here are brief descriptions of the regular expression classes (you may need to view online documentation or browse through the later parts of this chapter to understand some of the terminology): Regex This class can be used to instantiate an immutable regular expression object. Instances of Group are returned by the Match. Capture A single subexpression match. MatchCollection A sequence of nonoverlapping successful matches.Groups property.11: The Regular Expression classes. Match The results of a regular expression matching operation. Group The results of a single capturing group.

GroupCollection A collection of captured Group objects.Match ("42"). you don't have to do much to use regular expressions.CaptureCollection A sequence of captured substrings.' Next. .NET's Help System. Note: The \d{1. 'Using C# . provide a string to test against as an argument.NET Framework SDK. Next. The general form is to create a Regex object that is a pattern.') A Regular Expression Test Bed At a very basic level. Figure 9. Note: The best way to find online documentation about regular expression classes and language is to set the Help Search filter to '. the location of the match. Note: The string verbatim symbol (@) is required before the regular expression patternotherwise the compiler will throw an exception because of an unrecognized escape character. Three multiline TextBoxes are intended for user entry of a test string and a pattern and to display the results. search for 'Regular Expressions. For example.12 shows the user interface for a general regular expression test bed using this construction.3}"). The properties of the Match object will then contain information about the first match: whether it was successful. and assign the results of the Regex instance's Match method to a Match object: Match match = regex.' (You'll find details on using and configuring online help in the Appendix. the substring matched. and so on. The Captures property of the Match and Group classes returns a CaptureCollection object. The Groups property of the Match object returns a GroupCollection object.3} regular expression will be explained later in this section. the following regular expression will match any integer with between one and three digits: Regex regex = new Regex (@"\d{1.

12: It 's easy to set up a program that can be used to test regular expressions. } } Warning: It perhaps doesn't need to be said this late into this book. } else { txtResults.Text = str.Success) { string str = "Found a match at postition " + match.formed patterns. Listing 9.Figure 9. Match match = regex. but .Text = "Sorry.Text). entering a backslash (\) by itself as a pattern will cause an exception to be thrown when the compiler attempts to instantiate a Regex object based upon it.Value. no regular expression pattern match!". Listing 9.Index. It's fairly easy to throw exceptions in the Regex class by entering strings that are not recognized as well .Text).13 shows the click event code used to test for a regular expression match when the user enters a test string and pattern. System. . the real world .EventArgs e) { Regex regex = new Regex (txtPattern.13: Testing Regular Expressions private void btnTest_Click(object'd want to include exception handling in this code. if (match. str += "The substring matched is " + match.Match(txtStrTest. For example.

insensitive pattern. you should know that there are some attributes. the pattern: (?i:saur) matches the string 'ApatoSaUrus'. also called options. if you had tried to match the pattern 'saur' against the string. Regex regex = new Regex ("saur".insensitive match. we can start looking generally at how they are constructed.sensitive way.alphabetic characters using escape sequences. it would have behaved in a case . you can match many non . (For a complete list of these options. For example. prefix the pattern with a question mark followed by the attribute character followed by a colon. case . and enclose the whole thing within parentheses.') These options are specified using inline characters.IgnoreCase). and there would have been no match. Ignore unescaped white space within a pattern.The Syntax of Regular Expressions Now that we have a way to test our regular expressions. Attributes can also be passed as an argument to an overloaded Regex constructor. search online help for the topic 'Regular Expression Options. which impact the way that a match is made. In addition. Options First. and enable commenting within a pattern following a pound sign (#). To enable an attribute inline. For example. RegexOptions.6. Literal Characters You've already seen that an alphanumeric character within a regular expression matches itself. . The most commonly used are: Character i x Meaning Perform a case . Regular expression literal character matches are shown in Table 9. would create the same. Of course.

and Their Matches Character or Sequence Alphabetic (az and AZ) or numeric (09) \b \f \n \r \t \/ \\ \. For a regular expression. (Character classes are explained in the next section. a match. where it means a backspace. Within a regular expression. you'll see that there is. . use \r. \b means a word boundary. $ ^ { [ ( | ) * + ? \ match themselves) Backspace (see note following table) Form feed New line Carriage return Tab Slash ('forward slash. indeed.' literal /) Backslash (literal \) . except within a [] character class.) It's not a bad idea to see that these escape sequences really do match as specified. In the first box. When you click the Test button. as the string to test. * + ? | ( ) [ ] { } The ASCII character specified by the octal number xxx The Unicode character specified by the hexadecimal numberxxxx Note: The escaped character \b is a special case.6: Regular Expression Characters and Character Sequences. \* \+ \? \| \( \) \[ \] \{ \} \xxx \uxxxx Matches Itself (all characters other than . Let's use the test environment developed earlier in this chapter to make sure that \r matches a carriage return.Table 9. just hit the Enter key.

the syntax of regular expressions provides special . A hyphen is used to indicate a range. you may have noticed that you could use the case attribute instead of separately listing uppercase and lowercase ranges.Z0 . For example. you'll find that the first match is the letter ‘a'.zA .L] does not produce a match with the string "XYZ" but does match the string "xyz".z] [a . Common Character Class Representations Because some character classes are frequently used.z]) is the equivalent to [azA . If you run the pattern in the test environment against 'abc'.Character Classes Individual literal characters can be combined into character classes in regular expressions. The characters within a class can be specified using ranges. By the way. Negating a Character Class A character class can be negated. A match occurs when one or more of the characters contained in the class produces a match with the comparison string. Here are some examples: Sequence [a . the regular expression [^a . "abcABC123" contains a match. Character classes are contained within square brackets. but "abcABC" does not. rather than by specific enumeration.and uppercase letters and all numerals Using this notation.zA .Z] will match if and only if the comparison string contains at least one nonalphabetic character.zA . [a .zA . place a caret (^) as the first character inside the left bracket of the class. A negated class matches any character except those defined within brackets. For example. To negate a character class. (?i:[a . the regular expression [csharp] matches against any string that contains at least one of the letters (such as 'abc').L] [a .9] Meaning All lowercase characters from a to z All lowercase characters from a to z and all uppercase characters from A to L All lower .Z].

Any one character that is not a digit. such as "lions and tigers.. . equivalent to [a . Any one space character or other white . Square brackets are not used with most of these special character 'abbreviations. such as "ineedsomespace". \w \W \s \S \d \D Matches Any one character between the square brackets.sequences that are shorthand representations of these classes. In another example.. \s matches a string containing a space. equiva lent to [^ \t\n\r\f\v]. equivalent to [\t\n\r\f\v]. equivalent to [^0 . For example. But \s fails against strings that do not contain white .space characters. Any one character other than new character.' The sequences that can be used for character classes are shown in Table 9.9_]. equivalent to [^\n]. For example. equivalent to [0 . And \w\w\w\w\w would match any five . the pattern \W matches a string containing a hyphen ( .] . \d\d would match any two . This isn't good enough. equivalent to [^azA . Any one character other than a letter.digit number. it doesn't allow complex pattern matches involving varied numbers of characters.) but fails against a string containing only letters (such as "abc").space character. the only way to achieve this using a regular expression would be to enumerate each character. Repeating Elements So far. Table 9.Z0 . Any one digit. Any one letter. and bears". or underscore. number.] [^.Z0 . or a pair of letters followed by a number of any length.9]...9_]. Any one character other than a space or other white .9]. you might want to match a number between two and six digits in length. number.zA . if you wanted to match a multiple number of characters. such as "string" or "94707".7: Character Class Sequences and Their Meanings Character Sequence [. In addition to being cumbersome.letter alphanumeric string. For example. or underscore. Any one character not between the brackets.7.

Grouping. Match zero or more occurrences of the preceding element in other words. to match a number with one to three digits. equivalent to {1. The curly braces follow the pattern element that is to be repeated and specify the number of times it repeats. Groups several items into a unit. or subexpression. Table 9.. Matches the character or subexpression to the left or right of the | character. and Reference Characters Character | (. some special characters are used to specify common types of repetition. Both the curly .8.9 and discussed below.}.}.This kind of 'wildcard' pattern is specified in regular expressions using curly braces ({}). the element is optional. For example. Organizing Patterns Regular expression syntax provides special characters that allow you to organize patterns. that can be used with . These characters are shown in Table 9.9: Alternation. equivalent to {0. you can use the regular expression \d{1. As we've already seen. Match the preceding element exactly n times. In addition.brace syntax and the special repetition characters are shown in Table 9. equivalent to {0. the element is optional. \s+love\s+ matches 'I love you!' but not 'Iloveyou!'. Match one or more occurrences of the preceding element.) Meaning Alternation.} {n} ? + * Meaning Match the preceding element at least n times but no more than m times. Table 9.. Match the preceding element zero or one times in other words.m} {n.1}.8: Syntax for Repeating Pattern Elements Repetition Syntax {n. Another example is that you can match a word surrounded by space characters by creating a pattern that sandwiches the word between \s+ sequences. but can also appear multiple times. Match the preceding element n or more times.3}. Grouping.

using the position of the left parenthesis to determine order. Subexpressions are referred to using a backslash followed by a number.Z]{4} matches either two digits or four capital letters. and so on. Each subexpression that has been grouped in parentheses is internally assigned an identification number. \2 the second. Grouping Parentheses are used to group elements in a regular expression. this expression doesn't distinguish between the two kinds of quotes. that. matches any number of characters. the regular expression jaws|that|bite matches the three strings jaws. [^'"]*. Nesting of subexpressions is allowed. \n Referenced subexpression match. For example. \d{2}|[A . (The middle element. Matches the same characters that were matched when the subexpression \n was first matched. they can be treated as a single element using repetition syntax. Once items have been grouped into subexpressions. For example. Referring to Subexpressions Parentheses are also used to refer back to a subexpression that is part of a regular expression. chocolate(donut)? matches chocolate with or without the optional donut. the regular expression ['"][^'"]*['"] matches a string that starts with a single or double quote and ends with a single or double quote. For example. The subexpressions are numbered from left to right. the string "We called him Tortoise because he taught us' . Alternation The pipe character (|) is used to indicate an alternative.) However. A comparison string that started with a double quote and ended with a single quote would match this expression. For example. So \1 means the first subexpression. In another example. provided they are not single or double quotes. A reference to a subexpression matches the same characters that were originally matched by the subexpression. or bite.repeated syntax and referred to later in an expression.

Here's the regular expression pattern to accomplish this: (['"])[^'"]*\1 As you'll see if you try running this in the test bed program. you should know about the regular expression characters that are used to specify a match position. typically the beginning or end of a word or a string. it should end with a double quote.Matching Characters Character ^ $ \b Matching Position The beginning of a string. the beginning of a line. To match a word. Also see the note about \b in the "Literal Characters" section of this chapter. rather. Specifying a Position for a Match Finally. if it starts with a single quote. A word boundary. the end of a line. Not a word boundary. in multiline searches. Here are two examples of regular expressions that match positions. it matches the position between a \w character and a \W character. A better result would be to have a match depend on which kind of quote the match began with. If it begins with a double quote. Table 9. The end of a string. they match boundaries. you can use the regular expression \b[A-Za-z'-]+\b . In other words. It's easier to get a sense of how to use them in the context of actual tasks. matches the regular expression pattern. This can be accomplished by referring to the earlier subexpression so that the same kind of quote that the string starts with is required for a match. it should end with a single quote.10. \B You'll find that many regular expressions use positioning characters.10: Regular Expression Position . These characters. likewise.which starts with a double quote and ends with a single quote. in multiline searches. do not match characters. it matches 'CSharp' but not 'CSharp'. shown in Table 9.

is shown matching this pattern in Figure 9.3}|i[vx])$) MDCCLXXVI. To match a string by itself on a line. or 1776.Note: You have to think carefully about what exactly a 'word' is and what you need to accomplish in a given programming context. but not if any other characters (even spaces) are on the line. Pattern Matches To match a date in mm/dd/yyyy format.and that you can use to test your understanding of the syntax of regular expressions.13. For example. This regular expression defines a word to include apostrophes and hyphens but not digits or underscores (so 85th would fail the match).3}|c[dm])(l?x{0.matching abilities in combination with string and regular expression class methods in your programs.3}|x[lc])(v?i{0. So I'll start by showing you a few somewhat complicated regular expression patterns that may be useful . start the regular expression with a caret (^) and end with a dollar sign. Regular Expression Examples The full power of regular expressions becomes apparent when you start to use their pattern . the regular expression ^Prospero$ matches the string Prospero if it is alone on a line. But regular expressions can do a lot for you more or less on their own. use the pattern: (?i:^m*(d?c{0. . use the pattern: \b(\d{2})\/(\d{2})\/(\d{4})\b To match a number in Roman notation.

As explained earlier in this chapter. you might need to know the protocol and http://www.. but optional.there's no real need for regular expressions .bearhome. Path information following the domain is allowed.13: The regular expression shown here matches a Roman number. I'll show you how to use regular expressions to 'decompose' a URL into its constituent parts.but following this example should help you to understand how the . include protocol . Matching Substrings in an Expression The preceding pattern that matches a URL is all very well and good. Note: Note that the pattern used in the Roman number example includes the i (case insensitivity) attribute. For a user interface. once again. To match a URL.]+\/?\S* This will match any standard URL that includes a protocol. you could also do this with string methods .sybex. I've used a TextBox for entering the URL (which is to. this could have been accomplished using the RegexOptions enumeration and the Regex constructor rather than as part of the pattern string. or the domain. In this match this pattern. Obviously. http:// or ftp://.NET Framework regular expression classes are related. but what if you need only part of the URL? For instance. use the following regular expression: \w+:\/\/[\w.g. So both http://www. e.Figure 9.

]+)(\/?\S*) In order to assign the pattern to a string variable. . string [] results = new string[4].]+\/?\S* To do substring matches on this. The original URL matching pattern that I showed you is \w+:\/\/[\w. The first step in the click event is to rewrite the regular expression pattern a bit. we start a counter and a string array to hold the results: int k = 0. Next. a ListBox to display the URL's decomposed parts.IgnoreCase). we need to break it up into groups using parentheses: (\w+):\/\/([\w. we have to use it to retrieve the substring values. create a Regex instance using the "ignore case" option: Regex regex = new Regex (pattern.followed by ://). we need to use the verbatim symbol (otherwise the compiler thinks the string includes an invalid escape character): string pattern = @"(\w+):\/\/([\w. First.]+)(\/?\S*)".14: Regular expressions can be used to decompose a URL into constituent parts. and a Button whose click event will process the decomposition (Figure 9. Figure 9. RegexOptions.14). Now that we have our regular expression.

For each item in it (each Group).Clear(). private void btnDecomp_Click_1(object sender.]+)(\/?\S*)".Captures.Match(txtURL. use the Group instance's Captures property to populate a CaptureCollection instance.Text). GroupCollection gc = match. Use the Match instance's Groups property to populate a GroupCollection object: GroupCollection gc = match. string pattern = @"(\w+):\/\/([\w. Cycle through the GroupCollection instance.Count.14. j < cc.Text). Match match = regex. i < gc.Count.14: Decomposing a URL into Groups using System. add the text in the results array to the ListBox Items collection for display. System. i++) { CaptureCollection cc = gc [i]. . j++) { results[k] = cc[j].Items.Count .. for (int j = 0.Use the instance's Match method to assign the match with the URL entered by the user to a Match object: Match match = regex.IgnoreCase).Match(txtURL.EventArgs e) { lstURL.Groups. k++. int k = 0.Text. Listing 9.RegularExpressions. i < gc. The complete code for the URL decomposition is shown in Listing 9. RegexOptions. for (int i = 0.. i++) { . Cycle through each CaptureCollection instance.Value.Groups. string [] results = new string[4]. Regex regex = new Regex (pattern. } } Finally. and use each item's Value property to add a substring match to the results array and increase the array counter: for (int i = 0.

'everything.I concluded the chapter with an example showing this.Captures. I also showed you how to implement ToString methods in your own classes.Add ("Protocol: " + results[1]). I explained how to create instances of the types. and how to use the instance and static methods related to these classes. lstURL.Items. and how to create a custom interface for use with Dinosaur and its derived classes. char. input.CaptureCollection cc = gc [i]. lstURL.Add ("Full URL: " + results[0]). for (int j = 0. dubbed ICarnivore. } } lstURL.NET regular expression classes and collections interact .related types: least . indeed. we touched on the fact that the string type implements IComparable. While regular expressions are not unique to C# or . } Conclusion This chapter has covered a lot of ground.introduced to everything you need to know to work with strings. We started with the various string .Items. and output. The final part of this chapter explained working with regular expressions. It's time to move on.Add ("Domain: " + results[2]). and the StringBuilder class.Items. lstURL. j++) { results[k] = cc[j].Value. . k++. successfully using them can add a great deal of ease and power to your programs. j < cc. You do need to understand how the . If string manipulation is. Chapter 10 will show you how to work with files.Count . Moving on.NET.' it is fair to say that you've been .Add ("Path: " + results[3]). I showed you how to implement IComparable in your own classes.Items.

using both the system Registry and isolated storage. In other words. For a full list of Environment members. I'll show you how to save initialization information . you can retrieve command .Working with Streams and Files Overview • Files and directories • Using the Environment and Path classes • Finding files • Working with the system Registry • Using isolated storage • Understanding streams • Reading and writing files • Web streams • Using a FileStream asynchronously It's an unusual program of any size that doesn't save and retrieve values for initialization purposes. DirectoryInfo.or. along the way you'll need to obtain information about the file system a program is operating in. File. and read from and write to files. directories. paths. part of the System namespace. the current user logged on to a system. and show you how to read and write both text and binary files. Most programs also need to work with files. variables that are used to maintain information about the system environment. Next. Directory. and FileInfo classes essential for obtaining information about local systems. Using the Environment class. This chapter explains how to work with files and directories. the version of the CLR that is running. Environment Class The Environment class. read (and write) configuration data. the contents of the call stack. the time since the last system boot. exit codes. and more. I'll explain streams and how the stream classes interrelate.line arguments. Path. to accomplish many tasks. contains properties and methods that let you get information about the system on which a program is running.and for manipulating them. Files and Directories You'll find the Environment. We'll also take a look at web streams and at invoking FileStreams asynchronously. . and files . besides information about environment variables. and the environment strings .

GetLogicalDrives Method returns an array of strings containing the names of the logical drives on the system. It is unusual that this enumeration is defined within the Environment class.SpecialFolder enumeration (see Table 10.SpecialFolder Enumeration Constant Special Folder Description .2: Values of the Environment.1: Environment Class Members Related to Files and Directories Member What It Does CurrentDirectory Property gets (or sets) the fully qualified path for the current directory.2 shows the possible values of the Environment. SystemDirectory Property gets the fully qualified path of the system directory. Table 10.g.1 (it is more common to define the enumeration directly within the namespace.look up "Environment Members" in online help.1: The SpecialFolder enumeration is defined within the Environment class.SpecialFolder enumeration.2). as you can see in the Object Browser in Figure 10. e. System). Table 10. Table 10. Figure 10.. GetFolderPath Method gets the fully qualified path to the special folder identified in the Environment. Table 10.1 shows some of the static Environment methods and properties that are related to the file system.

Hence String str = Environment.roaming user Directory that serves as a common repository for documents (My Documents) Program files directory Directory that contains the user's program groups Directory that contains the user's most recently used documents Directory that contains Send To menu items Directory that contains Start menu items Directory that corresponds to the user's Startup program group System directory Directory that serves as a common repository for document templates Being defined within the Environment class implies that the enumeration must be referenced with the class as a qualifier. The correct formulation is String str = Environment.ProgramFiles).ApplicationData Directory that serves as a common repository for applicationspecific data for the current roaming user CommonApplicationData Directory that serves as a common repository for application specific data that is used by all users CommonProgramFiles Cookies DesktopDirectory Favorites History InternetCache LocalApplicationData Personal ProgramFiles Programs Recent SendTo StartMenu Startup System Templates Directory for components that are shared across applications Directory that serves as a common repository for Internet cookies Directory used to physically store file objects shown on the Desktop Directory that serves as a common repository for the user's favorite items Directory that serves as a common repository for Internet history items Directory that serves as a common repository for temporary Internet files Directory that serves as a common repository for applicationspecific data that is used by the current. non .ProgramFiles).time syntax error.GetFolderPath(SpecialFolder.GetFolderPath(Environment. . produces a compile .SpecialFolder.

Text = Environment... System.1..1: Getting the Personal Directory private void btnMyDocs_Click(object sender. I have been rigorous about always using some variant of the try. Let's look at a brief example.): Note: In the examples in this chapter.Personal).SpecialFolder.GetFolderPath (Environment. of course.IO namespace.EventArgs e) { try { txtStartDir. Files can be moved or deleted. "Zen and Now: The C# Language. Path Class The Path class... This is an arena in which. Drives can be unavailable. Permission levels may be required to access certain information.catch.with the interesting implication that a path string does not have to represent an existing path in order to be used .catch.. Note that these methods do not interact with the file system and do not verify the existence of specified files or paths . realistically.finally exception handling syntax explained in Chapter 6. you can never assert perfect control .which on my Windows XP system happens to be C:\Documents and Settings\harold\My Documents.The fully qualified path that these constants refer to depends. provides static methods that perform string manipulations on file and path information stored in string instances.shown in an example a little later in this chapter. on the operating system and configuration. } } If you run this code. } catch (Exception excep) { MessageBox. shown in Listing 10. Listing 10.finally. the current Personal directory will be displayed in a TextBox." The reason is that when it comes to files and file systems..Show (excep.and should therefore embed program statements within exception handling blocks.Message). defined in the System.. or try. one just never knows. try. (I'll be using the contents of this TextBox as the starting place for a recursive scan of a computer's directories . and so on. namely getting the current Personal directory .

Returns the extension of the specified path string.GetExtension method returns a file extension. that the specified file name and path in the fileName variable represents an existing file. Gets a value indicating whether the specified path string contains absolute or relative path information.3. Returns the directory information for the specified path string. The methods of the Path class. So the following code stores the value "sybex" in the variable extension: string fileName = @"C:\theDir\myfile. Returns the file name and extension of the specified path string. As another example. Returns the path of the current system's temporary folder. Determines whether a path includes a file name extension. except you do not have to worry about whether a backslash is the end of the left part or the beginning of the right part. are shown in Table 10. Returns a unique temporary file name and creates an empty file by that name on disk. Gets the root directory information of the specified path. all of which are static. This is like string concatenation. GetFullPath GetPathRoot GetTempFileName GetTempPath HasExtension IsPathRooted Returns the absolute path for the specified path string. . Joins a path name (on the left) with a path and/or file name (on the right).3: Path Class Methods Method ChangeExtension Combine What It Does Changes the extension of a path string. GetDirectoryName GetExtension GetFileName GetFileNameWithoutExtension Returns the file name of the specified path string without the extension. the Path.GetExtension(fileName).or. Table 10. extension = Path. So you could use Path class methods to construct a string representing a path and file. for that matter.with the members of this class.sybex" string extension. But nothing about this code snippet guarantees that 'sybex' is a valid file extension . and then check to see whether the file actually exists before using the string.

Selected members of FileInfo are shown in Table 10. File. It is more or less the case that one can achieve comparable functionality using either parallel pair Directory and File. which require a string argument representing a file name. • File.Directory and File Classes Four parallel classes are designed to let you work with (and perform discovery on) directories and files. The GetFiles method of DirectoryInfo returns an array of FileInfo objects that are the files in a given directory. Selected members of the DirectoryInfo class are shown in Table 10.IO namespace and all sealed so they can't be inherited . • FileInfo contains no static members. it probably makes more sense to instantiate DirectoryInfo and FileInfo objects .4 for class methods).are Directory. Directory and File.which is my stylistic preference in any case. root information. and FileInfo. so you must instantiate a DirectoryInfo object to use it. DirectoryInfo.4: Directory Class Static Methods Method CreateDirectory Delete Exists GetCreationTime GetCurrentDirectory GetDirectories GetDirectoryRoot What It Does Creates a directory or subdirectory Deletes a directory and its contents Returns a Boolean value indicating whether the specified path corresponds to an actual existing directory on disk Gets the creation date and time of a directory Gets the current working directory of the application Gets the names of subdirectories in the specified directory Returns the volume information.all members of the System. or both for the specified path . DirectoryInfo inherits from the abstract class FileSystemInfo (as does FileInfo). The Directory method returns a DirectoryInfo object that is an instance of the parent directory of the FileInfo object. so you must obtain an instance to use it. provides static methods used for manipulating files.6. or DirectoryInfo and FileInfo. The classes . it's probably easiest to use the static classes. • DirectoryInfo contains no static members. such as a Path string (see Table 10. Here's some more information about these classes: • Directory contains only static members for manipulating directories. if you expect to use members multiple times. Table 10.5. If you need only a few items of information or to perform only a few operations. However. which require an argument that is a directory. Selected File members are shown in Table 10.7. like Directory.

including both absolute and relative paths Moves a file or a directory and its contents to a specified location Sets the creation date and time for the specified file or directory Sets the application's current working directory to the specified directory Sets the date and time that the specified file or directory was last accessed Sets the date and time a directory was last written to Table 10.GetFiles Returns the names of files in the specified directory GetFileSystemEntries Returns the names of all files and subdirectories in the specified directory GetLastAccessTime GetLastWriteTime GetLogicalDrives GetParent Move SetCreationTime SetCurrentDirectory SetLastAccessTime SetLastWriteTime Returns the date and time that the specified file or directory was last accessed Returns the date and time that the specified file or directory was last written to Retrieves the names of the logical drives on the current computer (inthe form "<drive letter>:\") Retrieves the parent directory of the specified path.5: Selected DirectoryInfo Class Instance Members Member Attributes CreationTime Create CreateSubdirectory Delete Exists Extension FullName GetDirectories GetFiles What It Does Property gets or sets the FileAttributes of the current FileSystemInfo Property gets or sets the creation time of the current FileSystemInfo object Method creates a directory Method creates a subdirectory or subdirectories Method deletes a DirectoryInfo and its contents from a path Property returns a Boolean value indicating whether a DirectoryInfo instance corresponds to an actual existing directory on disk Property gets the string representing the extension part of the file Property gets the full path of the directory or file Method returns the subdirectories of the current directory Method returns an array of FileInfo objects representing the files in the current directory .

GetFileSystemInfos LastAccessTime LastWriteTime MoveTo Parent Root Method retrieves an array of FileSystemInfo objects Property gets or sets the time that the current file or directory was last accessed Property gets or sets the time when the current file or directory was last written to Method moves a DirectoryInfo instance and its contents to a new path Property gets the parent directory of a specified subdirectory Property gets the root portion of a path Table 10. providing the option to specify a new file name Opens a FileStream on the specified path (see "Streams" later in thischapter for more information about FileStreams) Opens an existing file for reading Opens an existing text file for reading .6: File Class Static Methods Method AppendText What It Does Creates a StreamWriter that appends text to an existing file (see"Streams" later in this chapter for more information about StreamWriters) Copy Create CreateText Delete Exists GetAttributes GetCreationTime GetLastAccessTime GetLastWriteTime Move Open OpenRead OpenText Copies a file Creates a file in the specified fully qualified path Creates or opens a new file for writing text Deletes the file specified by the fully qualified path (an exception is notthrown if the specified file does not exist) Determines whether the specified file exists Gets the FileAttributes of the file on the fully qualified path Returns the creation date and time of the specified file or directory Returns the date and time that the specified file or directory was last accessed Returns the date and time that the specified file or directory was last written to Moves a specified file to a new location.

7: Selected FileInfo Class Instance Members Member AppendText Attributes CopyTo Create CreateText CreationTime Delete Directory DirectoryName Exists Extension FullName LastAccessTime LastWriteTime Length MoveTo Open OpenRead OpenText OpenWrite What It Does Method creates a StreamWriter that appends text to the file Property gets or sets a FileAttributes object that represents the file's attributes Method copies an existing file to a new file Method creates a file Creates a StreamWriter that writes a new text file Property gets or sets the creation time of the current object Method permanently deletes a file Property gets an instance of the parent directory Property gets a string representing the directory's full path Property gets a Boolean value indicating whether a file exists Property gets the string representing the extension part of the file Property gets the full path of the file Property gets or sets the time that the file was last accessed Property gets or sets the time when the file was last written to Property gets the size of the current file or directory Method moves a file to a new location.only FileStream .only FileStream Method creates a StreamReader that reads from a text file Method creates a write . providing the option to specify a new file name Method opens a file with various read/write and sharing privileges Method creates a read .OpenWrite SetAttributes SetCreationTime SetLastAccessTime SetLastWriteTime Opens an existing file for writing Sets the specified FileAttributes of the file on the specified path Sets the date and time that the file was created Sets the date and time that the specified file was last accessed Sets the date and time that the specified file was last written to Table 10.

(Alternatively.2 shows the user interface for this application (searching through a subdirectory of My Documents for . I put a regular expression pattern match behind the file text entry box . the fully qualified name of the file. An array of FileInfo objects is created in the directory. so don't expect all expressions that you might enter in a Windows File Find dialog to work. A DirectoryInfo instance is created using the initial directory. recursively through the user . When the user clicks Start. Warning: This is a very simple regular expression match. and using static methods is easier if you expect to do only a few things. I don't want to belabor the you can just enter a name.selected directory and all its contents. entering *.txt will cause an exception to be thrown.' explained regular expressions. the sample application lets the user enter a starting path that is anywhere in your file system. For example. using the Environment. Let's put together a sample application that works with DirectoryInfo and FileInfo. it is largely a matter of the taste of the programmer as to which pair of classes you choose to use. an array of DirectoryInfo objects is created based on the subdirectories within the directory. a button lets you set the starting path to your My Documents folder. instantiation probably makes more sense if you will do many operations on the objects created. such as mytext. This application searches. Each directory . If a file is found that matches the file input specification.and its time of last access . indented to show its level in the hierarchy. the user enters a file name to be searched for. Figure 10. is displayed in another TextBox. The whole procedure starts over with each DirectoryInfo instance. In a little more detail.txt. along with its size and attributes. Tip: As I mentioned before. or enter a pattern using regular expression wildcards. but it bears repeating that you could replace the instance members of DirectoryInfo and FileInfo with static members of Directory and displayed in a multiline TextBox.Finding the Files in (or Under) a Directory It's really pretty simple to use these classes. to find files that match the specifications given. 'Everything Is String Manipulation. for example mytext.) Next. Because Chapter 9. and they provide a tremendous amount of power in manipulating files and directories. and each is checked for a match. to show how this can work. the application moves recursively though all directories 'below' the initially selected directory.*. Next. my preferred pair of these classes.Personal enumeration explained earlier in this chapter. However.

with the DirectoryInfo object and the pattern specification string passed as arguments to it: .. Here's what happens first. the cursor is set to an hourglass. A DirectoryInfo object is created based on the user's input: DirectoryInfo dir = new DirectoryInfo (txtStartDir. and the recursive FindFile method is called.Text). If it is.Text)..2: The application recursively moves through the directory structure.*).all files matching the specification mytext. Let's start with the first and last things that happen when the user clicks the Start button. some initial text is displayed.Exists == true). the DirectoryInfo instance is checked to see whether it really is an existing directory: if (dir. Figure 10. The Regex instance is created based on the user's file name input (as explained in Chapter 9): Regex regex = new Regex (txtFileName. Next.

.. txtResults. Within FindFile. used to control the hierarchal display of each directory: static int indent = -10. this. because it is within a finally block). The GetFiles method of the DirectoryInfo instance is used to read the files in the current directory into an array of FileInfo objects: FileInfo[] filesInDir = dir.. the indentation is incremented and information about the current DirectoryInfo instance is displayed: private void FindFile (DirectoryInfo dir. string fn) { indent += 10.. FindFile (dir. the foreach syntax is used to cycle through the array of FileInfo objects.GetFiles(). .Text == String.LastAccessTime + "\r\n".Cursor = Cursors. regex). Next.. if (txtFilesFound.Name + " " + dir.Default..Cursor = Cursors. } Before we get to the FindFile method. and if no matching files were found. Each time there is a match. Here's what happens last (and we can be sure that this will be executed last. The cursor is restored to its normal state. size.Text = "No matching files found!". and attributes are added to a TextBox for display: ..10 so that the increment that is used within the recursive method would bring it back up to 0 for the highest . an appropriate message is displayed: finally { this.WaitCursor.level directory. . note the static variable indent. indent) + dir.Empty) txtFilesFound.Text += new String (' '. I started this variable at . the fully qualified file name.

Match (fi. txtFilesFound. } } } Once the files in the current directory have been checked. The complete code for the recursive FindFile method and the Start click event is shown in Listing 10. regex).2.Text = String. .Empty){ txtFilesFound. } Finally. the GetDirectories method of the DirectoryInfo object is used to read the subdirectories of the current directory into an array of DirectoryInfo objects: DirectoryInfo[] dirs = dir.Text != String. .. System.Text += fi.Success){ if (txtFileName.Name).foreach (FileInfo fi in filesInDir){ Match match = regex..Text.Text = String. and the resulting matches displayed. try { DirectoryInfo dir = new DirectoryInfo (txtStartDir.RegularExpressions.EventArgs e) { txtResults.Text).Empty.Attributes + "\r\n". the FindFile method is called recursively: foreach (DirectoryInfo di in dirs){ FindFile(di.FullName + " " + fi. private void btnStart_Click(object sender. the indent is decremented to mark the appropriate hierarchal level: indent -= 10.GetDirectories(). after returning from the recursive call. if (match.2: Recursing through Directories and Matching a Specified File using System.Empty.IO. The array of DirectoryInfo objects is cycled through. using System.Length + " " + fi. Listing 10. For each subdirectory.

indent) + dir.Regex regex = new Regex (txtFileName.Exists == true) { txtResults. if (txtFilesFound.Text == String. } else { txtResults.Empty) txtFilesFound. this.WaitCursor.Cursor = Cursors. txtResults.Text + "\r\n".Name + " " + dir.Empty){ txtFilesFound.Message). regex).. } } catch (Exception excep) { MessageBox. FindFile (dir.Text != String. foreach (DirectoryInfo di in dirs){ FindFile(di.FullName + " " + fi. regex). if (match.GetFiles().Text = "Starting Directory: " + txtStartDir. if (dir.170) + "\r\n".Text = "No matching files found!".Text += new String (' '.Text).Match (fi. foreach (FileInfo fi in filesInDir){ Match match = regex. txtResults. } } } DirectoryInfo[] dirs = dir. } } static int indent = -10.Text += fi. FileInfo[] filesInDir = dir.Text = "Please start your search from an existing directory!".Cursor = Cursors. } .GetDirectories().Attributes + "\r\n".Text += "Searching for file: " + txtFileName. txtResults.Name).Success){ if (txtFileName.Text += new String ('-'.Default.Show (excep.Text + "\r\n". Regex regex) { indent += 10.LastAccessTime + "\r\n".Length + " " + fi. } finally { this. private void FindFile (DirectoryInfo dir.

In the due fullness of time.text files consisting of key/ value pairs named with a . } Working with Initialization Information It's often the case that a program needs initialization information. INI files are easier to use for application-global settings such as database connections (since the settings for all users can be changed simply by distributing a new INI file if the server information changes). per-local systemarrangement is not very convenient when updating hundreds of systems at once. The Registry is actually a small .for example. The data may have to do with a user preference . This kind of information is usually stored in key=value pairs. Usually.ini suffix. (You can use the technique shown in the isolated storage example in this section to parse the file. INI Files Although the classes provided under the . Isolated storage is on a per-user basis.ini and System. 'start the application with a red background. I bring this up because. It is used to be conventional to store this kind of data in private profile files (also called INI files) . you might want to name it something like myapp. it is much easier to just send out a new initialization file to all the users of your application. supported classes or no.ini were examples of public INI files. Note: Win. Best-practices use of the Registry is also per-user (generally under the HKEY_CURRENT_USER node)-although you can also store information on a "per-local system" basis in the Registry. This kind of per-user-or. For example. at best.) There's no need to name the file with a . the location of a required file.NET Framework no longer explicitly support private profile files. or the data needed to connect with a database. It was really considered bad form to save private initialization information in these public files . it's clearly easy enough to construct your own key/value initialization files to be placed in the same directory as your application.indent -= 10. and only show this year's data. this does not involve lengthy pieces of data.cfg.for example.but it could also be done. the system Registry came along.' Other pieces of initialization information may be more oriented towards the internal needs of a program . there are some cases in which INI files provide a more convenient mechanism for saving and retrieving initialization data than the system Registry or isolated storage.ini suffix (see the sidebar).

and I'll show you how to use them later in this section. It's nonetheless still common. correspond to the nodes that are the standard roots in the Windows system Registry.8: Registry Class Fields Field ClassesRoot CurrentConfig CurrentUser DynData LocalMachine Users Registry Key Equivalent HKEY_CLASSES_ROOT HKEY_CURRENT_CONFIG HKEY_CURRENT_USER HKEY_DYN_DATA HKEY_LOCAL_MACHINE HKEY_USERS Typically Used For Information about types and classes and their properties Per .of any kind .system local hardware information Per . as well as a default user configuration for new users PerformanceData HKEY_PERFORMANCE_DATA Per . Using the Registry The Registry and RegistryKey classes. used for accessing the system Registry. new thing in technology. an instance of the RegistryKey class is created by using the . I'll show you an example later in this section. such as real . shown in Table 10. In other words.NET supports a concept called isolated storage. .time information provided by virtual device drivers (VxDs) Per .8.NET Framework support the Registry. There's always a next new. are located in the Microsoft.user preference information (organized per application) Dynamic data.application performance counters In the example shown in Listing 10. The CLR implements this data compartment as a directory in the file system. Table 10. isolated storage provides a place to save initialization data. and it is generally preferred to INI files for reading and writing initialization information. to store initialization data as key/value pairs. Classes provided by the .3.within the directory.user data compartment.system local configuration information Contains a branch for each user of the local system. applications save data to a per .database. and that's certainly the case in working with initialization information. Using isolated storage. Administrators can limit the amount of isolated storage available to applications. even when using isolated storage. but it does not impose a structure as to how that data is saved. The static public fields of the Registry class. and the application is free to store whatever data it would like in files .Win32 namespace.

.. Figure 10.Text. txtValue. } If you run this code.CurrentUser field (giving the Registry key a starting value of HKEY_CURRENT_USER): RegistryKey theKey.CurrentUser. open the Registry Editor (by selecting Run from the . is called in the finally clause of the code. Next. The Close method of the RegistryKey object.SetValue(txtKey. a new subkey named SybexCSharp is created under the CurrentUser key (and assigned back to the theKey variable): theKey = theKey.Registry.Text). so that it is sure to execute: .. under HKEY_CURRENT_ USER.3: The key/value pair shown will be saved under the SybexCSharp node.Close().. theKey = Registry.3). you can enter a key pair to test that it works (Figure 10. . which closes it and flushes its contents to disk if they have been modified. The key/value pair is written under the SybexCSharp node: theKey. finally { theKey. Tip: Returning the static Registry field creates an instance of RegistryKey without having to explicitly instantiate it. To verify that this has all worked as planned.CreateSubKey("SybexCSharp").

CreateSubKey("SybexCSharp"). and make sure that the key and value have been written to the Registry (Figure 10. Find the HKEY_ CURRENT_USER node.. and 2000 (the user should be a member of the Administrator group).Text.4: Open the Registry Editor to verify that the key/value pair has been written to the Registry. Listing 10. and opening regedit in the Run dialog).Show (excep.Windows Start menu. locate SybexCSharp. it will not be able to write to the Registry. RegistryKey theKey. private void btnSave_Click(object sender. txtValue. theKey. . } catch (Exception excep) { MessageBox.Win32.CurrentUser.4).3: Saving a Key/Value Pair to the Registry using Microsoft.Message).EventArgs e) { try { theKey = Registry.Close(). NT.SetValue(txtKey. System. theKey = theKey. Figure 10.. } } . Warning: If your application is run by a user that does not have the right level of privileges under Windows XP. } finally { theKey.Text).

so it needs to be explicitly cast to string.Text != String.. if (txtValue. } finally { theKey. RegistryKey theKey. presuming you know the key.Close()..Retrieving the value. private void btnRetrieve_Click(object sender. txtValue.4: Retrieving a Value Using a Key from the Registry using Microsoft.CreateSubKey("SybexCSharp"). String. Note: The return type of the GetValue method is object. .Empty){ try { txtValue. Listing 10.Win32.CurrentUser.Text.Empty)..Text = "Could not get value!". theKey = Registry. System.CurrentUser.4. theKey = theKey. theKey = Registry.. a RegistryKey instance containing the SybexCSharp node is created: RegistryKey theKey. String. follows the same general course.GetValue(txtKey.Show (excep.Text == String.GetValue(txtKey.Text.Text = (string)theKey.Message).CreateSubKey("SybexCSharp"). If the .Text = (string)theKey.Empty. theKey = theKey.EventArgs e) { if (txtKey.Empty) txtValue.Text = String. } catch (Exception excep) { MessageBox. as shown in Listing 10. } } } First.Empty). The RegistryKey's GetValue method is used to retrieve the value: txtValue. .

IsolatedStorageFileStream is part of the System. you'll need to use the IsolatedStorageFileStream class. it will be returned in the Enter Value box: Isolated Storage In discussing isolated storage. and a message is displayed if a value cannot be obtained: finally { theKey. and StreamReader classes are discussed in the subsequent sections of this chapter.IO. the empty string). if (txtValue. you won't be able to obtain a value. or because the value is empty). You can omit the second argument.Text == String. To start with. in which case it will return null if no value is returned (either because the key doesn't exist. whereas if there is a value associated with an existing key in the specified location. which is derived from the FileStream . you'll see that if the key/value pair doesn't exist. the RegistryKey is closed. it will be a little less theoretical since you'll have already seen them in action once. so you'll need to include a using directive to both these namespaces: using System.Text = "Could not get value!".IO namespace. StreamWriter.front order is okay. and StreamWriter and StreamReader belong to the System. } If you take the sample program for a test spin.value doesn't exist.Empty) txtValue.IO. To work with isolated storage. In the finally clause. When you get to the discussion of how the Stream classes interrelate. The isolated storage example also shows one way to use StreamWriters and StreamReaders. The FileStream.IsolatedStorage namespace. .Close(). the second argument passed to GetValue is returned (in this case. I am getting a little ahead of myself. This apparent back .

.9: FileMode Enumeration Values Constant Append Create CreateNew Open How File Is Opened Opens the file if it exists and moves to the end of the file. a new file is created.IO. Next. If the file already exists. Truncate Opens an existing file and deletes the contents.WriteLine (str). it will be overwritten. isfs. The constructor is given a file name. FileMode.Text..FileMode enumeration value. Opens an existing file. with the IsolatedStorageFileStream instance as an argument.. writer.IO. Creates a new file.FileNotFoundException is thrown if the file does not exist.using System. use a StreamWriter constructor.IsolatedStorage. if it does not. use the constructor of the IsolatedStorageFileStream class to create an instance of the class: IsolatedStorageFileStream isfs.Append). Table 10. If the file already exists. Add a key/value pair to the isolated storage file using the WriteLine method of the StreamWriter: string str = txtKey. a new file is created. The values of this enumeration are shown in Table 10. If the file doesn't exist. isfs = new IsolatedStorageFileStream ("isfs.9. This is the simplest of the IsolatedStorageFileStream constructors. which can be named anything you'd like (in this case. and it also requires a System. Next. to create a StreamWriter: writer = new StreamWriter (isfs).hld).Text + "=" + txtValue.IO. An attempt to read from a file opened with Truncate throws an exception. OpenOrCreate Opens a file if it exists. Creates a new file.hld". The FileMode enumeration specifies how the operating system should open a file. A System. . an IOException is thrown.

EventArgs e) { .Flush(). } The code for saving a key/value pair to an isolated storage text file is shown in Listing 10.5. you can then use the Windows Search facility to locate the file using the name specified in the code. flush and close the StreamWriter. Listing 10. writer.. Figure 10. private void btnSave_Click(object sender.. If you run it and add a few key/value pairs. isfs.IsolatedStorage. . System. and close the IsolatedStorageFileStream: finally { writer. StreamWriter writer.IO.5: You can find where your isolated storage is by using the Windows Search facility. Figure 10.5: Saving a Key/Value Pair to a Text File in Isolated Storage using System.Close().5 shows where it turned up on my system. StreamReader reader.In the finally block. IsolatedStorageFileStream isfs. using System. along with the contents of the file with a few key/value pairs.Close().IO.

Close().null line is split. each non . and a StreamReader is created (rather than a StreamWriter): isfs = new IsolatedStorageFileStream ("isfs.Text){ txtValue.Append). string str = txtKey.Append).Text = subStrs[1]. break.try { isfs = new IsolatedStorageFileStream ("isfs.Flush().WriteLine (str). writer = new StreamWriter (isfs). The file is opened in FileMode. then the second element is the value returned: do { lineOfText = reader. Within the loop.ReadLine(). FileMode.Text + "=" + txtValue. writer.Close().while loop that runs until it hits a null line.hld". The file is read line by line in a do.Message). into an array of elements. isfs. reading the key/value pairs is essentially the same process as writing them.IndexOf('=') != -1)) { string [] subStrs = lineOfText. } } . if ((lineOfText != null) && (lineOfText. } } Once again. if (subStrs[0] == txtKey.Split('=')..Show (excep.Open). Tip: Reading a file within a loop until a null character (or line) is reached . reader = new StreamReader (isfs).is a standard technique. writer. If the first element of the array matches the key. but reversed. } catch (Exception excep) { MessageBox. using = as the delimiter.Text.Split method..Open mode (rather than FileMode. } finally { writer. using the static String.which indicates the end of the file .hld". FileMode.

System.Open).Close().IsolatedStorage.Show (excep. FileMode. Note: You should be aware that there are some limitations to this parse for key/value pairs. do { lineOfText = reader.ReadLine().6. string lineOfText = null. } } } while (lineOfText != null). . using System. the second equal sign and anything following it will not be returned.} while (lineOfText != null).IO. In addition. IsolatedStorageFileStream isfs..Empty.IndexOf('=') != -1)) { string [] subStrs = lineOfText. .EventArgs e) { try { txtValue. } finally { reader. In other words. reader = new StreamReader (isfs). } catch (Exception excep) { MessageBox.Text = subStrs[1].Text = String.hld". isfs = new IsolatedStorageFileStream ("isfs.Text){ txtValue.ToString()).6: Retrieving a Value Using a Key from a Text File in Isolated Storage using System. Listing 10. in a production situation.. private void btnRetrieve_Click(object sender. break. StreamWriter writer. if ((lineOfText != null) && (lineOfText. you should modify this code to fit your specific needs.IO. only the first occurrence of a key is matched.Split('='). StreamReader reader. if (subStrs[0] == txtKey. The complete click event procedure for retrieving a value using a key is shown in Listing 10. If the value string itself contains an equal sign.

' For more information about XML configuration files. It's easy to store information in an XML configuration file that is both human.NET. or be created in memory. Streams In his great novel The History of Henry Esmond. or . from within a running application.exe. The classes in the System.isfs. which can include key/value pairs. look up 'Configuration Files' in online help. 'Working with XML and ADO. These files are stored in the same directory as a corresponding . if (txtValue. A digital stream is an abstract concept representing flows of information.NET executable. The stream comes from somewhere. it becomes a stream. many users can be updated all at once just by distributing a new file. the Victorian author William Makepeace Thackeray wrote: What! does a stream rush out of a mountain free and pure. XML configuration files have the same advantage as INI files over mechanisms like the Registry or isolated storage: that is.NET Framework methods for writing to XML configuration files.Configuration namespace make it easy to access this data. if global changes are made to an application's startup data.a little less abstractly . if an application were named theApp.config. to roll through fair pastures. then its XML configuration file would be named theApp. However. transfer across a network. this quotation does to some degree convey the idea of stream. For example. A stream could come over the Web.flows of bytes. and have the same name as the executable with a . When a file is opened for reading or writing.Close(). A caveat is that there are no intrinsic .exe.Empty) txtValue. to feed and throw out bright tributaries.config extension added.Text == String. } } XML Configuration Files Yet another way to store initialization information is in XML configuration files.Text = "Could not get value!".and machinereadable. they can easily enough be edited by hand or by using some of the techniques described in Chapter 12. and to end in a village gutter? In our context. .

writing. How do you create a FileStream? There are two different routes.IO.Security. A stream across a network. Abstract base class that supports reading and writing bytes.NET Framework. Located in the System. An instance of the class can be created using one of the class constructors. IsolatedStorageFileStream Supports reading and writing to files in isolated storage (see example in previous section). Alternatively. random access. the most important of which is FileStream (you've already seen IsolatedStorageFileStream.) A stream that is used to link data stream to cryptographic transforma tions. and primarily used for short . these classes can be found in the System. MemoryStream NetworkStream Stream A stream whose data is available in memory. which derives from FileStream). In truth. Streams . you can use it to instantiate objects used to make specific kinds of file operations easier.10: Stream and Its Derived Classes Class BufferedStream CryptoStream Purpose A stream that adds buffering to another stream.Cryptography namespace. as you'll see later in this chapter.and not much more.Net. Various classes derive from Stream. Located in the System. support both synchronous and asynchronous action are used for reading.In the . .which. (FileStream already supports buffering. are shown in Table 10. Stream is an abstract base class representing this concept. some of the methods of the File and FileInfo classes . But once you have a FileStream.10 (unless otherwise noted. Located in the System. and the classes that derive from it.IsolatedStorage namespace. Table 10. FileStream Supports reading and writing to files.term storage. FileStream itself supports reading and writing bytes .IO namespace).such as the Open method return a FileStream object. The constructor is passed the file name and enumeration values that indicate how the file should be opened. the bulk of streaming operations involve files .Sockets namespace. Stream. and searching (called 'seeking') though a stream source. Not buffered.explaining why FileStream is the most important of the Stream classes.

• On the other side of the fence. . derived from the abstract TextReader/TextWriter pair. The relationships between objects based on the classes derived from Stream and the "Readers/ Writers" can be a bit confusing at first . make reading and writing text files a breeze: • The most commonly used of these two class pairs for text files is StreamReader/ StreamWriter.) The specific classes come generally in pairs. • The StringReader/StringWriter class pair is an alternative . in fact. use an instance of any class derived from Stream.You do this by using an instance of a FileStream to create an object of the right class for the type of file and operation you want.6 should help to make it clearer. matched up as typeReader and typeWriter: • Although it is wise .it reads from and writes to a file using the StringBuilder class as the underlying data type. many times one knows that one is operating on a text assume that a file is just plain binary. you'll find yourself using FileStream most of the time. but unless you have specific requirements for something like buffering or cryptography. (You can.when in doubt .but the diagram in Figure 10. Two different pairs of classes. which reads characters from and writes them to a stream (see the example in the next section). the BinaryReader/BinaryWriter classes extend the FileStream facilities available for working with binary files by allowing you to read and write encoded strings and primitive data types to a stream.

Figure 10. As an example.7)." Reading and Writing Text Files The simplest way to deploy a StreamWriter is to use its Write method to save a block of text to a file. I'll show you how to save the contents of a multiline TextBox to a file selected by the user.6: An object created from any class derived from Stream can be used to create a "Reader" or "Writer. 'Building a Better Windows Interface') are used to allow the user to choose a file for reading or writing. and the file in which to save the text is stored in the variable theFile: theFile = saveFileDialog1. A common dialog control is used to allow the user to select a file (Figure 10.FileName. Common dialog controls (explained in Chapter 4. Let's start with saving a block of text.including the one just saved . The contents of a text file . Similarly. the ReadToEnd method of a StreamReader can be used to retrieve a block of text from a file.can then be retrieved by the user back into the TextBox. .

Create).Write (txtToSave.Flush(). The FileStream is used to create a StreamWriter: writer = new StreamWriter(fs).7. folks! The complete click procedure that lets a user select a file and then saves the text is shown in Listing 10. FileMode. In the finally block. Note: See Table 10. The Write method of the StreamWriter is used to save the text in the multiline TextBox to the file: writer. writer.Close().9 earlier in this chapter for the meanings of the FileMode enumeration values.Close(). } That's all. within the try block. the StreamWriter and FileStream are closed: finally { writer. Next. .7: Common dialog controls are used to choose a file for reading or writing.Figure 10. a FileStream is created: fs = new FileStream(theFile.Text). fs.

Filter = "HLD Files (*.Close(). StreamWriter writer.*".Write (txtToSave.ExecutablePath. FileStream fs. With a user .7: Using a StreamWriter to Save Text using System.Show (excep. Next.IO. saveFileDialog1.selected file. } } Retrieving the text is just about as simple. } catch (Exception excep) { MessageBox.Text). try { fs = new FileStream(theFile. a StreamReader is created.Close(). saveFileDialog1.hld|All Files (*.OverwritePrompt = true.FileName. FileMode.InitialDirectory = Application.Message).FileName = "harold".Create). writer. once again a FileStream is created (this time in Open mode). System. The StreamReader's ReadToEnd method is used to assign the entire contents of the opened text file to the TextBox: theFile = openFileDialog1. saveFileDialog1.. writer = new StreamWriter(fs). private void btnSaveStream_Click(object sender.. .DefaultExt = "hld". saveFileDialog1. fs.Listing 10.EventArgs e) { string theFile. . theFile = saveFileDialog1. saveFileDialog1.hld)|*.*) | *.FileName.ShowDialog(). //custom format saveFileDialog1.Flush(). writer. } finally { writer.

hld|All Files (*. Listing 10. } In the finally block. txtToSave.FileName = "harold".Text = reader.InitialDirectory = Application.*) | *.hld)|*. the StreamReader and FileStream are closed: finally { reader.Open).8).ReadToEnd().*". . reader = new StreamReader(fs).IO.Close().try { fs = new FileStream(theFile. openFileDialog1.8: The StreamReader reads the text in the selected file into the multiline TextBox. FileMode. Figure 10..Filter = "HLD Files (*. openFileDialog1. private void btnRetrieveStr_Click(object sender. fs. the text in the file that the user selects is displayed in the TextBox (Figure 10.DefaultExt = "hld".ExecutablePath.Close(). //custom format openFileDialog1. FileStream fs.. .EventArgs e) { string theFile.8). System. } When the user runs the complete program (Listing 10. StreamReader reader. openFileDialog1.8: Using a StreamReader to Retrieve Text using System.

. common dialogs let the user select files for reading or writing.Text = reader.9 is used to demonstrate reading and writing binary files.)).9: Text is saved and retrieved using binary files.. try { fs = new FileStream(theFile. I slightly prefer including the explicit check of the dialog's result. and Listing 10. txtToSave. but I thought I'd show you both. } catch (Exception excep) { MessageBox. reader = new StreamReader(fs).7 does not. Once again.8 shows a check for the DialogResult value (if (openFileDialog1. } } } Note: Listing 10. fs.OK){ theFile = openFileDialog1. For stylistic reasons. Figure 10. } finally { reader.ReadToEnd().if (openFileDialog1.Close(). FileMode.Close().ShowDialog() == DialogResult. Binary Files The user interface shown in Figure 10.Show (excep.ToString()). .FileName.Open). There's no practical reason in these particular procedures to do it one way rather than the other.

10.9).FileName. you probably wouldn't want to do it this way.Write method allow you to easily write a variety of primitive types to a binary file. . private void btnSaveStream_Click(object sender. System.. let's have a look at what it takes to write the contents of the TextBox to a binary file using a raw FileStream (the code is shown in Listing 10.EventArgs e) { string theFile.9: Writing Text to a Binary File Using a FileStream using System. I've elected to demonstrate saving and retrieving text to and from binary files. Many of these overloads are shown in the Object Browser in Figure 10. . If you knew it was text. using System.IO.10: The overloaded variants of the BinaryWriter.. But then again. .Text... You can use variants of it to write many different kinds of simple types to binary files. since working with StreamReaders and StreamWriters is easier. Figure 10. const int SizeBuff = 2048. But before we get to the BinaryWriter. try { . Listing 10.. what if you aren't sure it's a text file? The BinaryWriter class Write method is overloaded. // Common dialog stuff theFile = saveFileDialog1. Stream theStream.Out of pure orneriness..

it is assumed that the user has selected a file to write to. the only type we get to work with is bytes.GetBytes (txtToSave. this time for the sake of variety using the File. buffer.Message). ASCIIEncoding byteConverter = new ASCIIEncoding(). and the stream closed: theStream.theStream = File. the byte array is written to the stream: theStream.OpenWrite(theFile). buffer = byteConverter.we can just directly write the string type to the file: . with the name saved in the variable theFile. theStream. buffer = byteConverter. First.Text).Write (buffer.OpenWrite(theFile). using a common dialog.Text).Length). 0. buffer.OpenWrite method: theStream = File. and the text to be saved is converted into the array using an instance of the ASCIIEncoding class (found in the System.Close().GetBytes (txtToSave. } } Note: In the code shown in Listing 10.9. So let's change our text to bytes! An array of bytes is created. ASCIIEncoding byteConverter = new ASCIIEncoding(). } catch (Exception excep) { MessageBox. 0.Text namespace): byte [] buffer = new Byte[SizeBuff]. } finally { theStream.Length). Using a FileStream.Show (excep.Close(). Finally.Write (buffer. a stream is instantiated. byte [] buffer = new Byte[SizeBuff]. This is actually considerably simpler using a BinaryWriter .

bw = new BinaryWriter (theStream). } } Listing 10.OpenWrite(theFile). catch (Exception excep) { MessageBox... // Common dialog stuff theFile = saveFileDialog1. try { theStream = File..Text).FileName. Listing 10.Text). bw. bw. . .Flush().OpenWrite(theFile).IO.Show (excep. System.. BinaryWriter bw.Write (txtToSave.10: Writing Text to a Binary File Using a BinaryWriter using System. Stream theStream.. Listing 10. .11: Reading String Data from a Binary File Using a BinaryReader using System. const int SizeBuff = 2048.theStream = File. without the common dialog portion) that uses a BinaryWriter to save text to a binary file.. bw = new BinaryWriter (theStream). private void btnSaveStream_Click(object sender. Listing 10.Message).. bw. .11 shows how you can use a BinaryReader's ReadString method to read a binary file containing string data (once again.Close(). .EventArgs e) { string theFile. the material related to the common dialog is left out of the listing for clarity).10 shows the click event (once again..Write (txtToSave.IO. theStream. } finally { bw.Close().

OpenRead(theFile).Text = br. try { theStream = File. and Converting It to String using System. .ReadString().. } } Listing 10. BinaryReader br. const int SizeBuff = 2048.Length. Listing 10. using System.Show (excep. const int SizeBuff = 2048. Stream theStream. .EventArgs e) { string theFile. // Common Dialog code omitted theFile = openFileDialog1.Stream theStream.12 shows how you could achieve the same thing using the FileStream without the BinaryReader.FileName.. // Common Dialog code omitted theFile = openFileDialog1. theStream. System. } catch (Exception excep) { MessageBox..EventArgs e) { string theFile.12: Using a FileStream to Read a Byte Array. try { theStream = File. byte [] buffer = new Byte[SizeBuff].Text. FileInfo fi = new FileInfo (theFile). System..Close(). private void btnRetrieveStr_Click(object sender. and reading the text into a byte array and then converting it to a string. ..IO. txtToSave.. . } finally { br.FileName. private void btnRetrieveStr_Click(object sender.OpenRead(theFile).Close(). br = new BinaryReader (theStream). int byteCount = (int) fi.Message).

} catch (Exception excep) { MessageBox. use the WebRequest.Create(theURL). } } Web Streams It's quite easy to read a web page as a stream using the classes in the System.Close(). txtToSave. Encoding. Specifically.") To start with.11. (This is a process sometimes called "screen scraping.GetString (buffer). } finally { theStream. Using the HttpWebResponse object.GetResponse(). byteCount). 0. and the complete click .Net namespace and a StreamReader.theStream.GetResponseStream().Create is a static method that returns different object instances depending on what is passed in. Next. Note: WebRequest.ReadToEnd() The results of reading a web page into the TextBox are shown in Figure 10.Message). a StreamReader can be created: sr = new StreamReader (wres. ASCIIEncoding byteConverter = new ASCIIEncoding(). Since the return type of the method is WebRequest.Text = byteConverter.Create method to create an HttpWebRequest object (using a URL as an argument): HttpWebRequest wreq = (HttpWebRequest) WebRequest.Default). The HttpWebRequest statement establishes a connection to a web page. This StreamReader can be used like any other StreamReader.Show (excep.Text = sr. it must be cast to HttpWebRequest. the GetResponse method of the HttpWebRequest object is used to load the connected page into an HttpWebResponse: HttpWebResponse wres = (HttpWebResponse) wreq.Read (buffer. its ReadToEnd method can be used to load the contents of the web page into a TextBox: txtResults.

Create(theURL).Text = sr.ReadToEnd(). Listing 10.. HttpWebRequest wreq. using System. .13. HttpWebResponse wres = (HttpWebResponse) wreq.Net.11: You can use a FileStream to read the contents of a web page. private void btnEngage_Click(object sender.. System.GetResponse().13: Reading a Web Page as a Stream using System. } catch (Exception excep) { MessageBox. txtResults.Show (excep.Empty.Text.Message). try { HttpWebRequest wreq = (HttpWebRequest) WebRequest. txtResults.GetResponseStream().event code for doing so is shown in Listing 10.Close().EventArgs e) { string theURL = txtURL.IO.Text. sr = new StreamReader (wres. Figure 10. StreamReader sr. using System. Encoding. . } finally { sr.Text = String.Default).

Asynchronous I/O In Chapter 3. 'Windows Uses Web Services.' I showed you the general design pattern for asynchronous invocation. a display counter is incremented. In particular. you can 'screen scrape' any web page. in the context of web services. However. While the web page is being read by the FileStream.14 shows an example of asynchronous use of a FileStream in the context of the web streaming example from the last section.12: FileStreams support asynchronous as well as . you might want to initiate asynchronous invocation of a FileStream if you had a big file to read and wanted your program to be able to do other things while reading besides twiddling its virtual thumbs. a message box is displayed.} } Warning: Obviously. I generally hate hectoring adults. Too!. Asynchronous I/O operations can be performed using any class derived from Stream.using the same design pattern explained in Chapter 3. As shown in Figure 10. please be sure that you have copyright permission to use the text and HTML of any page that you choose to retrieve this way. when the asynchronous read operation completes. using this technique. You'll probably not be surprised to learn that the FileStream class supports asynchronous invocation as well . This means that FileStream provides BeginRead and BeginWrite methods that reference an AsyncCallback object. Figure 10.12. Listing 10. These methods return an IAsyncResult object that can be queried to determine the status of the asynchronous operation.

Create(theURL). } finally { sr. private void btnAsynch_Click(object sender. } catch (Exception excep) { MessageBox. wreq = (HttpWebRequest) WebRequest.EventArgs e) { string theURL = txtURL.ToString(). System.Show (excep. . MessageBoxButtons.OK. "Asynch Demo".EndGetResponse(ar). while (!ar. try { AsyncCallback cb = new AsyncCallback (IOCallback).. lblCounter. } MessageBox. null).DoEvents(). . Listing 10.Show ("We can do other things!".Text.Close(). using System. MessageBoxIcon.. txtResults.Empty.Net.BeginGetResponse(cb. } } private void IOCallback (IAsyncResult ar){ HttpWebResponse wres = (HttpWebResponse) wreq.Text = String.IO. counter ++. long counter = 0.14: Asynchronously Using a FileStream to Read a Web Page using System. HttpWebRequest wreq. StreamReader sr.synchronous operation.IsCompleted){ Application. using System.Text.Text = counter.Message). IAsyncResult ar = wreq.Exclamation).

} Conclusion In this chapter.Close(). but the relationship of your programs to files. In addition. Next. txtResults. I showed you how to store and retrieve initialization data from the system Registry and isolated storage. Encoding. and other sources of streaming data is very important. Almost every program needs these things. . So I've included this chapter as a good first step towards connecting your projects with the rest of the world. These topics may not be the most exciting on the planet.NET Framework classes that provide information about files and the file = new StreamReader (wres. I moved on to the large topic of input and output and streams. the file system. and showed you how to read and write information to and from files and other sources. sr. Finally. use of appropriate initialization information gives you a chance to get your program going with the 'right foot forward' and give a good first impression. I showed you how to use the .GetResponseStream().Text = sr.Default).ReadToEnd().

asynchronous messaging loop. This is probably a good activity it is intended to aid or replace. you call someone and ask them if they would do something for you). Only the simplest linear. I'll start this chapter by showing you how to look at the Windows message stream. the mechanism behind Windows itself .) This leads to the supposition that programs of any complexity . (Put differently.Messaging Overview • Subclassing Windows messages • Understanding MessageQueues • Creating queues and messages • Receiving and replying to messages • Message queues and application architecture • Asynchronous peeking and receiving With a nod to the singer Madonna.with appropriate action taken when a particular message is encountered.NET. These distributed. The principal mechanism is firing events and writing the code that responds to these events. non .to . When the user moves the mouse.meaning. All the messages that are floating around a Windows application are processed in a giant switch statement. you have several tools that enable asynchronous intra . we live in a world that is both material and asynchronous.or groups of programs ought to operate asynchronously. to always execute commands in a predictable. I'll briefly show you how . (A less .program design. top . but this is not a recommended practice.) As you probably know. mechanisms are needed to keep us in touch and to facilitate teamwork. you certainly can't write good programs to help with it.optimal mechanism is to throw and respond to . waiting for one command to finish processing before embarking on the next. this causes a message to be sent.event driven program can really be expected to behave synchronously . peer . comparable to linear. as real . Successful software starts by mimicking the real .peer mechanisms are usually asynchronous in real life (for example. which is always asynchronously waiting for messages . if you don't understand the real .world processes do.and any form that you use to create a Windows application . Under Windows and .is a giant.down code execution? At the same time. top . As an illustration of how a complex program can use messaging to enable asynchronous execution. who among us would want their every activity dictated by a central mechanism.bottom fashion.

System.Message. and that it is marked protected and virtual .Forms.Forms. for example. to facilitate asynchronous program design. form size limits can be enforced using the form class MinimumSize and MaximumSize properties.Windows. it was required in order to enforce limits on form size. is different from the Message class used with message queues. Many things could not be accomplished without intercept these messages . you'll need to qualify the Message class when you use it. as shown in Figure 11. The main topic of this chapter. Note: The Message class used with Windows. although it should not be confused with deriving a class via inheritance.NET. subclassing is still an instructive technique for having a look at what is going on behind the curtains in Windows.a process called subclassing. But in some languages and in some situations. In earlier versions of Visual Basic. Using the Object Browser. and process messages going from Windows to a form or windowing control.Forms and System. . System. There should rarely be a need to use subclassing in the more powerful and rigorous Visual Basic . is how to use the classes in the System.meaning that you can override it in any class derived from Form. then.1.Windows. for example. the class from which Form is ultimately derived. observe.Windows. subclassing was needed as a 'down and dirty' tool to get around limitations of the language. and. which is invoked for every message sent by Windows to the form. both intra . discussed in other sections of this chapter. we can determine that WndProc is a member of Control.Messaging namespaces are referenced in your project.program. To avoid an ambiguous reference.Form class exposes the WndProc method.Message. The System.Messaging namespace to add messages to a queue.and inter .Messaging. to retrieve messages from a queue. so the compiler knows which one you are talking about if both the System. Observing Windows Messages Subclassing is a technique that lets you intercept. generally.

These include Msg. The Object Browser also tells us (Figure 11. (Another property.1: Using the Object Browser. Listing 11.Message class include an ID number.1 shows the code necessary to display the Windows messages sent to a form's WndProc method in the Output window. Listing 11. which is a numerical ID.of the window. .Figure 11.WndProc(ref m). which is the handle or identification . Result. and LParam and WParam.) Figure 11.2: The members of the System.1: Viewing Windows Messages in the Output Window protected override void WndProc(ref Message m){ base. which are messagespecific arguments. HWnd. one can see that the WndProc procedure has been marked with the protected and virtual attributes. is the value that will be returned to Windows.2) that the Message object that is the parameter of WndProc exposes properties.Windows .Forms.

3. and open the Output window if it isn't already visible. I can set up constants that are their equivalent: .3: Windows messages sent by the application can be viewed in the Output window. you'll want to start with a call to the base class WndProc method. you'll see many Windows messages (Figure 11.WndProc(ref m). it is easy enough. Subclassing a Message Supposing you do want to do something with a Windows message. Figure 11. let's write a very short override to WndProc that intercepts the WM_MOUSEMOVE and WM_MOUSELEAVE messages. so that the messages will be processed normally: base.) The value of the ID stored in the Msg parameter for WM_MOUSEMOVE is hexadecimal 0x200. (The first of these is sent when the mouse moves on a window.3) . (This can be seen in the Debug output shown in Figure 11. the second when the mouse cursor leaves a window.Diagnostics directive so that you can use the Debug object.Debug. one for every time you move the mouse over the form. To see how. In most cases.WriteLine(m). } Note: Be sure to include a using System.for example. If you start this code in debug mode. and WM_MOUSELEAVE is 0x2a3.) Translating these values to decimals.

Note: Since both MouseLeave and MouseMove are events supplied with a form. change back and forth to reflect the message subclassing: Listing 11. It's no problem now to write our own switch statement that intercepts each message. If you don't invoke the base class method.Default. as shown in Listing 11. you'll see that the text and cursor do. this.".Hand.Cursor = Cursors. and attempting to open the window by running the form class will throw an exception ("Error creating window handle"). switch (m. again!".Msg) { case WM_MOUSEMOVE: this.Text = "Mouse is moving. you could place code in the related event handler procedures . the code shown in Listing 11. const int WM_MOUSELEAVE = 675.implying that there is no reason you'd ever need to subclass for these two messages.2. this. } } Warning: Once again. const int WM_MOUSELEAVE = 675. If messaging works to organize a program that is as complex as Windows. const int WM_MOUSEMOVE = 512. case WM_MOUSELEAVE: this..Cursor = Cursors. so that messages could get processed normally. changing the form's text and cursor on a mouse movement (WM_MOUSEMOVE) and changing them once again when a WM_MOUSELEAVE is intercepted.WndProc(ref m).2 started by invoking the base class WndProc method. the window won't even display.. break. indeed. break.const int WM_MOUSEMOVE = 512. why not consider it as architecture for your own solutions? . If you run this code.2: Subclassing Mouse-Move and Mouse-Leave Messages protected override void WndProc(ref Message m) { base.Text = "Hello.

• Private queues Visible only on a local machine.MessageQueue Preliminaries As you'll recall from the discussion of the Queue class in Chapter 7. as you'll see.) Before we move on to making sure that you have message queuing installed. Indexers. Message queues under the Windows operating systems come in three varieties: • Public queues Allow machines on a network to send and receive messages across the network. you may need to install it).Windows. The objects on the queue are messages as implemented in the System. Formerly a separate product called MSMQ.) A good way to think of these messages is as an analog to e . first out data structure. Note: The examples in this chapter use private queues but would essentially be the same if public queues were involved. work in exactly this fashion . what is message queuing likely to be good for? . MSMQ services are not available for the Windows 98 or Windows Me family of products. let's back up for a second and ask. public (as opposed to private) message queuing cannot be run using non . in which case it is placed on the end of the queue. and auditing facilities can use these queues.Message class.Forms.although. An object can be enqueued (or pushed). and Collections. these are quite different from objects of the System. the terminology differs a bit." a queue is a first in. as implemented in the System. message queuing is now part of Windows XP and Windows 2000 (however. (You can substitute one of the GetPublicQueues methods for the GetPrivateQueuesByMachine method shown in one of the examples.Messaging. Alternatively. acknowledgment. which means it is retrieved and removed . It is additionally available to Windows NT users as part of the NT4 Options Pack.except that these messages are used for communication within or between software applications. Message queues.from the front of the queue. In addition. • System queues Primarily used for administrative purposes.Server versions of NT or 2000. An object can be dequeued (or popped). peeking at a queue means to have a look at the front object on the queue without removing it.MessageQueue class. (As noted earlier.mail (or instant messaging) that people use to communicate . "Arrays. although applications that need access to a journal queue (which saves copies of messages as they are processed).Messages class.Messaging. as I'll show you shortly.

message queues encourage self the Services shortcut. select Control Panel from the Start . One of the tasks might be to find certain text on the Internet. or you should use the Services applet to make sure that the Message Queuing service has been installed and is started (Figure 11. Administrative Tools. double . This could happen within a single large application or across multiple applications. because it is limited to servers using Microsoft operating systems. you may need to apply specific locking conditions to the resource to prevent conflicts using synchronization objects. in which myriad applications are set loose to create their own world within a world in the computer as part of the ultimate object . For more information. a vast army of 'crawlers' might go out to parse the text behind web pages . Most large networks are Unixonly or are heterogeneous that is. and Windows. and double . In this case. On a more mundane level. such as a file. using the screen scraping technique explained at the end of Chapter 10. Note: Microsoft message queuing is somewhat limited for this kind of activity. 1992). you may need to consider issues around giving multiple programs access to the same resource. (To open the Services applet in Windows XP.First.4).oriented paradigm. An application might send out messages saying what it is doing and providing interim results that could be used by other applications performing related tasks. they include servers using a variety of operating systems. one could easily see how messaging could be used as an architecture to divide up computationally intensive tasks.) . you should verify that messaging is installed on a server that you are connected to. look up 'Synchronization' in online help. It also makes conceivable a scenario like the one proposed by David Gerlernter in his seminal book about the future of computing.' A crawler might send out messages like 'I found it' or 'Completed assignment without finding requested text. Mirror Worlds (Oxford University Press. 'Working with Streams and Files. such as Linux.peer divisions of load. An application could cause each server in a large network to send out messages with traffic and status statistics.' Workflow like this facilitates peer .for example. Making Sure Message Queuing Is Installed To begin with. Note: In some cases. An automated administrative program could use these messages to route incoming traffic.

you won't see the System. In the Add or Remove Programs dialog.Figure the Add or Remove Programs link in the Control Panel. As shown in Figure 11. select Properties from the context menu. If you don't see the Message Queuing service in the Services applet. then it probably has not been installed.Messaging . click Next. To remedy this under Windows XP. To do this. select . In the Services applet. double . and the classes in that namespace will not be usable.4: You can check the Services application to make sure that Message Queuing is installed and started. and set it for Automatic startup (if the service hasn't already been configured this way). make sure that Message Queuing is selected. As a preliminary. if the Message Queuing service is not shown as running. Figure 11.Messaging namespace in the Object Browser. click the Start link to start the service. If you open a new Visual C# . and follow the instructions to complete the installation.5: The Windows Component Wizard is used to install the Message Queuing application on a system. you must add a reference to System. The Windows Components Wizard will open.5. click Add/Remove Windows the Message Queuing service.dll.NET project in the Visual Studio development environment. You can also right .

Messaging namespace will then appear in the Object browser (Figure 11. .7: With Message Queuing installed and a reference to the System.6: A reference to the selected component (System.dll and click Select. Click OK to add the reference. The classes in the System.Messaging. right .Project > Add Reference (alternatively. the System.NET tab of the Add Reference dialog. Figure 11.6). and select Add Reference from the context menu). highlight the project in Solution Explorer. Using the .Messaging) will be added to the current project. highlight System.7). Figure component added. The Add Reference dialog will open. The DLL will appear in the Selected Components list at the bottom of the Add Reference dialog (Figure 11.Messaging namespace and the Messaging classes appear in the Object Browser.

In the example toward the end of the chapter. I'll show you how to receive . by their very nature. that the Message Queue item on the components tab of the Toolbar makes it easy to set the properties of public queues using the Properties window (rather than code). then I am behaving asynchronously. From a coding standpoint.Messaging directive.asynchronously. even though the whole process of message queuing is essentially asynchronous by definition. (If I'm going about my business doing things but waiting to get a message from you that you've completed a task.Messaging namespace should include a using System.Note: Code that uses the System. . however. applications organized around message queues are. you need to know how to create or join a queue. Next. You also might want to retrieve a list of all the current queues and .or peeked at either synchronously or asynchronously. In other words. it doesn't make much difference if the message is received by the application that sent it or by another application. I'll show you how to receive messages synchronously. The first example shows an application that sends and receives messages to itself. you need to know how to push a Message object onto the queue (by sending it) and how to pop one off the front of the queue (by receiving it). In most cases. I'll leave it to your personal taste whether you prefer to set properties using code or in the Properties window. so let's get started. messages can be popped off a queue . This is a lot of programmatic ground to cover. I've omitted these directives from the program listings in the remainder of this chapter. Note: In this section. and providing a result. it is possible to add another level of asynchronism on top of this.) But as you'll see in the examples in this chapter. You should know. in fact. Working with Message Queues and Messages In order to work with MessageQueue objects.with a queue from this list selected to retrieve all the messages on one queue. asynchronous. provided you are connected to a server that supports public queues. Note: The examples in this chapter show creating and using message queues in code. In the interests of clarity.and peek .

Exists property: if (!MessageQueue. and we can instantiate it in our class .Creating or Joining a Queue Listing 11. } else MessageBox.Text != String.\Private$\" + txtQName.3 shows joining a queue . mq = new MessageQueue(qpath). 'Everything Is String Manipulation. One way or another.EventArgs e) { if (txtQName.using the name supplied by the user in a TextBox. as determined by the MessageQueue.or creating a queue if it doesn't already exist .\Private$\" (which specifies that a private queue on the current machine is being created): string qpath = @". "No path".Error).Create (qpath).Exists(qpath)) MessageQueue.Empty) { string qpath = @". If you run this code and enter a queue name (as shown in Figure 11.level variable mq: mq = new MessageQueue(qpath).8).Exists(qpath)) MessageQueue.OK. Note: For an explanation of verbatim strings.3: Creating or Joining a Queue MessageQueue mq. MessageBoxButtons. private void btnCreate_Click(object sender. MessageBoxIcon. a MessageQueue with the requisite name now exists.Show ("Please enter a Queue name!". see Chapter 9.' It's then an easy matter to create the MessageQueue if it doesn't already exist. you can use Server Explorer to .Text.Create (qpath).Text. } The name of the queue is built up by appending the name supplied by the user to the verbatim string @". Listing 11. if (!MessageQueue. System.\Private$\" + txtQName.

Clearly. The first step is to apply a formatter to the MessageQueue: mq. XMLMessageFormatter The default formatter.that is. look up "XMLMessageFormatter" in online help. Sending a Message To send a message . which implement the IMessageFormatter interface. can be applied to Messages or to a MessageQueue as whole. For further information. you want to deformat a message with the same formatter used to add it to the queue. Message Formatters Message formatters. one can do some pretty cool things with XMLMessageFormatter (for example.Messaging namespace: ActiveXMessageFormatter Used when sending or receiving COM components. the MessageQueue's Send method is used to push a message onto the queue: . it is standard practice to deserialize a message using XMLMessageFormatter into specified elements of an XML schema).is pretty easy.Formatter = new BinaryMessageFormatter(). The job of the message formatter is to produce a stream to be written to or read from a message body.verify that it has been created (I'll show you Server Explorer in just a moment). serializes the message to and from XML. BinaryMessageFormatter Used to serialize the message into a binary representation. Next.8: It' s easy to join an existing queue (or create the queue if it doesn't already exist). Figure 11. There are three formatters to choose from. supplied in the System. to push a Message object onto the queue . Although XMLMessageFormatter is a little slower than BinaryMessageFormatter.

4 shows the code for sending the relatively simple message.Send (txtMsg.Show ("Please create or select a Queue!". Note: Since the first parameter of the Send method is of type object. txtSubject.Empty){ if (mq != null){ mq. called the label. such as DigitalSignature. UseTracing.Text.Text). not limited to just text. Listing 11.EventArgs e) { if (txtMsg. private void btnSend_Click(object sender.Text = String. This includes the possibility of sending objects based on classes you have designed yourself. txtSubject. using the text string entered by the user to instantiate it (the string becomes the body of the message). has been sent along with the text of the message.Text).4: Sending a Message MessageQueue mq.Text = String.Text != String. you can enter a message and a subject line (Figure 11. txtMsg.Empty. } else MessageBox.. MessageBoxButtons. MessageBoxIcon. . Priority. } else MessageBox. } If you run the code. MessageBoxButtons.Formatter = new BinaryMessageFormatter(). Listing 11. System. In the example shown. . the example implicitly constructs a Message object. mq.Text.Error). an optional subject heading.Send (txtMsg.Empty. But if you needed to..Show ("Please enter a Message!". "No text".9).OK. "No queue".mq.OK.Error). you could explicitly instantiate a Message object using one of the Message class constructors and then set Message instance properties. txtSubject. etc. Tip: It's important to understand that you can send any object as a message. MessageBoxIcon.

Figure 11.TimeSpan instance represents an interval of time .9: When you send a message. You'll find Private Queues beneath the Message Queues node.5)).that execution will wait for the message retrieval (this is . click Send. Next.5) means five seconds . there will be a node for the newly created queue. and each message on the queue will be listed by subject label (Figure 11. Expand the node for the server hosting the message queue.Figure 11. the messages in a queue will be labeled with their subject.10). Within Private Queues.Messaging.Messaging.Message instance. Note: Receiving a message asynchronously is covered later in this chapter. use the Receive method of the MessageQueue object with a TimeSpan instance: System. The MessageQueue Receive method stores a reference to the first message on the queue in a new System. Retrieving a Message To retrieve the first message on the queue synchronously. it is placed at the end of the queue.0.Receive (new TimeSpan(0. The System. and open Server Explorer (View ? Server Explorer).0.Message msg = mq.10: If you open Server Explorer.TimeSpan(0.

If a timeout is not supplied. private void btnRetrieve_Click(object sender. MessageBoxButtons. Note: Windows message queuing is designed to automatically handle connection problems and issues of transient connectivity (such as a dial .Error). System. Listing 11.11. .Formatter = new BinaryMessageFormatter().Body. The code shown in Listing 11.Text = msg.Message msg = mq.sometimes referred to as a timeout parameter).OK.Show ("Nothing on the queue!".. as shown in Figure 11. MessageBoxIcon.5)).5 starts with a check to make sure that there are some messages on the queue in question. If you send a message to a machine .GetAllMessages().Length > 0) { mq.EventArgs e) { if (mq.up connection between two machines).Receive (new TimeSpan(0. the message will wait in a queue on the source machine until the connection is established.5: Retrieving the Message at the Head of the Queue MessageQueue mq. txtRetrieve. System.Messaging.that you are not currently connected with. } The code shown in Listing 11.5 converts the Body property of the Message instance to string and displays it.normally a server .11: The Receive method retrieves the message at the 'head' of the queue. "No messages".0. using the Length property of the array of Message instances returned by the GetAllMessages method.. Figure 11. } else MessageBox. . the execution will wait indefinitely for the message. when it will be sent.ToString().

Listing 11. } } The code in Listing 11.Items.12: You can easily retrieve all the queues on a machine. but it isn't quite sure of the name of the queue.Clear(). is shorthand for the current machine.12. Getting All Messages in a Queue It's not a whole lot harder to display all the messages on a queue selected by the user.Items.6 uses the GetPrivateQueuesByMachine method to read the existing MessageQueues into an array declared at the class level. System. private void btnGet_Click(object sender. foreach (MessageQueue m in msq) { lstQ. The argument passed to the method. msq.FormatName).Add(m. Figure 11.Getting a List of Message Queues Let's move on to a new application. most of the programmatic work involves determining which MessageQueue the user selected. As a preliminary before doing this. Perhaps this application would like to receive and reply to messages sent by the first application. Here's the property: private int m_MsgIndex = 0. It's easy to list all the MessageQueues on a machine.". I decided to set up a property at the class (or form) level to store the index in the array of the selected MessageQueue."). as shown in Figure 11.6: Retrieving All the Private Queues on a Machine MessageQueue [] msq.6. Once the MessageQueues are retrieved into the array.GetPrivateQueuesByMachine(". each instance's FormatName property is displayed in a ListBox. ". so that this could easily be used to reply to a message in the queue. as shown in Listing 11. msq = MessageQueue.EventArgs e) { lstQ. .

and populated using the GetAllMessages method of the selected MessageQueue: System.Receive method.MsgIndex = i. MessageQueue.SelectedItem) When a match is made.GetAllMessages does not delete the messages obtained. since.SelectedItem property cast to string. Properties are wonderful agents of encapsulation. we have the MessageQueue that was selected by the user and can save the index in the property created for that purpose: this. I compared the FormatName property of each MessageQueue in the msq array with the contents of the ListBox.using the Body property and the ToString method: .Message. An array of System. With the messages formerly on the queue safely stored in the messages array.level scope to accomplish the same purpose (as with msq. } } Note: I could have used a variable declared with a class . but I decided to use a property for the sake of variety.using the Label property . to determine which MessageQueue was selected: if (msq[i].FormatName == (string) lstQ.Message [] messages = msq[i].Messaging. and it is good to get in the habit of using them. it is now time to display the message subject line . unlike the MessageQueue.Message objects named messages is declared.GetAllMessages().public int MsgIndex { get { return m_MsgIndex. The messages on this queue are now eliminated using the Purge method.and its body text . } set { m_MsgIndex = value. Note: You will have to decide in the context of a particular application whether it is appropriate to purge a message queue or not. the variable holding the array of MessageQueues).

msq[i].Items. j++) { lstMessages.Message [] messages = msq[i]..EventArgs e) { lstMessages. } When the code is run.Length.Label + ": " +messages[j].7: Getting All the Messages in a Queue MessageQueue [] msq.FormatName == (string) lstQ. private void btnGetMessages_Click(object sender.Add( messages[j].Clear().Messaging.SelectedItem) { this.13).7. the subject and body of each message. System.Purge().13: You can use an array of messages to retrieve the subject and text of all messages in a queue.Items.. The complete code for retrieving all the messages in a queue is shown in Listing 11. is displayed (Figure 11. j < messages. System. msq[i]. Figure 11.ToString()). i++) { if (msq[i].MsgIndex = i.Formatter = new BinaryMessageFormatter(). Listing 11.Body. .for (int j = 0. i < msq. for (int i = 0.GetAllMessages(). .Length. separated by a colon.

j < messages.Length. . This time. } } } } private int m_MsgIndex = 0. j++) { lstMessages. however.ToString()).Label + ": " +messages[j]. public int MsgIndex { get { return Figure 11.Items.14 shows no room for the message reply subject. } } Replying to a Message In the interests of completeness. let's reply to a message selected by the user in the Get Messages ListBox.for (int j = 0. we'll automatically give it the subject 'REPLY' . instead of letting the user select a subject for the message.Add( messages[j]. Here's how we get the right MessageQueue in the msq array: MessageQueue theQ = msq[this. } set { m_MsgIndex = value.MsgIndex].Body.

ToString() + ". \"" + lstMessages. . then when you click Retrieve.\"and I say: \"" + txtReply.SelectedItem. Here's sending back the message with the REPLY subject line: theQ. if you make sure that the queue that is joined is the one that was used to display messages in Figure 11.8.Send("You said. The code for replying to the selected message is shown in Listing 11. Going back to the application that started this section.14: The message sent in the box shown is automatically labeled REPLY.14.Figure 11. "REPLY").15). the reply to the original message will be displayed (Figure 11.Text + "\"".

and then type out a response.8: Replying to a Message MessageQueue [] msq.Empty.Text + "\"".Formatter = new BinaryMessageFormatter().Text = String.mail or instant messaging and save some programming time. we could use e . . to really get power out of messaging. Please bear in mind that these applications don't do anything much. they might be performing extensive numerical calculations. System. txtReply. "REPLY"). regardless of what program sent it. or mimicking complex systems such as a tornado or the stock market. .EventArgs e) { MessageQueue theQ = msq[this.Figure 11. (If the idea were to have a human select a MessageQueue. theQ. and then select a message.Send("You said.SelectedItem.ToString() + ".MsgIndex]. } Messaging as Architecture Of course. theQ.\"and I say: \"" + txtReply. \"" + lstMessages.. In the real world.. My simple applications simulate the parts of a more complex organization that might be constructed around message queuing. private void btnReply_Click(object sender. software needs to be sending messages and responding to them.) I've put together a pair of small applications that simulate how messaging might be used as architecture to create a distributed application. crawling through text. Listing 11.15: The Receive method takes the first message off the queue.

One of my applications. The only action the user can take with respect to Colossal is to initialize it by clicking the Start button. since it sends out a message telling both nodes (itself and Colossal) to start.17). The idea is that the application that gets to the target number first sends a message out saying that it is done. Figure 11. Colossal. The initialization code.16.down) first node. the other node stops working. dubbed 'Colossal. The other application.' counts down from 100 to 1. wait for a message. serves as a kind of controller for the whole thing. since it sends the message that starts the race. as shown in Figure 11. causes Colossal to go into standby mode.' counts up from 1 to 100. is intended as the prototype for many distributed objects where something more complex than counting from 1 to 100 would be involved.up) other node. A target number (between 1 and 100) is supplied. .9. the (bottom . shown in Listing 11. When the 'done' message is received. The start message includes the target number.16: The initial program serves as a kind of controller. and display a Ready status indication (Figure 11. which I've called 'Gigantic. the (top . Gigantic.

System. string qpath = @".Text = String.Purge().\Private$\Race".Create (qpath). mq. . private void btnStartMe_Click(object sender. mq = new MessageQueue(qpath).PeekCompleted += new PeekCompletedEventHandler(MyPeekCompleted).Exists(qpath)) MessageQueue. so I named the message queue Race.Figure 11.EventArgs e) { lblResults. mq.Formatter = new BinaryMessageFormatter(). it signals that it is asynchronously waiting in standby mode for the start message.17: After the other program has been initialized.9: Initializing a 'Node' and Asynchronously Waiting to Peek MessageQueue mq. lblResults. The code shown in Listing 11. if (!MessageQueue.BeginPeek().9 uses the familiar syntax to create or join that queue. // Can do stuff here while waiting for asynch event to complete } I picture the competition between Gigantic counting down and Colossal counting up as a kind of race.Text = "Status: Ready". mq.Empty. mq. Listing 11.

and the BeginPeek method is invoked to start an asynchronous peek operation with no timeout: mq. If this asynchronous code looks familiar. mq. which gets invoked when a peek completes.Messaging..Note: The MessageQueue also is purged so that any old messages that may be sitting in it don't lead to erroneous results. return.PeekCompleted += new PeekCompletedEventHandler(MyPeekCompleted). Next." The code in the MyPeekCompleted event handler.Body)). } } public void CountUp (int target){ this. .Label == "START"){ CountUp (Convert. if (target > 100) target = 100.10: Responding to the 'Peeked' Message MessageQueue mq. Too!.ToInt32(msg. .Message msg = mq. if (target < 0) target = 0. the event handler for the asynchronous MyPeekCompleted event procedure is added. is really pretty simple (see Listing 11. public void MyPeekCompleted (Object source.. 'Windows Uses Web Services. PeekCompletedEventArgs ar){ System. } if (msg.EndPeek (ar..' and to perform FileStream operations in Chapter 10. it should: it is based on the same asynchronous design pattern shown used with web services in Chapter 3.AsyncResult). if (msg.Text.10)..BeginPeek(). "Working with Streams and Files.Text = "START: " + this.Label == "DONE"){ return. Listing 11.lblResults. .

lblResults.level variable). } i++.. If the message is labeled "START". a DONE message is sent: mq. "DONE"). .Label == "START"){ CountUp (Convert.Send (this.. .AsyncResult).Message msg = mq. break.lblResults. . when the target is reached.EndPeek (ar. Note: that. } return.. with the AsyncResult object passed into the event handler: System. then the count .ToInt32(msg.g.Body)). The source parameter of the event procedure is the object that fired the event . "DONE").Message object is created using the mq EndPeek event. then work ceases.ToString().Text. } A System. if (i == target){ mq. If the message is labeled "DONE". in this code.up method is called. However the MessageQueue is obtained. In the CountUp method. its purpose is to use its EndPeek method to obtain a look at the message at the front of the queue.e.Text += " " + i = 0.Messaging. the MessageQueue causing it . I've used the fact that I know what MessageQueue is involved (and it is available as a class . PeekCompletedEventArgs ar){ MessageQueue mq = (MessageQueue) source.Text += " Status: Not Ready". using the number passed in the body of the message: if ( source can be cast to MessageQueue and used like so: public void MyPeekCompleted (Object source. while (i <= target){ this... this.Text.Send (this.Messaging.

except that it also sends a START message using the number input in a TextBox: mq. // Can do stuff here while waiting for asynch event to complete } Gigantic responds to messages in the MyReceiveCompleted event handler in a similar way to the Colossal node (see Listing 11.Empty.Formatter = new BinaryMessageFormatter(). private void btnStart_Click(object sender. mq.12).\Private$\Race".11: Starting the 'Race' and Waiting for an Asynchronous Receive MessageQueue mq.EventArgs e) { lblResults. Moving back to the Gigantic controller node.BeginReceive(). string qpath = @".and the status message is put back to 'Not Ready': this. mq = new MessageQueue(qpath). "START").lblResults.Text. a display is created that uses the text of the message to display the "winner. the CountDown method is called.Text. mq. you can see from Listing 11. . the difference being that Receive will delete the message from the queue. In addition." the queue is purged.Send (txtNum. if (!MessageQueue. When the START message is received.Create (qpath). System.Exists(qpath)) MessageQueue. and Peek won't.11 that its initialization process is pretty much the same as a normal node. When a DONE message is received.Send (txtNum. the controller node is asynchronously waiting for a Receive (rather than a Peek). mq. "START"). Listing 11.Text += " Status: Not Ready". and work stops.ReceiveCompleted += new ReceiveCompletedEventHandler(MyReceiveCompleted).Text = String. mq.Purge(). mq.

as the originator of the START message. break.Text.ToString() + " is done first. if (target < 0) target = 0. .Listing 11.because Gigantic. } } Note: I've added a short delay within the CountDown method by putting the thread to sleep for 0. if (target > 100) target = 100. // Delay it a little.Messaging.System. return.Label == "START"){ CountDown (Convert.12: Responding to the Received Messages MessageQueue mq. mq.Send (this. a DONE message is sent. ReceiveCompletedEventArgs ar){ System. } i--.lblResults.Text. when the target is reached..lblResults. } if (msg. Within the CountDown method.Message msg = mq.Body)).. seemed to have an unfair head start. since it gets the message first System.AsyncResult). } } public void CountDown (int target){ this. public void MyReceiveCompleted (Object source.Text += " " + i.ToInt32(msg.ToString(). if (msg.Threading. mq.EndReceive (ar.2 of a second .Text = "START: " + this. while (i >= target){ this.Thread.BeginReceive().Sleep(200) .lblResults. "DONE").Thread.Sleep(200).Label == "DONE"){ this.". In addition. the . int i = 100.Purge().Text += " " + msg. if (i == target){ mq.Threading.Body.

When its display indicates that it is ready. as shown in Figure 11. you should plan for this and use a system that relies on acknowledgments and redundant messaging. or even in what order nodes will receive a particular message. (This may depend to some degree on network loads.Text. . it is possible that messages may get dropped or lost. Messaging Architectures and Non . mq. Figure 11.BeginReceive(). the first DONE message is displayed. There's really no way to say exactly when a given node will receive a given message.determinism.BeginReceive method is invoked another time . In addition. enter a number in Gigantic and click Start.18. Click Colossal's Start button to initialize it. In a rigorous architecture that depends on message queues. "DONE").so that the DONE message can be responded to: if (i == target){ mq. just like in real life. } To experiment with Gigantic and Colossal.Determinism You should be aware that application architectures that use message queues have some issues with non . run both programs. The display will indicate which node completed first.18: Each program sends a message when it is done with its task. break.) So your applications should not depend on the timing or order of message delivery.Send (this.

Conclusion Although the logic of applications built around sending and receiving messages can be confusing. message queues and messaging do not demand much in the way of programmatic infrastructure. One can easily imagine scenarios in which applications are objects that interoperate solely using a messaging infrastructure.peer applications.' explores the exciting and important world of XML .to .and shows you how to effectively use data with your applications.NET. Chapter 12. 'Working with XML and ADO. This chapter touched very briefly on messages formatted using XML. The technology creates powerful opportunities for distributed and peer . . using .NET.

NET Framework. and much more.NET.NET programming structure finds XML the best tool to use for application integration.NET languages. reading and writing to files as explained in Chapter 10. In the long run. configuration. why not use it in your own applications as well? Note: For more information about many of the ways XML is used within the .NET Framework. look up “XML in Visual Studio” in online help. the developer has no great need to be concerned with these uses of XML—you certainly don’t need to know how to program XML to run the Visual Studio IDE—except that if the .NET Framework and development environment.centric viewpoint whenever this is a plausible approach. In large part. This chapter provides an overview of some of the ways you can work with XML using C# . “Working with Streams and Files”—for many developers today.related capabilities are built into the . By starting the chapter in this book that treats data with a discussion of programs are designed to query databases to populate the . the great majority of real . .NET Overview • Understanding and serializing XML • The XmlSchema class and XSD schemas • XmlTextReader and XmlTextWriter classes • The XML Document Object Model (DOM) • XSL transformations (XSLT) • Understanding ADO. In effect.Working with XML and ADO. and Visual Studio rely on XML as the underlying facilitator of interoperability. While there are a variety of ways you could go about doing this—for example.NET. input/output essentially means working with a database. There are literally hundreds of classes you can use to generate and manipulate XML in C# . the .NET and databases • Connection strings • Managed providers and data components • Working with DataSets Under the hood. I am emphasizing the fact that in today’s world it is an intelligent move to regard data from an XML . A great deal of XML and XML . you can’t do anything very sophisticated or useful without the ability to save and retrieve data between program sessions.

technologies. and last names. so Gradgrind turns to one of his pet students. “Your definition of a horse.In other words. such as <h1></h1>. indeed. the meaning of an XML tag is not fixed for all users the way an HTML tag is. it can be read by both humans and machines. HTML and XML are both markup languages. In addition. In other words. XML is deceptively simple. HTML tags are fixed in nature (at least in each version of HTML) and used to describe the elements that make up an HTML page.programs’ objects. HTML elements are usually visual. XML tags are custom in nature and are used to describe data. For example.NET. That’s about where the similarity ends. is used to describe the appearance of the contents within the tag. meaning that they consist of tags that describe content. and constructing XML documents and schemas is easy. and in my XML the <name> tag might mean first.” to define a horse. a boy named Bitzer: “Bitzer. and languages that have emerged in the past few years. and a man of “facts and calculations”— asks his student Sissy Jupe.named teacher Thomas Gradgrind—only interested in realities. and how to use XML to facilitate interoperability. users need to be able to query stored data and to update it. middle. The chapter’s primary focus is the tools available to help you create the database connectivity layer in . a connectivity layer usually is interposed between an application and a database. in your XML a <name> tag might identify a first name. whom he refers to as “girl number twenty. You can invent your own XML tags and use them to mark data as you’d like. On the other hand. Hard Times. Understanding XML Of all the tools. Programs that perform jobs such as managing accounts receivable or hotel reservations must also be able to save their work and pick up where they left off. For example. an HTML tag. In contrast. is unable to produce a definition. This simplicity belies a great deal of power and sidesteps the occasional complexity of using XML.) The meaning of the <h1> tag is fixed.” . As you probably know. it would take at least an entire book to do justice to the topic. It’s easy to understand. As a general matter of contemporary application architecture. put on the spot. and it means the same thing to everybody. XML tags can be used for anything that you might logically use when structuring data. none has had a greater impact on the interoperability of applications and data than XML (short for Extensible Markup Language). the aptly .” said Thomas Gradgrind. an XML tag such as <phone_num></phone_num> identifies the contents as a phone number. This chapter is not concerned with programming databases—and. in the last novel that Charles Dickens completed. Sissy. (The <h1> tag means that the tag content is a level 1 heading.

” said Mr Gradgrind. Graminivorous. Age known by marks in mouth. it might begin something like this: <?xml version="1. “You know what a horse is. Forty teeth. So a more realistic use of XML in connection with a horse would not be a description of where the horse fits into a Linnaean genus (which is essentially what Bitzer’s description is about: what kind of animal is this?) but rather one that has to do with horse transactions and affairs for specific .four grinders. and twelve incisive.“Quadruped. four eye .0"?> <horse> <legs> Quadruped </legs> <diet> Graminivorous </diet> <teeth> <totalnumber> 40 </totalnumber> <grinders> 24 </grinders> <eye-teeth> 4 </eye-teeth> <incisive> 12 </incisive> </teeth> . If rendered into XML.” Thus (and much more) Bitzer. Hoofs hard. but requiring to be shod with iron. sheds hoofs. </horse> The point of this XML description of a horse is that almost anything can be described using XML. in marshy countries. However. too.” Bitzer’s definition of a horse is actually a kind of pidgin XML. in the real world there is little use for data description—such as XML— without the ability to communicate.. Sheds coat in the spring. namely twenty .. “Now girl number twenty.teeth.

SOAP is an open .XML structures specific to that community. These structures.NET by XSD.neutral and not controlled by any one company.1. Table 12. it is vendor .Serialization Contains the classes used to serialize and deserialize XML and XML schemas to and from SOAP (Simple Object Access Protocol). or Document Type Definition. are both older schema definition formats. and the XML classes in the . <cost>. to make sure that the elements in the documents comply with the schema.NET Framework. System.standard mechanism for wrapping XML and other content so that it can be transmitted over while we’re at it.1: Namespaces Related to XML Namespace System. (DTD. why not a <teeth> section?) Business communities create meta . This XML description might include elements such as <name>. make it easy to create XML schemas and to validate XML documents—which means. As long as you mark your data as elements following a specific schema. Contains the XML classes that provide support for XML Schemas Definition (XSD) schemas. a proprietary Microsoft schema specification. schemas are themselves written in XML and saved as XSD files. <weight>.Xml. and so on.Xml System. SOAP is used as a mechanism for invoking methods on a remote server using XML documents.Xml.Schema Description Provides primary support for a variety of XML functions. Whether specified by an industry group or created on an ad hoc basis. <sire>. and XDR. called schemas—in the same spirit that the tabular structure of relational databases are also called schemas (and convertible to and from database schemas)—are used to standardize XML communications. the tools built into the development environment. .horses.) An example of an XSD schema is shown later in this chapter in Listing 12. <dame>.w3c.1. all participants will know what you are talking about. (And sure. Note: XSD is a standard specified by the World Wide Web Consortium (W3C. As I’ll show you in this chapter. replaced in . XML Namespaces Some of the most important namespaces that contain classes related to XML development are shown in Table 12. In keeping with the role of the W3C. www.

Essentially.Xml.Xsl Provides the tools needed to work with XSLT (Extensible Stylesheet Language Transformations).1: You can create an XML schema by adding an XML Schema module to your project. and click Open. The XML Schema Designer will open. .NET Schema Designer.2. One use of XSLT is to trans form XML into HTML so that it can be rendered in a web browser.System. Creating an XML Schema To create an XML schema using the Visual Studio . as shown in Figure 12. XPath (XML Path Language) enables you to easily write queries that retrieve partic ular subsets of XML data. System. you can also see the templates you would select to open an XML Designer (XML File) and an XSLT Designer (XSLT File). as shown in Figure 12. as explained later in this chapter. Figure 12.XPath Provides an XPath parser and evaluation engine. choose Project > Add New Item from the Project menu.1.Xml. select XML Schema.1. Note: In Figure 12. When the Add New Item dialog opens. The designer is empty when it first opens. XSLT lets you create tem plates that can be used to manipulate XML documents into other formats and/or to tweak theXML.

The XML Schema tab of the Toolbox. with the XML Schema Designer open. The techniques. • Dragging . choosing Add from the context menu. there are many ways you can go about adding elements.and . .dropping any of the items from the XML Schema tab of the Toolbox to the designer.2: The XML Designer is empty when it opens.clicking the surface of the the designer and choose Add. and other items to the schema.3. At this point. include: • Right .Figure 12. and selecting a new item from the submenu. Figure 12. will be available when the XML Designer is open. which is shown in Figure 12. attributes. some of which will be covered later in this chapter. right . To add elements and attributes to your schema.3: You can add an item to a schema using the components on the XML Schema tab of the Toolbox.

Attributes can be global.2 shows the meanings of the items that appear on the XML Designer context menu and on the XML Schema tab of the Toolbox. or added to groups.approved XSD schemas within the . The schema will be generated automatically based on the database table elements. The XmlSchema class can also be used to validate the syntactic and semantic structure of a programmatically built schema. • Using the XML Designer to generate a schema based on specific XML in the designer. discussed later in this chapter. the SOM implements W3C . added to other elements. objects of the XmlSchema class are each elements in a schema. or used to construct complexTypes.• Using the Server Explorer to drill down into a database and its tables. you can create a schema programmatically using the Items collection of the XmlSchema class. • Clicking the XML tab of the designer. and editing the XML that constitutes the schema by XMLSchema. as I’ll show you shortly. attribute .NET Framework. For example. which associates a name with a type and is associated with an element or complexType. You can also have a look at the information published by the W3C regarding XML schemas at www. which are collectively called the Schema Object Model (SOM). Table 12. The element can be global. The items in the XML Schema tab—and the XML Designer Add context menu—correspond to the standard elements of a XSD schema as approved by the World Wide Web Consortium (W3C). Note: In addition. can be used to edit XML documents themselves. Table 12. You can then simply drag and drop a table onto the Schema Designer. added to elements.XML.2: XSD Schema Items Toolbox Item element What It Does Creates an element. Note: You can also learn more about XSD schemas by looking up the topics “XML Schema Reference” and “XML Schema Elements” in online help. Creates an attribute. Put differently. added to groups. which associates a name with a type.Schema namespace.w3. The SOM can be used to programmatically create and edit schemas in much the same way that the Document Object Model (DOM). They also correspond to classes and members within the System.

attribute groups. from a specified namespace) that can be added to elements. or groups. facet key Sets limits on the type of content a simpleType can contain (for an example using facets. Launches the Edit Relation dialog box. Creates an any element (meaning any element. or complexTypes. which is used to define relationships between elements. or used in the construct of complexTypes. added to elements. anys. or sequence of attribute elements. or sequence of elements. attributeGroups. which is used to create keys when added to an element. complexTypes. from a specified namespace) that can be added to elements.4. see “Create It in Code” later in this chapter). attributes. Launches the Edit Key dialog box. complexType simpleType group any anyAttribute Creates a complexType to which you can add elements. or complex types. the schema appears in the designer in tabular form (can we all say together. Creates groups that can be global. elements. Creates a simpleType to which you can add facets. added to other groups. Creates an anyAttribute element (meaning any attribute element. “just like a database schema”?) as you can see in Figure 12. . Relation Whichever method you use with the Schema Designer to create a schema. and anyAttributes. Keys are the primary fields that tie relations together (see “Database Basics” later in this chapter).attributeGroup Creates an attributeGroup (a group of attributes) that can be global.

and as many or few subparts as one would like. a description. Here’s how this looks as an XML schema with the xs schema item prefix omitted for clarity: <?xml version="1.0" encoding="utf-8" ?> <Product ProdId="123" ProdType="Candy"> <Name>Purple Wheeler</Name> <Desc>Tastes like grape</Desc> <SubPart>Wheel</SubPart> <SubPart>Purple</SubPart> </Product> To enforce the conditions I’d like for the XML. with one of them taking values from an enumeration. or “Gadget”. context menu. with zero to many occurrences of the SubPart element. each product should have a name.w3. In addition. this is likely to be the way schemas are created in the real world). Each product must have an integer product identification number and a product type chosen from the enumeration “Candy”. let’s define the schema manually.4: When you add items from the Toolbox. the schema appears in tabular form in the designer. which is an element that is a complex type.0" ?> <schema xmlns="http://www. In addition. Here’s an XML rendering of this element for a candy named a “Purple Wheeler”: <?xml version="1. Programmatically Creating and Validating a Schema Before I show you how to use the XmlSchema class to create and validate a"> <element name="Product"> . To put the cart even further before the horse. I must create a sequence of simple elements falling within my product element. let’s first create some XML that we’d like to be the basis for the schema.Figure 12. or Server Explorer. One If by Hand My XML excerpt describes a product. and describe how we’d like the XML to work (in fact. “Toy”. two required attributes must be specified.

The targetNamespace declaration names the schema and provides a URI as its default namespace (the same purpose is also achieved using xmlns and xmlns:mstns declarations). Listing 12.xsd" xmlns:xs=""> <xs:element name="Product"> .<complexType> <sequence> <element name="Name" type="string" /> <element name="Desc" type="string" /> <element name="SubPart" minOccurs="0" maxOccurs="unbounded" /> </sequence> <attribute name="ProdId" use="required" type="integer" /> <attribute name="ProdType" use="required" > <simpleType> <restriction base = "string"> <enumeration value="Candy" /> <enumeration value="Toy" /> <enumeration value="Gadget" /> </restriction> </simpleType> </attribute> </complexType> </element> </schema> Listing 12.w3.1: An XSD XML Schema <?xml version=" and that the xs prefix will be used before elements. you’ll probably find that some additional attribute declarations. many of them Microsoft . Note: The xs:schema declaration says that the text represents a" declaration says that all tags should be interpreted according to the W3C defaults.0" ?> <xs:schema targetNamespace="http://www.w3.specific. The xmlns:xs="http://www. If you create a schema within Visual Studio using one of the tools available (such as the Create Schema menu item available from the XML Designer).1 shows the same XML schema rendered a little more formally. have been added to the schema tag. we can now enter it in an XML Designer and connect it to its intended schema: <?xml version="1.<xs:complexType> <xs:sequence> <xs:element name="Name" type="xs:string" /> <xs:element name="Desc" type="xs:string" /> <xs:element name="SubPart" minOccurs="0" maxOccurs="unbounded" type="xs:string" /> </xs:sequence> <xs:attribute name="ProdId" use="required" type="xs:integer" /> <xs:attribute name="ProdType" use="required"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="Candy" /> <xs:enumeration value="Toy" /> <xs:enumeration value="Gagdet" /> </xs:restriction> </xs:simpleType> </xs:attribute> </xs:complexType> </xs:element> </xs:schema> Next.0" encoding="utf-8" ?> <Product xsi:ProdId="123" xsi:ProdType="Candy" xmlns="http://www.xsd" xmlns:xsi="http://www. Going back to our original XML fragment.xsd"> <Name>Purple Wheeler</Name> <Desc>Tastes like grape</Desc> <SubPart>Wheel</SubPart> . then the schema is “well . if the XML schema shown in Listing 12. If there are no validation errors.1 is copied into an XML Schema Designer in Visual a closing bracket—validation will tell you. it can be validated by giving the designer the focus and selecting Schema ¨ Validate.formed” and usable. If you’ve left something off—for instance.

tempuri. Note: that connecting XML and target schemas within Visual Studio that have not been created using auto . you may need to use the Properties window to point an XML Designer at its target schema. Create It in Code The XmlSchema and related classes are used to construct and validate an XSD schema programmatically. Let’s try this out in the example. To see how this works.Schema namespace.formed XSD schema and linking XML to it is that the schema can enforce conditions on the "Game".xsd"). In the XML Designer. select Project > Add New Item.<SubPart>Purple</SubPart> </Product> Note: To open a new.Xml. Note: If the XSD schema isn’t qualified by a namespace. empty XML Designer in Visual Studio.5: An XSD schema is used to enforce typing and conditions within an XML document. The whole point of creating a well . you can link an XML document to it using a noNameSpaceSchema declaration. Figure 12.5. XmlSchema is a member of the System. and then select XML File in the Add New Item dialog. let’s construct the same schema (for the Product defined earlier in this chapter) . In addition to the internal namespace declaration (xmlns="http://www. as shown in Figure 12. If you now try to validate the XML against the schema (by selecting XML > Validate XML Data). change the value of the ProdType enumeration to a value not included in the enumeration—for example.generation tools can be a little tricky. you’ll get an error message.

SchemaType = ct. no human being is available to build the schema.Items. Often. As you’ll recall. objects. or many times. establish the Product element as a complex type that includes a sequence: XmlSchemaComplexType ct = new XmlSchemaComplexType(). eName.Particle = more time using code mechanisms. create a new schema and the Product schema element: XmlSchema schema = new XmlSchema().SchemaTypeName = new XmlQualifiedName("string". add it to the sequence by adding it to the schema sequence Items collection: XmlSchemaElement eName = new XmlSchemaElement(). schema. eProduct.Items. you’ll want to know the classes. eName. XmlSchemaElement eProduct = new XmlSchemaElement(). This is coded using the element’s MinOccurs and MaxOccursString properties: .Name = "Product". the SubPart element can occur no times.Name = "Name".Add (eName). For each simple element in the schema sequence. ct. ss.. XmlSchemaSequence ss = new XmlSchemaSequence(). But if you need to work with XML. "http://www. in the sequence. considering the excellent tools available in Visual Studio for creating schemas without programming."). and relationships involved in a programmatic construction of a schema. First. Note: The code for creating the schema may seem like a great deal of trouble. eProduct.w3. .Add (eProduct).

f3. aProdId. aProdId.Value = "Candy". "").w3. "http://www.Facets.MinOccurs = 0. res.w3."). XmlSchemaEnumerationFacet f1 = new XmlSchemaEnumerationFacet().Add(aProdId).Name = "SubPart".Required.Facets. XmlSchemaEnumerationFacet f2 = new XmlSchemaEnumerationFacet().Add(f1).XmlSchemaElement eSubPart = new XmlSchemaElement(). ss.Value = "Gadget".w3. The ProdType attribute is a little more complicated.Attributes.SchemaTypeName = new XmlQualifiedName("string".SchemaType = st. eSubPart.Items.Add(f2). f2. st. res. . the attributes are added to the complex type’s Attributes collection: XmlSchemaAttribute aProdId = new XmlSchemaAttribute(). res.Use = XmlSchemaUse. "http://www.Name = "ProdId".org/2001/XMLSchema"). aProdType. Next. eSubPart. eSubPart. XmlSchemaSimpleTypeRestriction res = new XmlSchemaSimpleTypeRestriction().MaxOccursString = "unbounded".Value = "Toy". f1.Content = res.Add (eSubPart). ct. aProdId.BaseTypeName = new XmlQualifiedName ("string". XmlSchemaEnumerationFacet f3 = new XmlSchemaEnumerationFacet(). since it uses a type restriction and facets on a simple type to achieve its goal of restricting user choice to an enumeration: XmlSchemaSimpleType st = new XmlSchemaSimpleType().SchemaTypeName = new XmlQualifiedName("integer".

ReadToEnd()..Facets.Attributes.Indented. FileAccess.res. I used a FileStream along with an XMLTextWriter to write the schema to a file: file = new FileStream("Product.".Text = reader. schema.ReadWrite)..Compile(new ValidationEventHandler(SOMHandler)). the code after the Compile method is invoked displays a "validates without problems" message. schema.xsd".6).Formatting = Formatting.Add(aProdType). if (txtValidate.Text == "") txtValidate. If you run the program. the schema will be displayed (Figure 12. .Text += e. } Note: If no validation errors are reported. txtSchema. FileMode. assigning it a callback method for reporting any validation errors: .Open.Create. the remaining step is to use the XmlSchema’s Compile method. With the schema elements complete. xwriter.xsd".Write (xwriter).ReadWrite). ct.Add(f3)..2. } private void SOMHandler(object sender.. FileAccess. FileMode. new UTF8Encoding()). reader = new StreamReader(nfile). I chose to read the file as plain text back into a multiline TextBox: nfile = new FileStream("Product. The complete code for creating and validating the schema is shown in Listing 12.Message. ValidationEventArgs e){ txtValidate.Text = "Schema validates without problems. XmlTextWriter xwriter = new XmlTextWriter(file. .

eBogus. but it does not cause a C# syntax error because the syntaxes of the statements creating the element are legal: XmlSchemaElement eBogus = new XmlSchemaElement(). ss. It’s worth experimenting to see what will happen if you add some errors into the schema. For example.Items. the following “bogus” element type is not “really” part of the W3C schema specification.Name = "Bogus". you’ll get an appropriate error message (Figure 12.Figure 12.7). Doing this involves walking a fine line. because your “error” has to pass syntax compilation. If you add the “bogus” element type to the").Add (eBogus).6: The XmlSchema class is used to create a Schema Object Model (SOM) schema in code. . eBogus. which not just any old error will do.w3.SchemaTypeName = new XmlQualifiedName("bogus". "http://www.

it’s a good idea to copy the schema into the Schema Designer. Note: If you find yourself struggling with schema validation issues.Name = "Product".IO. System. using System.Figure 12. FileStream file. private void btnCreate_Click(object sender. try{ XmlSchema schema = new XmlSchema(). using System.Schema. XmlSchemaElement eProduct = new XmlSchemaElement(). .7: A schema validation error is thrown because “bogus” is not a recognized element type. . eProduct.Text. StreamReader reader. which has much more helpful validation error messages (including line and character numbers) than the XmlSchema.WaitCursor.Xml.Compile method. using System.Xml. Listing 12. FileStream nfile..EventArgs e) { Cursor = Cursors.2: Creating and Validating the Schema in Code using System..

w3. XmlSchemaElement eName = new XmlSchemaElement(). "http://www. eSubPart. "http://www. XmlSchemaSimpleTypeRestriction res = new XmlSchemaSimpleTypeRestriction(). XmlSchemaSequence ss = new XmlSchemaSequence().org/2001/XMLSchema").Name = "SubPart". res. .Name = "Name".Name = "ProdType".").Add (eName).MinOccurs = 0. ss.Add (eSubPart). XmlSchemaSimpleType st = new XmlSchemaSimpleType().org/2001/XMLSchema"). eProduct.Items.SchemaTypeName = new XmlQualifiedName("string". aProdId.Use = XmlSchemaUse. ct.BaseTypeName = new XmlQualifiedName ("string".SchemaType = st.SchemaTypeName = new XmlQualifiedName("integer" .Required. aProdType.Items.Required.SchemaTypeName = new XmlQualifiedName("string".Items. aProdType.Use = XmlSchemaUse. XmlSchemaComplexType ct = new XmlSchemaComplexType(). ss. "http://www. eSubPart.schema. XmlSchemaAttribute aProdType = new XmlSchemaAttribute(). aProdId.SchemaType = ct. eName.Name = "Desc".org/2001/XMLSchema").Add (eDesc).w3. aProdType. eSubPart. XmlSchemaAttribute aProdId = new XmlSchemaAttribute().w3. eDesc. eName. XmlSchemaElement eDesc = new XmlSchemaElement().Name = "ProdId".SchemaTypeName = new XmlQualifiedName("string". eDesc.MaxOccursString = "unbounded".Attributes.Add (eProduct). eSubPart. ss. "http://www.Add(aProdId). XmlSchemaElement eSubPart = new XmlSchemaElement().Particle = ss.Items. ct.

Add(f1). schema. xwriter.Compile(new ValidationEventHandler(SOMHandler)). schema.Text = reader. XmlSchemaEnumerationFacet f2 = new XmlSchemaEnumerationFacet().ReadWrite). ct.Close().Attributes. file = new FileStream("Product. txtSchema. xwriter. } finally { file. if (txtValidate.Facets.Text == "") txtValidate.Facets.Value = "Candy".Close().ReadToEnd().w3.Content = res. f3. reader = new StreamReader(nfile). reader.xsd". Cursor = Cursors. XmlSchemaEnumerationFacet f1 = new XmlSchemaEnumerationFacet()."http://www. nfile = new FileStream("Product. XmlSchemaEnumerationFacet f3 = new XmlSchemaEnumerationFacet().Add(f3). res.Indented.Close(). } . ValidationEventArgs e){ txtValidate.ReadWrite).Message.Value = "Toy". XmlTextWriter xwriter = new XmlTextWriter(file.Value = "Gadget".Create. nfile.". res. FileAccess. new UTF8Encoding()). f2. FileAccess.Default. FileMode.Write (xwriter).Text += e. } } private void SOMHandler(object sender.Facets.Add(f2). f1. res.xsd".Text = "Schema validates without problems.Close().Add(aProdType)."). FileMode.Formatting = Formatting.Open.

.Text). That is normally. the point of a schema.xml". otherwise text may not get written from the buffer to the file. FileAccess. after all.Element..Close(). If you flush and close the StreamWriter within the finally clause of a try.finally construct. Note: As usual when writing to a file. FileMode. The file containing the Product.. Next. XmlValidatingReader vr = new XmlValidatingReader (nfs. be sure to flush the StreamWriter when creating file. fs.Flush(). the contents of a TextBox are saved using normal FileStream mechanisms as a text file: .ReadWrite). XmlNodeType.Create. go with the tide. So the application allows the user to enter some XML in the TextBox that can be programmatically validated against the schema.Validating Against the Schema It’s a good thing to go with the flow. you can be sure it will get executed. } .. opening the XML file that we just created: FileStream nfs = new FileStream("Product.Close().. Similarly.xml". As in the previous example. } finally { sw. FileMode. and not to cut against the grain.Open. another FileStream is used to create an XmlValidatingReader. try { sw. FileAccess. FileStream fs = new FileStream("Product. null). you don’t want to go against the schema—which is all a roundabout way of saying that the next logical step is to validate XML programmatically against the programmatically created schema.xsd schema is added to the schemas collection of the XmlValidatingReader: .ReadWrite).Write (txtXML.. sw.

Schema.Text == "") txtValidate.3: Validating XML Against the XSD Schema using System. .ValidationEventHandler += new ValidationEventHandler(XmlValidHandler). .Text += e.Text = "XML validates without problems. The complete code for validating a piece of XML against an XSD schema is shown in Listing 12.. "Product.Add(null.".Xml.ValidationType = ValidationType.Schemas.Message. using System. Note: A XmlValidatingReader can also be used to validate XML with older schema formats— such as DTD and XDR—by changing the ValidationType setting.Show("Please enter some XML to validate!").Text == String.Schema: vr. if (txtValidate. using System.vr. a callback is set up to handle validation errors: vr. ValidationEventArgs e){ txtValidate..Schema..Read()). } The only thing that remains is to read through the entire XmlValidatingReader—and display an appropriate message if there are no validation problems: while (vr. . private void btnValXML_Click(object sender.Xml. } private void XmlValidHandler(object sender.Empty){ MessageBox. As with the schema validation code.3.EventArgs e) { if (txtXML.xsd")..IO. Listing 12. System. The ValidationType of the XmlValidatingReader is set to ValidationType.

} To test it. if (txtValidate. FileAccess.Flush().ValidationEventHandler += new ValidationEventHandler(XmlValidHandler). vr.Read()). FileMode.xml". "Product.ReadWrite).Text == "") txtValidate. } } private void XmlValidHandler(object sender.Close(). try { sw.Schemas. vr. FileStream fs = new FileStream("Product. } finally { sw.Empty.Text).xml".".Message. FileMode.Write (txtXML.Text += e. fs. null). vr. sw.return. StreamWriter sw = new StreamWriter(fs).ValidationType = ValidationType. and enter the original Product XML in the TextBox: <?xml version="1. XmlValidatingReader vr = new XmlValidatingReader (nfs. vr. XmlNodeType.xsd").0" encoding="utf-8" ?> .Close(). } FileStream nfs = new FileStream("Product.Add(null.Open.Text = String.Element. } txtValidate. ValidationEventArgs e){ txtValidate.Create. FileAccess.Close(). while (vr. run the program.Text = "XML validates without problems.ReadWrite).Schema.

such as entering a ProdType value that is not part of the enumeration and a "Bogus" element.9).<Product ProdId="123" ProdType="Toy"> <Name>Purple Wheeler</Name> <Desc>Tastes like grape</Desc> <SubPart>Wheel</SubPart> <SubPart>Purple</SubPart> </Product> Next. try making some changes. .formed XML that matches the schema file is validated without problems. Figure 12. In the interests of science. Figure 12. click Validate XML. Both things will be reported as validation problems (Figure 12.8). The message will indicate that there have been no validation problems (Figure 12.8: Well .9: The XmlValidatingReader has found some problems in the XML.

shown in Figure 12.Creating an XML Data File Before we get to some more of the interesting things you can do reading and writing XML. or code generation. you’ll probably prefer to get your XML data from databases. This approach is a great deal faster than hand constructing each XML data element—although in real life.10: XM L data can be entered using a table once the element has been constructed. Once you have entered your data in tabular fashion. files. you can start entering your data using the table provided. if you switch to the Data tab of the XML Designer. . In order to use this feature. you can view it as straight XML by clicking the XML tab. it’s worth emphasizing that you can use the Data tab of the Visual Studio XML Designer to add element data against XML. Figure 12.11. you must first create the XML for a table using the XML tab of the XML Designer. the XML for a simplified Product table might look like this: <Product ProdId="" ProdType=""> <Name></Name> <Desc></Desc> <SubPart></SubPart> </Product> With this in place.10. as shown in Figure 12. For example.

click in the XML Designer and choose Create Schema from the context menu. Serializing XML Let’s have some fun with serializing a class to XML! Actually. The reasons to first create an XML schema are to ensure consistency and to make the schema available to remote applications that may communicate with your application. It’s a neat feature that you can auto . you don’t need to create an XML schema. Note: In many cases. right .11: Dat a entered in the table can be viewed as “straight” XML. The XML required to connect the XML file to the schema will automatically be added to the XML file. To do this. Tweak the schema.xsd file extension.hydrate" the serialized XML and access it by the name of each element—just as though you were accessing a property belonging to an instance of a class. The Deserialize method lets one "re . create an XML fragment. the DataSet will do the best it can to create a schema on the fly from an XML data file. As I mentioned earlier in this chapter.generate an XML schema based on your XML data file.generate a schema. Use it to auto . First. It is a little bit harder to serialize multiple elements of the same type under a single root node—but not . Then distribute the schema for validation of longer XML documents. it’s quite easy to use the Serialize method of XmlSerializer objects to turn the fields and properties of a class into XML (methods are ignored). The newly generated XSD file will appear in the Solution Explorer with the same name as the original XML file but with a .Figure 12. you might want to mirror the sequence I’ve used in this chapter. If you don’t set up a schema.

} } public string Lname { get { return m_Lname. public string Fname { get { return m_Fname. and title properties for each employee. It would have worked just as well from the viewpoint of serialization to use public class fields. Note: I went to the trouble of implementing properties in this class because it is good programming practice to use properties for access combined with private variables to maintain property state. last name. string lname. string title) { m_Fname = fname.4. it has a class constructor that supplies these values. private string m_Title = String. } set { m_Fname = value. This will be the member information used for serialization into XML.4: A Class Ripe for Serialization to XML public class Employee { public Employee(string fname. Here’s how it works. m_Lname = lname.Empty. . } public Employee(){ } private string m_Fname = String. Listing 12. as well as a parameter less constructor.Empty. m_Title = title. For convenience sake.that difficult.Empty. Let’s start with the Employee class shown in Listing 12. The class provides first name. private string m_Lname = String.

ICollection defines size. } set { m_Title = value. and synchronization for all collection classes. Listing 12.5. enumerators.. For my own convenience. In addition. shown in Listing 12.} set { m_Lname = value. The next step is to “wrap” the Employee class in a collection class named OurTeam that implements the ICollection interface. public Employee this[int index]{ get{return (Employee) empArray[index]. } } public string Title { get { return m_Title. see the topic “Controlling XML Serialization Using Attributes” in online help. I also added a Clear method to the class.. For more information. the XmlSerializer needs the class to have an Add method and an Indexer.} } public int Count{ . . public class OurTeam: ICollection { public OurTeam(){ } private ArrayList empArray = new ArrayList().5: Implementing the ICollection Interface Around the Employee Class using System. } } } Note: Many aspects of how an object is serialized to XML can be controlled with attributes.Collections.

To get started in the form.GetEnumerator(). } public object SyncRoot{ get{return this. When the Add Employee button is clicked.Clear(). a variable needs to be declared to hold the OurTeam instance at the form . the Serialize button is used to serialize the class to a file. the employee will be added to the OurTeam collection.} } public bool IsSynchronized{ get{return false. index).Add(newEmployee).CopyTo(a. The user interface can be implemented in a Windows form: The user will enter a first name. last name.} } public IEnumerator GetEnumerator(){ return empArray. When all the employees have been added.get{return empArray. } } The preliminaries are now in place. } public void Clear(){ empArray. } public void Add(Employee newEmployee){ empArray. and title for each employee.Count. The Deserialize button recovers the information from the XML and reads it into ListBoxes.} } public void CopyTo(Array a. int index){ empArray.

xml". txtLname. ourteam = new OurTeam()..Text = String.Serialize(sw.Text.Serialization.6: Adding Employee Instances to the Collection Class private void btnAdd_Click(object sender.IO and System. Listing 12.EventArgs e) { try { XmlSerializer theSerial = new XmlSerializer (typeof(OurTeam)). // in the form constructor Listing 12. you need to add directives to System.7: Serializing the Collection Class using System.Empty.. txtTitle. } catch (Exception excep){ . theSerial.Text = String.7 shows the code required to serialize the class. System. ourteam). using System. to the collection class.Text = String. txtTitle. based on user input.Xml.false).6 shows the code for adding Employee instances. Listing 12.Empty.Empty.Add (employee). sw = new StreamWriter("employee. txtLname. //at the form level . private void btnSerial_Click(object sender.Xml. StreamWriter sw.Serialization. . System. Listing 12.level. txtFname.Text.Text)..IO. and the object is instantiated in the form constructor—both are done so that the instance of the collection class is available while the form is running: OurTeam ourteam. } To serialize the collection class. ourteam.EventArgs e) { Employee employee = new Employee(txtFname..

Message). } } This code is pretty straightforward. A new XmlSerializer is created. If you open the file created by the XmlSerializer in an XML editor.MessageBox. Note: The second argument to the StreamWriter constructor. sw.xml in the application directory.Close(). } finally{ sw. the XmlSerializer’s Serialize method is used to connect the collection class and the StreamWriter.Clear(). it will appear along the lines of Figure 12. you’ll see that it produces multiple XML elements of this sort: <Employee> <Fname>Frodo</Fname> <Lname>Baggins</Lname> <Title>Hobbit</Title> </Employee> All the <Employee> elements are contained within a single root <ArrayOfEmployee> </ArrayOfEmployee> node. ourteam. A StreamWriter is connected to the file employee. tells the StreamWriter to overwrite a file with the given path (rather than append to it). using the typeof method to pass it the type of the OurTeam collection. Finally.12. If you try this out and examine the file that results. the Boolean false.Show (excep.Flush(). .

xml".12: The <Employee> elements are contained within one <ArrayOfEmployee> root node tag. The code for deserializing the XML in the file is shown in Listing 12. . Listing 12. use the Deserialize method of the XmlSerializer to connect with the FileStream. and cast the result to type OurTeam: newteam = (OurTeam) theSerial. FileStream fs. OurTeam newteam = new OurTeam().Fname).Open). private void btnDeserial_Click(object sender. fs = new FileStream("employee.FileMode.EventArgs e) { try{ XmlSerializer theSerial = new XmlSerializer (typeof(OurTeam)).Serialization. Next.Items.Deserialize(fs).. .Add(emp. newteam = (OurTeam) theSerial.8: Deserializing XML from a File to Class Instances using System. foreach (Employee emp in newteam){ lstFname.Figure 12. System..8.Xml.Deserialize(fs).IO. using System. To deserialize the XML contained in the file into Employee instances of an instance of the OurTeam class. first instantiate an XmlSerializer and a FileStream.

using instance member syntax. the Employee items in the collection class can be cycled through.13: Once the XML has been deserialized from the file back to class instances. } finally{ fs. The results of adding the XML elements.14. Using the XMLTextWriter. Reading and Writing XML You don’t need to serialize XML content to write (and read) XML documents to and from files. it can be accessed normally.Add(emp. and the text entered in the Value text box will become its value: . } } Finally. shown in Figure 12. Figure 12. lstTitle. In other words.Add(emp.Title). The XmlTextWriter class allows you to write whatever XML you care to generate. To see this in action. and XmlTextReader lets you read the contents of an XML document.lstLname. and their XML elements accessed like instance members. } } catch (Exception excep){ MessageBox.Lname).Show (excep.13.Items.Message). The text entered by the user in the Element text box will become the XML tag.Items.Close(). an XML document is written from top to bottom. you get a single forward pass at creating the XML document. to ListBoxes. is shown in Figure 12. which lets the user decide the name for XML nodes and the values that those nodes enclose. I’ve set up a simple interface.

So the structure of the XML document will look like this. with each element/value combination chosen by the user: <UserChoice> <element> value </element> <element> value </element> .. Listing 12.<element> value </element> Figure 12.14: Users can enter as many element/value pairs as they want.9 shows the bare . Once again. we’ll use a class that is based on ArrayList to handle this (this time inheriting directly from ArrayList rather than using ArrayList functionality via encapsulation as in the example in the previous section). the application must have some way to store the information.bones class that will be used for each element/value pair and the collection class that will be used to store the user’s XML choices until the XML document is ready to be generated. all the element/value pairs entered by the user will fall under one root XML node. Listing 12. when all the pairs have been entered.. <UserChoice>. In the interests of simplicity. </UserChoice> Creating the Class and Collection Class If the user can enter multiple element/value pairs before the XML document is generated.9: An Element/Value XML Class and a Collection Class Used to Store Element/Value Pairs . the XML document is created.

// in Form1 constructor In the click event procedure of the Add button. } public class XmlCollection : ArrayList { public void Add (Xml aXml){ base.9: XmlCollection theXml. but. theXml = new XmlCollection(). So I marked the variable name with the @ literal identifier. which can be used as a variable.Xml.using System.Collections. declare at the form level and instantiate in Form1’s constructor a collection object based on the collection class defined in Listing 12. and the Write button will be used to generate the relevant XML document. instantiate an element/value object.. public class Xml { public string element.Add (aXml). assign the values of . // Declared at form level . Next. public string @value. At the top of the form module. @value.Xml namespace (it contains the XmlTextReader and XmlTextWriter classes): using System. The Add button will be used to add an element/value pair to the collection. value is a reserved keyword in C#. import the System.. of course.. like that shown in Figure 12. } } Note: I really wanted to use the identifier value for the variable holding the value of an element.14.. Using the Class and Collection After you’ve set up the class and collection. . create an interface with text boxes for the user input of elements and values.

Setting the encoding to null causes it to be written out as UTF .8. by default the file will be written to the executable.Indentation = 3. myXmlTextWriter.Formatting = Formatting.WriteStartElement("UserChoice"). use the foreach syntax to cycle through the collection to write each item in the collection: . If no path is provided. Now let’s write an XML document based on the contents of the collection! In the click event procedure for the Write button. myXmlTextWriter. System. Next. null). declare and instantiate a XmlTextWriter. or bin. txtElement. declare an element/value XML object based on the class defined in Listing 12. txtValue.Indented.element = txtElement.@value = txtValue.Text. place a comment at the start of the document. } That takes care of adding element/value pairs to the collection.Text = String.9. directory of the project.WriteStartDocument().Empty. aXml. There’s nothing to stop you from using the common dialogs (or other mechanism) to allow the user to select a file.Empty.Add (aXml).Text.WriteComment("This is a sample XML document" + " generated using an XmlTextWriter object. Warning: The XmlTextWriter will overwrite the specified file if it already exists. theXml. myXmlTextWriter.EventArgs e) { Xml aXml = new Xml().Text = String. Next.the text boxes to it. aXml. specifying a file (including path) and the encoding to use: XmlTextWriter myXmlTextWriter = new XmlTextWriter("doc. and create a root XML element called <UserChoice>: myXmlTextWriter. myXmlTextWriter.xml"."). Use the methods and properties of the XmlTextWriter object to tell the XmlTextWriter to format the XML with three spaces for indentation. myXmlTextWriter. and add it to the collection: private void btnAdd_Click(object sender.IndentChar = ' '.

@value). Note: I just wrote “finally” close the root element and document. null).. } private void btnWrite_Click(object sender.. as you may be thinking. .10: Creating a Collection of Element/Value Pairs and Writing Them to an XML Document using System.finally structure.element = txtElement. aXml. I haven’t done this here to make the code more readable—but you should place this kind of closure code within finally blocks in all real .. the closure code in this example (and in some others in this chapter) should be placed in the finally block of a try.Text = String. // Declared at form level ...Text.Formatting = Formatting.Empty. myXmlTextWriter. System.foreach (Xml aXml in theXml){ myXmlTextWriter. System.10. myXmlTextWriter.EventArgs e) { Xml aXml = new Xml().Text = String.Indented.. theXml. aXml.EventArgs e) { XmlTextWriter myXmlTextWriter = new XmlTextWriter(" applications.Empty. aXml. .Close().element. private void btnAdd_Click(object sender.WriteEndElement(). txtElement. theXml = new XmlCollection().Xml. Well.Text.Add (aXml). The complete code for adding XML element/value pairs to the collection and writing an XML document based on the collection items that have been added is shown in Listing 12. XmlCollection theXml..WriteElementString(aXml. txtValue.. Listing 12. close the root element and document: myXmlTextWriter.xml".@value = txtValue. // in Form1 constructor . } Finally.

myXmlTextWriter.IndentChar = ' '. myXmlTextWriter. foreach (Xml aXml in theXml){ myXmlTextWriter. For example.WriteComment("This is a sample XML document" + " generated using an XMLTextWriter object. in which we know that the root element is named . } myXmlTextWriter. this simple application doesn’t check to make sure that each element added is unique. myXmlTextWriter. myXmlTextWriter. You’ll find that an XML document.myXmlTextWriter.@value). you will know a great deal about the formatting of the XML file you want to read. } Run the project and enter some element/value pairs. Much of the time. myXmlTextWriter.").WriteStartDocument(). clicking Add each time. Next.0"?> <!--This is a sample XML document generated using an XmlTextWriter object. Realize that this is by no means the only class available in .--> <UserChoice> <Protagonist>Nicholas Nickleby</Protagonist> <Schoolmaster>Wackford Squeers</Schoolmaster> <InfantPhenomenon>Ninetta Crummles</InfantPhenomenon> </UserChoice> Note: You could certainly extend this application if you were interested or needed to. Reading XML with XmlTextReader The counterpart to the XmlTextWriter class is the XmlTextReader class.Indentation = 3. click the Write button. has been created along these lines: <?xml version="1. XmlNodeReader and XmlValidatingReader (used in the example in the previous section of this chapter) are also powerful classes.WriteElementString(aXml.WriteEndElement(). That’s certainly the case with the example that I’ll show you. aXml.element.NET to read (or parse) XML files. Duplicate checking could be implemented by calling the Exists method of the XmlCollection class before adding an element to the collection.Close(). containing the elements and values you added.WriteStartElement("UserChoice").

} Within the loop. .Read()){ if (myXmlTextReader. } Finally..xml").11.. it’s an easy thing to instantiate a new XmlTextReader object and start it past the root element: XmlTextReader myXmlTextReader = new XmlTextReader("doc.ReadOuterXml() + "\r\n".Text += myXmlTextReader. placed in the Display button’s click event.Read()){ if (myXmlTextReader.Name == "UserChoice") break. You could then set up a loop reading the rest of the document. the XmlTextReader should be closed: myXMLTextReader.Name == "UserChoice") break. you can use the ReadOuterXml method to read tags and values into a multiline text box: while (myXmlTextReader. txtReader.ReadStartElement("UserChoice").. and exiting only when the end of the document or the </UserChoice> tag is reached: while (myXmlTextReader.<UserChoice> and is followed by elements and values: <UserChoice> <element> value </element> <element> value </element> ..Close() The code. </UserChoice> If you are interested in only the elements and values. myXmlTextReader. . is shown in Listing 12.

Listing 12. I leave it. as an exercise for the reader to refine this project by adding exception handling and dealing with white space entered by the user for an element tag (as things stand.ReadStartElement("UserChoice").ReadOuterXml() + "\r\n". txtReader. myXmlTextReader. this will cause the program to throw an exception when XmlTextReader tries to retrieve the tag with white space)—and to customize the application for your particular needs. System.EventArgs e) { txtReader. .11: Reading an XML File private void btnDisplay_Click(object sender. XmlTextReader myXmlTextReader = new XmlTextReader("doc.Text += myXmlTextReader.xml"). } If you run the project and click the Display button.Close(). as they say. } myXmlTextReader. the elements and values in the doc.xml file contained between the beginning <UserChoice> tag and the ending </UserChoice> tag will be shown in the text box (Figure 12.Text = String.Name == "UserChoice") break.Empty. while (myXmlTextReader.Read()){ if (myXmlTextReader.15).

and so on.Figure 12. including SAX (see sidebar) and the Document Object Model (DOM)—implemented by the XmlDocument class and related classes—which is discussed in this section. which reads the entire XML document into memory). it is available in memory and can be traversed backwards and forwards. and only moves in one direction: forward. Once an XML document has been loaded into an instance of the XmlDocument class.) The SAX interface reads an XML document using an events-based model. SAX could fairly easily be implemented in a class derived from XmlReader. they don’t help you that much with adding. The Document Object Model (DOM) The XmlTextWriter and XmlTextReader classes described in the last section are great if all you need to do is make a single pass through an XML document—and read or write it to a file. Simple API for XML (SAX) Simple API for XML (SAX) is an alternative to DOM that is not supported explicitly by any . However. (As a publicly developed interface. A SAX reader is fast.3: Types of XmlDocument Nodes DOM Node Document . attributes. some of which are shown in Table 12.3.15: The XmlTextReader is programmed to retrieve the tags and values between the <UserChoice> and </UserChoice> tags. SAX requires less overhead than DOM. SAX can be configured to read only the information you need (unlike DOM. edit. You can add. editing. so it might make sense to use it if resources are an issue and you have a large number of XML documents to parse (or a single.NET classes.NET Class XmlDocument Description The container of all the nodes in thetree. and deleting members of an XML document. The parts of the XML document are represented by node classes. very large document). or delete node classes belonging to an active XmlDocument instance. Other mechanisms exist that do a better job of this. These classes represent elements. Table 12. It is also known as the document root (not always . In additionunlike DOMa SAX parse can be stopped when you find what you need. read-only.

look up “Types of XML Nodes” in online help. Text belonging to an element or attribute. An attribute of an element. e. A comment node.3) that can be used in .. Represents an element node.16: Interaction of the XML Document Object Model (DOM) classes.16 shows how the DOM classes interact. Note: For a complete list of XML nodes. nodes in an XmlDocument can be accessed and managed using the XmlNode class. Figure 12. which is derived from XmlDocument. DocumentFragment DocumentType Element Attr Comment Text XmlDocumentFragment XmlDocumentType XmlElement XmlAttribute XmlComment XmlText Temporary XML storage containing one or more nodes without any tree structure. As you can see in the figure.NET but are not approved by W3C. ProcessingInstruction XmlProcessingInstruction A processing instruction node. XmlAttribute and XmlElement. The topic describes nodes (not shown in Table 12. Figure 12. Note: You may also be interested in the XmlDataDocument class.the same as the root element). Represents the <!DOCTYPE…> node. They can also be accessed and managed using a class that depends on the specific type of the node.g. XmlDataDocument allows you to load relational data and manipulate it as XML using .

and a TreeView control to a form.) To start with.17: The user can select an XML file for parsing.xml suffix. I added the standard common . the classes and mechanisms related to DOM and the XmlDocument class are complex and powerful.XML file is selected (whatever the suffix). It would take at least a chapter to cover them all—actually.17. XmlTextReader xr. At the form level. an XmlTextReader and an XmlDocument are instantiated: . within the button’s click event.12. With a file selected.NET classes and how they work. (The complete code will be shown in Listing 12. in Figure 12.dialog code to allow the user to select an XML file. an OpenFileDialog. Note that not all XML files end with a . But I can provide a demonstration that allows you to load an XmlDocument object from a file. XmlNode xn. and then move recursively through the nodes in the XmlDocument. I declared some variables to hold the TreeView nodes. if a non . However. Next. As you can see. Figure 12. a big fat book.the DOM. displaying representations of them in a TreeView control. the XMLDocument nodes. I don’t have space to explain all the DOM . more like a book. and an XmlTextReader: TreeNode tn. the program will throw an exception.related .config XML file. the user is shown selecting a Web. I added a Button. Obviously.

Add(tmpTn). while (tmpXn != null){ .. then process them. tn).Add(tmpTnAttr). The top node of the TreeView is created and the WalkTheTree method invoked: tn = new TreeNode("XML Document").xr = new XmlTextReader(theFile). and their children and siblings. . by recursively calling WalkTheTree: if (xn.Attributes){ tmpTnAttr = new TreeNode(xnAttr.Nodes. xn = xDoc. } } If the node has children. tmpTn.Add(tn). tn. tmpTn = new TreeNode (xn. a node is added to the topmost node..Element){ foreach (XmlNode xnAttr in xn. Within WalkTheTree.Load (xr). using the XML name and value of the node for display purposes: XmlNode tmpXn. TreeNode tmpTn. XmlDocument xDoc = new XmlDocument() The Load method of the XmlDocument is used to connect the selected file to the XmlDocument: xDoc.Name + " " + xn.DocumentElement.HasChildNodes){ tmpXn = xn. TreeNode tmpTnAttr.Value).Value). tView.NodeType == XmlNodeType.Nodes.Nodes. WalkTheTree (xn.FirstChild.Name + " = " + xnAttr. Attributes need special handling: if (xn.

With the nodes expanded. and attributes will be displayed (Figure 12. XmlNode xn. } } If you try this on an XML file. value.WalkTheTree(tmpXn. XmlTextReader xr. openFileDialog1. TreeNode tn.config file selected earlier. System.xml)|*. such as the Web. Figure 12.12: Displaying the Nodes of an XML Document Using DOM using System.ExecutablePath.EventArgs e) { string theFile. tmpXn = tmpXn. . .NextSibling.18). openFileDialog1.*".DefaultExt = "xml".Filter = "XML Files (*..Xml. openFileDialog1. private void btnFile_Click(object sender.InitialDirectory = Application. you’ll have to expand the nodes in the TreeView. the element name. tmpTn).xml|All Files (*.18: You can display all the nodes of an XmlDocument using a recursive method.*) | *. Listing 12..

Add(tmpTnAttr). this.Show (excep. xDoc.Name + " = " + xnAttr. TreeNode tmpTnAttr.HasChildNodes){ tmpXn = xn.Add(tmpTn).NodeType == XmlNodeType.FileName. tmpTn = new TreeNode (xn. tn. tView. TreeNode tmpTn.OK){ theFile = openFileDialog1. tn).ShowDialog() == DialogResult.None.Value). XmlDocument xDoc = new XmlDocument(). } } if (xn.Nodes.Nodes.Message).Close().Cursor = Cursors.DocumentElement. . } finally { xr. } } } private void WalkTheTree(XmlNode xn. if (xn.WhitespaceHandling = WhitespaceHandling.Clear(). try { tView. TreeNode tn){ XmlNode tmpXn.Cursor = Cursors. xn = xDoc.WaitCursor. WalkTheTree (xn.Load (xr).Name + " " + xn.Default. xr.Nodes. tn = new TreeNode("XML Document"). this.Attributes){ tmpTnAttr = new TreeNode(xnAttr. tmpTn. } catch (Exception excep) { MessageBox.if (openFileDialog1.Nodes. xr = new XmlTextReader(theFile).Element){ foreach (XmlNode xnAttr in xn.FirstChild.Value).Add(tn).

but there are many other possible uses of XSLT (for instance. System. Derived from XmlDocument and allows XSLT transformations to be per formed on relational data retrieved from a database. tmpXn = tmpXn. Once again. At its best. XSLT is a topic far too big for a small portion of a chapter. For more information.while (tmpXn != null){ WalkTheTree(tmpXn. Table 12.only XML document processing with an XSLT transformation.XPath. . tmpTn). see "Synchronizing a DataSet with an XmlDataDocument" in online help. } } } XSL Transformations The purpose of Extensible Stylesheet Language Transformations (XSLT) is to convert the form (or structure) of an XML document to a different format or model for navigating an XML data source. XPathDocument Provides read . See previous section for more information.4.Xsl namespaces.Xml. a document could be transformed so that it contained only the fields required by an application). Probably the most common use of this is to transform XML into HTML so it can be displayed in a web page.NextSibling. Interface that provides a CreateNavigator method. XslTransform Transforms XML data using an XSLT style sheet. Some of the classes and interfaces most commonly used with XSLT in these namespaces are shown in Table 12.Xml. and System. XSLT is an extraordinary tool for separating form—the appearance of the thing—from structure. XPathNodeIterator Provides an iterator over a set of nodes in an XPathDocument. which is used to create an XPathNavigator. You’ll find classes related to XSLT in the System. Enables editing and parsing of an XML document.4: Classes and Interfaces Commonly Used with XSLT Class or Interface XPathNavigator IXPathNavigable XmlDocument XmlDataDocument What It Is Used For Provides an API that provides a cursor .Xml.

org/1999/XSL/Transform" version="1. shown in Listing 12.NET Web Application.0'?> <xsl:stylesheet xmlns:xsl="http://www. shown in Listing 12. open a new ASP.0" encoding="utf-8" ?> <NewDataSet> <Product> <Name>Kite</Name> <Desc>It flies</Desc> <SubPart>wings</SubPart> </Product> <Product> <Name>Sour Ball</Name> <Desc>Tastes good</Desc> <SubPart>n/a</SubPart> </Product> <Product> <Name>RoboDog</Name> <Desc>Does tricks</Desc> <SubPart>CPU</SubPart> </Product> </NewDataSet> Listing 12.14: The XSLT Transformation <?xml version='1.For a simple example of how this works.14.0"> <xsl:template match="/"> <html> <body> <table cellspacing="3" cellpadding="8"> . to the project.13. Listing 12.13: The XML File <?xml version="1. Add an XML file. and a file containing an XSLT transformation.w3.

Once the files have been added to the project. You can add these files to the project in one of several ways: by adding empty XML and XSLT files using the Add New Item dialog.13 Many_Prod. The XSLT file shown in Listing 12.14 is called Trans_Prod. they will appear in Solution Explorer: .xsl.xml.<tr bgcolor="#AAAAAA"> <td class="heading"><B>Name</B></td> <td class="heading"><B>Description</B></td> <td class="heading"><B>SubPart</B></td> </tr> <xsl:for-each select="NewDataSet/Product"> <tr bgcolor="#DDDDDD"> <td valign="top"><b> <xsl:value-of select="Name"/></b> </td> <td valign="top"><b> <xsl:value-of select="Desc"/></b> </td> <td valign="top"><b> <xsl:value-of select="SubPart"/></b> </td> </tr> </xsl:for-each> </table> </body> </html> </xsl:template> </xsl:stylesheet> I’ve called the XML file shown in Listing 12. or by creating the files in an external editor and then selecting Project > Add Existing Item.

org/1999/XSL/Transform namespace that are replaced with values from the XML file to be transformed.19). You can identify these replacements in Listing 12. with the properties of the Xml control. use the Toolbox to locate the Xml control (on the Web Forms tab). Add an Xml control to the project.13) and the TransformSource to Trans_Prod. The Properties window will open. and the XSLT transformation to use. right . With the project’s WebForm open. Run the project.14 because the tags begin with xsl:.14). In the Properties window.w3.20).xsl (shown in Listing 12. This listing combines HTML with tags specified in the http://www.The XML file shown in Listing 12.xml (shown in Listing 12. and select Properties from the context menu. The data in the XML file will appear in your browser window as formatted by the XSLT file (Figure 12.19: You can set the XML file to be it.14. . Figure 12. with the Xml control selected in it (Figure 12. set the DocumentSource to Many_Prod.13 is clearly straightforward. but let’s have a little closer look at the XSLT transformation shown in Listing 12.

of information. Structured Query Language (SQL) is the common language used to manipulate and retrieve information stored in relational databases. logically.) In effect. Each table usually contains a primary key. This is called a one . (Obviously.powered databases are organized as servers and are referred to as a database management system (DBMS). The software used to contain it is.NET without needing to program and without possessing a deep understanding of databases. High . An example of a poorly chosen primary key would be a customer’s last name. which is used to uniquely identify records. foreign keys.many relationship.mail address. Database Basics It’s true that you can create database applications in C# . A row across a table is called a record. the value of a primary key cannot be duplicated across records. called fields. You could also have an Orders table in which the same cust_ID showed up several times as a foreign key (because one customer made multiple orders). within a table. are used to establish relationships between the data in tables. because duplication is possible (two people can have the same last name). In addition to primary keys. For example. suppose you have a Customers table in which cust_ID is the primary key. Each table contains rows and columns. In other words. RDBMS. which do not need to be unique. an e . a relational database. and the XSLT file formats it.Figure 12. For . a primary key might be a Social Security number.20: The XML file supplies the data. willingness to program and having a sophisticated understanding of databases does help!) You do need to know that databases are organized in tables. or other unique identifier. Data organized in this fashion in independent tables is called relational. (You’ll also see the term relational database management system. .

NET are a set for Microsoft’s SQL Server DBMS and a set that works with any OLE database (OLE DB) source (in other words. Each set of objects. from the viewpoint of a C# . running stored procedures is generally far more scalable and secure than executing ad hoc SQL statements against a of course. input/output means working with a database. Each new version of Microsoft’s programming languages has shipped with a new model for the data connectivity layer. is known as a managed provider.5. Note: In production applications. or for further information.NET For many developers today. ADO.NET. this likely means SQL Server. Currently. targeted at a particular data source. the only managed providers that ship with . and are called stored procedures. An ODBC managed provider is also currently available for download.NET program is a client of the database server (in the same sense that an Internet browser is the client of a web server). what you want to do to a database is either execute SQL statements against it or run a stored procedure in the database intended for access purposes (in large part. the stored procedure itself executes SQL).NET program. and search for ODBC .NET Data Provider.NET Data components are supplied as a separate set of four components for each data source. For those in the Microsoft The latest incarnation is. The purpose of each of the components is shown in Table 12. written in an internal. of course. go to http://msdn. I’ll forgo historical discussion and concentrate on how ADO.NET provides data access to C# programs—and how to use the tools that Visual Studio provides to make connectivity easier. no exception. Since space in this chapter is limited. you should know that. a managed provider for all the rest). and . ADO. . database . Working with Managed Providers The . While stored procedures are far beyond the scope of this chapter. The two sets of Data components (one set for SQL Server and one set for OLE DB) are functionally identical.specific language intended for this purpose.NET is. Good architecture of databases often funnels access through special programs internal to the database. To download it.the access layer of a C# .

memory cache of data (sometimes called a data store) made up of one or more tables. The examples in this chapter use the SqlDataAdapter provider with the sample Northwind database tables that ship with Microsoft’s SQL Server 2000.only result sets from a database. DataReader objects are created in code using the ExecuteReader method of either the OleDbCommand or SqlCommand objects (see the example in the next section). Note: Don’t worry if you don’t have SQL Server running.NET Data components. probably in Access format. SqlConnection OleDbCommand. if you have Office XP installed in the default location.mdb sample database will depend on your system and the software that you have installed on it. it may help .5: ADO. SQL Server components are part of System. Controls can be bound to a DataView. Provides a connection to a database server (or source). on your system. If you need to quickly cycle through all the values in a field but have no need to update or change the values.mdb. you will need to use the OleDbDataAdapter provider rather than the SQL Server provider. Contains and controls Command objects (see Table 12.Data. ExecuteReader method of the Command object used to createa DataReader object (to be discussed shortly). In addition. You should also know about one other object. The specific location (and name) of the Northwind.Data namespace.NET object is filled using one or more managed provider data adapters. Displaying Data with a DataReader Before we get down to more specifics about how to use the ADO. The OleDbDataReader and OleSqlDataReader allow you to return read .Data.6).mdb. SqlCommand DataSet Executes SQL statements (or stored procedures).SQLClient. You almost certainly have some version of the Northwind database. the DataReader. and OLE DB components belong to System. then the DataReader is probably the easiest route. Those with older versions of Office may find it at C:\Program Files\ Microsoft Office\Office\Samples\Northwind. For example. This fundamental ADO.OleDB.NET Data Components Available on the Toolbox Component SqlDataAdapter OleDbConnection. An in . DataView The Data components belong to the System. which contains filtered and sorted data based on a DataSet. you can find a copy of the database at C:\Program Files\Microsoft Office\Office10\1033\FPNWind. Purpose OleDbDataAdapter.Table 12.

add a ListBox named lstData—which will be filled in the WebForm load event with the required data.SqlClient namespace: using System. Using the query string and the SqlConnection. you’d want to use the System. As a preliminary. Open the connection: myConnection. The first step in the code module is to add a using directive to the System.Data.ExecuteReader(). Note: If you were working with a OLE DB data source and the OleDbDataReader (instead of the SqlDataReader).SqlClient.initial" + " catalog=Northwind.user id=sa"). create a new SqlCommand: SqlCommand myCommand = new SqlCommand(mySelectQuery.Open() Declare an SqlDataReader.Data. create the query string and define a new SqlConnection object (for more information about connection strings. myConnection).OleDb namespace instead. The task is to display the company names in the CompanyName field of the Suppliers table of the sample Northwind database. Next.Data.password=harold. and use the ExecuteReader method of the SqlCommand to instantiate and populate it: myConnection.Open().you to get a feeling for how these components work to run through a simple code example. Use the Read method of the SqlDataReader to run through the DataReader and retrieve the desired . see the following section): string mySelectQuery = "SELECT CompanyName FROM Suppliers". in the web form application used to demonstrate XSLT transformations. SqlDataReader myReader = myCommand. SqlConnection myConnection = new SqlConnection("data source=SQLSERVER. within the WebForm’s load event procedure.

21).15 shows the complete procedure that uses the ADO.Data.15: Displaying Data with an SqlDataReader Component using System. the company name information will be displayed in the ListBox when the WebForm loads (Figure 12.Close() If you run the project and click the button.Close() myConnection.Items. } Finally. and then uses the SqlDataReader to display specified data.21: The DataReader displays the results of the query in the ListBox.NET components to create an SqlDataReader. Figure 12.Add(myReader. Listing 12. Listing close the SqlDataReader and the SqlConnection: myReader.Read()){ lstData.SqlClient. and its GetString method to display it: while (myReader.GetString(0)). .

a user ID. a bad idea in the real world from a security viewpoint. of course. SqlConnection myConnection = new SqlConnection( "data source=SQLSERVER. SqlDataReader myReader = myCommand. Alternatively. myConnection. System.Items. private void WebForm1_Load(object sender. presumably with a great deal of access. Next.Open().. discussed a little later in this chapter.Read()){ lstData..initial" + " catalog=Northwind.GetString(0)).Close(). is by convention a database administrator (DBA). select the server connection you are interested in and choose Properties from the context menu..22).Add( myReader.EventArgs e) { string mySelectQuery = "SELECT CompanyName FROM Suppliers". One way to find this information is to inspect the properties of the Connection object. the initial catalog.user id=sa").password=harold. You may well need help in finding the various values that go into creating a connection string for a particular connection.Close(). You can now view the connection string that interests you in the Properties window (Figure 12. SqlCommand myCommand = new SqlCommand(mySelectQuery. and a password (if required for the user).text password. the connection string shown in Listing 12. A typical SQL Server connection string requires at a minimum the data source.15 uses a plain . myConnection. myConnection). It compounds this sin that the user shown in the connection string. while (myReader.ExecuteReader(). assuming that a data connection has already been made. } Connection Strings Many things can go into a connection string—depending. } myReader. upon the provider and configuration. . Warning: As you are likely aware. “sa”. you can open Server Explorer by selecting View > Server Explorer.

click on the file in Windows Explorer. rename it to change its file extension to . the ubiquitous Data Link Properties dialog will open (Figure 12. Once you’ve completed the entries on the Provider and Connection tabs of this dialog and clicked OK.23).22: The properties of servers shown in Server Explorer can be browsed to determine connection strings.23: Opening a .udl.udl file allows you to set data link properties. Figure 12. you’ll find the connection string corresponding to your entries in the .Figure 12. You may also be interested to know that if you save an empty text document in Windows Explorer. Provided that you are connected to a data source. and double . the Data Link Properties dialog—which will be revisited in the next section—should allow you to choose a database server and database from drop down lists of those available.

Adding Data Components The Data components are found on the Data tab of the Toolbox: To use a Data component. which are Command objects of type OleDbCommand or SqlCommand. Setting DataAdapter Properties ADO. Table 12.6.Object Properties of OleDbDataAdapter/SqlDataAdapter Object SelectCommand InsertCommand UpdateCommand Purpose Retrieves data from a database Inserts data into a database Updates data in a database . Data components sit on the tray beneath the form. respectively.NET DataAdapter components (OleDbDataAdapter and SqlDataAdapter) each support four properties. drag it from the Toolbox to a form. Like the other nonvisual components.underlying text file. you can also instantiate these components in code without using the visual interface provided by dragging and dropping.6: Command . as shown in Table 12. if you prefer. Note: Of course.

Adding a DataAdapter Component With a form open in its designer. in some cases. The Data Adapter Configuration Wizard will open.24: You can choose a data connection from the list of current data connections or create a new connection. however.clicking the SqlDataAdapter component on the Data tab of the Toolbox. Items in the tables in the dataset are manipulated to perform the actions required by the program and. Figure 12. you may need to be continuously connected to a database— in which case you will need to use the legacy ADO COM objects. click Next to start the wizard. The first step toward creating a dataset is to add a DataAdapter component. add an SqlDataAdapter to the form by double . The second panel. are saved back to the database. The data connectivity layer “pulls” data from a database and uses it to create a dataset. In some cases. shown in Figure 12. . Although they’re very powerful. datasets are disconnected tables of data. because processing can now be done on the client side without consuming database server resources.24. allows you to choose a data connection from the existing data connections or to create a new connection. This has great performance advantages.DeleteCommand Deletes data in a database Datasets A dataset is one or more tables of data.

follow these steps: 1. as shown in Figure 12.25. with the Connection tab active. The Data Link Properties dialog will open.Creating a New Data Connection To create a new connection. Figure 12. The first step in using the Data Link Properties dialog is to select the OLE DB provider you want to use to connect to the data. . Click the Provider tab and select your provider.25: The Data Link Properties dialog allows you to create a new data connection.26 shows the Provider tab with the Microsoft OLE DB Provider for SQL Server selected. 2. Click the New Connection button in the Data Adapter Configuration Wizard. Figure 12.

4. Many people find it easier to use a visual tool to create their SQL statement rather than entering one by hand. To visually generate a query. (In the case of a flat file Access connection. Click Next. to specify file types and connection strings. shown in Figure 12. Figure 12. see “Connection Strings” earlier in this chapter.26: The Provider tab is used to select an OLE DB provider. The Connection tab will reopen. Click OK. Building a Query The next Data Adapter Configuration Wizard panel. either by typing in a database name or by browsing for it in a drop .27. You will be asked whether you want to use SQL statements or stored procedures. you can browse for the file using an Open file dialog.Figure 12. 3. Use this tab to select the database you are connected to. and to supply other information that may be required to successfully connect. . the Connection tab is also used when a username and password are required for accessing a database server.) Note: Depending on the provider you selected. Click the Query Builder button in the Data Adapter Configuration Wizard. follow these steps: 1. asks you to enter an SQL statement that will be used to create the dataset. For more information.27: An SQL statement is used to determine which data will be used to create the dataset.down list.

clicking in the upper pane and selecting Add Table from the context menu. 4. select the fields you would like to include.29. Select a table and click Add.28. as shown in Figure 12. Figure 12. select the fields you would like to include: Note: The primary key for the table is shown in bold in the Query Builder. In the new tables that you added. Once you have added a table.2. The Add Table dialog appears. . You can add multiple tables to your query by right . The Query Builder lets you add a table or view.28: You can add tables and views using the Query Builder. as shown in Figure 12. 3.

30 shows a relatively complex SELECT query. For the example that follows. The SQL statement will appear in the Data Adapter Configuration Wizard. I’ll use the simple query SELECT * FROM PRODUCTS.30. When you are satisfied with your query. 5.29: Queries built in the Query Builder can include multiple tables. The final panel of the wizard will list the tasks that the wizard has performed and give you .Figure 12. 7. Click Next.30: SQL generated by the Query Builder appears in the Data Adapter Configuration Wizard. click OK. Note: Figure 12. 6. Figure 12. as shown in Figure 12.

The wizard will complete its work. Working with the Data Adapter After you’ve added the DataAdapter component.warnings about anything left to do. Click Finish. if you’ve forgotten what your SQL query was (or wish to modify it). . For example. you can restart the wizard by right . Note that the properties of the SqlDataAdapter are accessible in the normal fashion in the Properties window. you can look at the CommandText property of the SqlDataAdapter’s SelectCommand: Note: Clicking the button in the right column of the Properties window of the CommandText property opens the Query Builder interface. you’ll notice that an SqlConnection component has been added to the tray beneath your form and synchronized with the SqlDataAdapter’s connection string: Note: If you need to reconfigure the DataAdapter.clicking the SqlDataAdapter and selecting Configure Data Adapter. 8.

click Fill Dataset.down list. Click OK. Select New. make sure that sqlDataAdapter1 is selected in the Data Adapters drop . The DataSet component will be added to the tray beneath your form (so there are now three components on the tray).31. The Results panel will preview the dataset. Figure 12. . To preview the dataset. will open.32. and give the dataset a name (for example. shown in Figure 12. Make sure the tables you want are selected. will open. DataSet1). Generating the Dataset Finally.31: The Data Adapter Preview dialog allows you to preview the dataset. The Generate Dataset dialog. make sure that the Add This Dataset to the Designer check box is checked. In this dialog. Also. The Data Adapter Preview dialog. generate the dataset by selecting Data > Generate Dataset (or use the SqlDataAdapter’s context menu). Next.Previewing the Dataset Your next step will likely be to preview the dataset. shown in Figure 12. choose Data > Preview Data from the Visual Studio menus (or use the SqlDataAdapter’s context menu).

33). you can view it in tabular form or as "straight" XML (Figure 12. it appears as an . You can open the DataSet in its designer in the usual ways you’d suspect: for example.Figure 12. . you are exactly right.33: A generated DataSet is an XSD schema. Bingo! If you guessed that a DataSet is an XSD schema specifying the data elements that it represents as XML. You can also generate a dataset by adding a DataSet component from the Toolbox to your form. Figure 12.xsd file in Solution Explorer.32: The Generate Dataset dialog adds a DataSet component to your form. With the DataSet open as an XML schema in its designer.

listBox2.DataSource = dataSet1.DisplayMember = "ProductID".16.Products. Next. set the DataSource property for each ListBox to the Products table. Warning: You’ll need to change the properties of the SqlConnection component to reflect your own connectivity situation. Listing 12. The complete click event code for filling the two ListBoxes. System. Next.WaitCursor. Also add a Button control. the query SELECT * FROM PRODUCTS was used to create the dataset. set the DisplayMember property of each ListBox to the field you would like it to display: listBox1. as in this example: listBox1. including rudimentary exception handling. Within btnFill’s click event. with the text "Fill"."Products"). code that relies on database connectivity should always employ exception handling (in case there is a connectivity problem). named btnFill.16: Filling ListBoxes with Fields from a Database private void btnFill_Click(object sender. Note: You can set these properties using the Properties window rather than in code. As a simple example. . it’s time to do something with them. For this example.Binding Controls to a Dataset Now that we have our Data components in place. or this code will not run.Fill (dataSet1. As a general matter. I’ll show you how to feed the ProductName and ProductID fields from the Northwind Products table into two ListBoxes.DisplayMember = "ProductName".EventArgs e) { Cursor = Cursors. Binding to a ListBox Add two ListBoxes to the form. is shown in Listing 12. the first step is to fill the DataSet using the SqlDataAdapter’s Fill method: sqlDataAdapter1. if you prefer.

listBox1.DataSource = dataSet1.try { sqlDataAdapter1. } } Run the project and click Fill.Show (excep. } finally { Cursor = Cursors. Creating a Dataset in Code Obviously.SqlException) { MessageBox. Figure 12. listBox2.Products.Fill (dataSet1.Message). } catch (System."). listBox2.Default.Data.Products.NET data objects’ interfaces.DisplayMember = "ProductName". The two ListBoxes will display data from the appropriate fields (Figure 12. you will probably be pleasantly surprised to learn that you can easily build . listBox1. This interface complexity being what it is.Show ("Please check your SQL Server connection " + "and verify the connection string.34).DataSource = dataSet1. there is a great deal to the Visual Studio .DisplayMember = "ProductID".SqlClient.34: ListBoxes that have been connected to a DataSet can be set to display fields in a table. } catch (Exception excep){ MessageBox."Products").

instantiate the DataSet and set up the DataAdapter. Within the event procedure that will fill the ListBox. Fill the DataSet using the DataAdapter: theAdapter. in Listing 12. As an example.user id=sa"). Warning: You’ll need to change the connection string used in the SqlConnection constructor to reflect your own circumstances—or. of course. SqlDataAdapter theAdapter = new SqlDataAdapter(mySelectQuery.DisplayMember = "CompanyName".and use datasets and related data objects in code. the DataSource property is set using the Tables collection of the DataSet. Load the ListBox as before: listBox3. Note: In this example. find this a great deal simpler way to go about things. "Customers"). listBox3." + "initial catalog=Northwind. including basic exception handling. in fact.17: Creating a DataSet in Code private void btnFillCode_Click(object sender.DataSource = theData. You’ll find the code for creating the DataSet and filling the ListBox. myConnection). this code will not run. Listing 12.EventArgs e) { . let’s load the CompanyName data from the Northwind Customers table into a third ListBox. using the connection string and SQL command: DataSet theData = new DataSet(). SqlConnection myConnection = new SqlConnection("data source=SQLSERVER.Fill(theData. System.17. Next.password=harold. You may. without using the interface Microsoft provides.Tables["Customers"]. declare variables and assign them the connection string and SQL command: string mySelectQuery = "SELECT * FROM Customers".

SqlException) { MessageBox. } } If you run the project and click the Fill Code button. } catch (Exception excep){ MessageBox. listBox3.Show ("Please check your SQL Server connection " + "and verify the connection string.").Message).WaitCursor.Tables["Customers"].Cursor = Cursors. } catch (System. "Customers"). listBox3. . SqlDataAdapter theAdapter = new SqlDataAdapter(mySelectQuery. the customer names will appear in the ListBox (Figure 12. theAdapter. } finally { Cursor = Cursors.DisplayMember = "CompanyName"." + "initial catalog=Northwind. myConnection). try { string mySelectQuery = "SELECT * FROM Customers".SqlClient.DataSource = theData.35: It’ s possibly to manage data connectivity entirely programmatically.35).Fill(theData.Show (excep.user id=sa"). Figure 12.Default. SqlConnection myConnection = new SqlConnection( "data source=SQLSERVER.password=harold. DataSet theData = new DataSet().Data.

fill the DataSet (and thereby fill the grid). and update the database with changes that the user has made within the grid. Next.Binding to a DataGrid and Updating a Database DataGrids are wonderful controls for displaying tabular data and for allowing the user to interact with that data. To do this. which—as you’ll recall—contains the Northwind Products table. Within the Fill button’s Click procedure. bind the DataGrid to the existing DataSet. System. Warning: Make sure the DataSource property is set to dataSet1 and not dataSet1. The DataGrid has now been bound to the DataSet. Next.Fill (dataSet1). Set the DataSource property of the DataGrid to dataSet1. set the DataMember property to Products (the drop . Then add two buttons: one to load the form and one to update the database. add a DataGrid control to the form. add a line of code using the SqlDataAdapter’s Fill method to fill the DataSet: private void btnFillGrid_Click(object sender. I’ll show you how to bind a DataGrid to a DataSet. with the DataGrid selected. but the DataSet still needs to be filled. Figure 12.EventArgs e) { sqlDataAdapter1. First.36).Products.36: Bind the DataGrid to the DataSet by setting the appropriate properties of the DataGrid. In this section. open the Properties window (Figure 12. } .down arrow will show you all the tables within the DataSet).

as you .37: Our grid is populated with the contents of Products.38 shows the addition of comments about Harold Davis and his liking for food added to the ProductName column. in the form constructor. } Run the project again. Figure 12. a variation on this would be to fill a grid automatically without user intervention— for example. add the code to the Update button’s Click event that writes any changes made by the user back to the database using the Update method of the SqlDataAdapter: private void btnUpdate_Click(object sender. "Products"). for example. The grid will be populated with data from the Products table. the changes that a user makes within the grid only impact the DataSet—until the Update method of the DataAdapter is invoked. The Update method writes back and reconciles the changes that have been made in the DataSet to the database.EventArgs e) { sqlDataAdapter1.Run the project and click Fill. However. I started this chapter by noting that a DataSet is a miniature database stored in memory.37. System. as seen in Figure 12. to the Product Name data). and make some changes (for example.Update (dataSet1. Note: Of course. since both the ListBox and the grid are taking their data from the same DataSet. these changes are also written to the database. When you click Update. changes made in the grid are instantly reflected in controls that are bound to the same DataSet. This happens without updating the database. the ListBox in the upper left of the form. This being the case. load the data into the grid. To set this up. Figure 12.

The good news is the material in this chapter will help point you in the right directions for your own applications. You certainly need to know about connecting to databases. Well. Conclusion This chapter has touched on some very big topics: XML in . And the extensive and flexible use of XML both above and below the covers in .NET database connectivity. This book started by showing you how to create relatively simple web services. no knowledge of C# or of the classes in the .can verify by taking the sample application down and up.NET should certainly get you thinking creatively about how you can best use XML in your own applications.NET and ADO. At that point. and then re .38: Changes made in the grid are instantly made to the DataSet and written to the database when the Update button is clicked. The next—and final—chapter wraps it all up with an exploration of what you can do with web services now that you have a sophisticated understanding of .filling the DataSet. Figure 12. The bad news is that either topic alone—let alone both together—is way too vast to be covered in a single chapter. But no two applications are the same.NET and a considerable amount of C# programming under your belt. . you’ve come a long way.NET Framework was assumed.

someone else has provided the web service and methods. most ambitious. and it’s good to end with a bang rather than a whimper. without reference to anything that came before or will come after the method’s invocation. “Creating a Web Service. . There’s no reason to sneer at a stateless method. much of the time. I’ll show you as the final example how to consume one of the niftiest. I moved from there to the nuts and bolts of the C# language.Web Services as Architecture Overview • Managing session and state in a web service • Exposing data and returning a DataSet from a web service • Transaction management • Exposing a message queue as a web service • Using the TerraService web service to display aerial photos and topographic maps This book started by showing you how easy it is to create ASP. generally. a stateless method (such as a stateless web service) is used because it is more convenient (and less trouble) than writing the method as part of an application—particularly since. and returns the appropriate object or value. Managing Session and State Most common web services—including the ones demonstrated toward the beginning of this book— provide methods that are stateless.NET web services—and to consume those services.” for a refresher on the basics of creating a web service. This chapter revisits the material presented in the beginning of the book. what kinds of techniques would you need? Since life is too short to always be serious. or retrieves something. In fact. A stateless method knows nothing about the calling code. how many times it has been invoked by any client code. And. But we’ve come a long way in knowledge about . it does not know whether this particular code has ever invoked it before— or. The question this chapter looks at is: if you were to use web services as part of “serious” application architecture. The demo program explained in this chapter will display aerial photos or topographic maps of almost any place in the United States. Using one can be very convenient. you may wish to have a look at Chapter 1.NET Framework. in a variety of scales. Generally. and most fun web services out there: Microsoft’s TerraService.NET and C# since then. this kind of stateless method does some kind of complicated calculation. and how to utilize many of the important classes of the . For example. in fact.

then they would be much more useful if they. web services that are created using ASP. the ASP. When Should You Use a Web Service? Back in Chapter 1. which is why they have generated so much interest: • They are cross . When should you definitely not use web services? If a method is only going to be invoked by one application. it’s hard to imagine a good reason for wrapping the method in a web service (you’ll save the performance hit by putting the method in a DLL instead).NET HttpSessionState object can be used to store and retrieve session and application information.NET mechanisms for storing session and state information. Web services have two huge pluses. Marking a web service method with the WebMethod attribute EnableSession = true gives the method access to the ASP.NET applications (so you are writing . If you expect all clients to be . For example: .But normally applications do need to keep track of state information. no form of distributed architecture (including web services) should be used. • Web services travel on standard HTTP: they do not require creating custom ports (also called “holes”) in firewalls for access.NET HttpApplication Application and Session objects for the web service. consider using . When the EnableSession attribute is applied to a web service method.NET). look up the topic “.NET to .NET remoting instead of web services (for .platform: I can consume a web service equally well from a Unix and a Windows application.NET remoting’s performance and its rich set of APIs).1 in that chapter summarizes information about some of these other technologies—which in many cases may have better performance characteristics than web services.” Table 1.based distributed computing. I noted that web services “are by no means the only architectural technology used for component . For more information. too.NET can use the ASP. if extreme security is required. As it turns out.NET Remoting Overview” in online help. Finally. could track state information. So if web services are to play an important part of an application’s architecture.

as you’ll see in the example. ResetGlobal Resets an individual session counter and the global application counter to zero. other than tracking state. It checks that a name was passed in. start adding the web methods.NET Web Service project. To get started with this." Next. you also need a mechanism for tracking the state information on the client side.. is shown in Listing 13. Generally.Services.[WebMethod (Description = "Starts the stateful service". Note: I named the class Ss—short for "stateful service.WebService { . The first. Each individual session must have a name—and.1. StartMe. EnableSession = true)] Note: In addition to the server")] public class Ss : System. Building a Stateful Web Service This section demonstrates a stateful web service that provides four methods: StartMe Starts an individual session and also globally initializes the stateful service. the application counter is started or incremented. Finally. Listing 13. the session counter is engaged and the session name saved. Delete the default and commented out code provided in the . This is a potential source of problems when the client is a browser and the user has turned cookies off.. open a new ASP.1: Initializing Session and Application Tracking .Asmx module and add a class declaration like this one: [WebService(Namespace="http://bearhome. cookies are used with the Application and Session objects to track information.Web. and returns the usage count for the individual session. Next. GetTotalUsage Returns the global usage count. GetSession Increments the counter tracking both individual session usage and the global number of sessions. returning that name is all it does.

If it does.[WebMethod (Description = "Starts the stateful service". shown in Listing 13.2. } else { Application["totalUse"] = (int) Application["totalUse"] + 1.Empty) { return "You must enter a name to start the service!". } else { if (Application["totalUse"] == null){ Application["totalUse"] = 1. the application and session counters are incremented.2: Incrementing the Session and Application Counters [WebMethod (Description = "Iterates the stateful service". . } } The next web service. } Session["numSession"] = 1. } else { if (Application["totalUse"] == null){ Application["totalUse"] = 1. and the session counter value is passed back. EnableSession = true)] public int GetSession (string theName) { if (theName != (string) Session["userName"]){ return 0. Session["userName"] = theName. your session has started!". } else { Application["totalUse"] = (int) Application["totalUse"] + 1. GetSession. Listing 13. Note: The values contained in the Application and Session objects must be explicitly cast to string or integer (as appropriate). return theName + ". verifies that the name passed in matches the name of the session. EnableSession = true)] public string StartMe(string theName) { if (theName == String.

EnableSession = true)] public int GetTotalUsage () { return (int) Application["totalUse"]. } } It’s easy to use to get the total usage for the application by just returning the "totalUse" value stored in the Application object.4: Resetting Application Usage [WebMethod (Description = "Reset Global". numSession ++.} int numSession = (int) Session["numSession"].3: Tracking Total Application Usage [WebMethod (Description = "Tracks total service usage". cast to integer type (Listing 13.3). } With the methods in place. Listing 13. run the project. EnableSession = true)] public void ResetGlobal () { Application["totalUse"] = 0. Listing 13. Session["numSession"] = 0. return numSession. Visual Studio will generate the “table of contents” WSDL page for the ASP. .4).1). } Resetting values to zero means simply assigning 0 to the "totalUse" value stored in the Application object and to the "numSession" value stored in a Session object (Listing 13.NET web service (Figure 13. Session["numSession"] = numSession.

. As you can see in the XML returned by the method (shown in Figure 13.1: The service’s methods are listed in the generated pages. Enter a name.2: You can test a service using the Invoke button. and click Invoke to test the method. the method is at least nominally working—it has returned a string indicating it received a name as input.2). Figure 13.3). You can click the StartMe link to see the generated test page for that particular method (Figure 13.Figure 13.

and to reset the application.Ss(). Next.?(You can change the name of the web reference to anything you’d like. so I changed it to “StateService.”) You also need to add to the form a?using directive that includes the System. a Windows forms application.Net. Figure 13. to increment the session counter.Net namespace: using System. in the form constructor. Before you can get started coding the client. and buttons to start the session (and application). . instantiate a StateService object and a CookieContainer. which includes a TextBox for the name of each session. you need to add the Stateful Service as a web reference to the client project (Project > Add Web Reference).4 shows the demonstration client. and connect the two: ss = new StateService.3: The XML shows the return value from the tested method. Consuming the Stateful Web Service The rubber meets the road in the client applications that will use the stateful service.4: The service requires that the user input a name before session tracking begins. CookieContainer cookiePot = new CookieContainer(). Figure 13.Figure 13.

I don't recognize you.StartMe(txtName.Text = "Welcome back.Text). int numApps = ss.Text = "Total named usage of application is " + numApps.GetSession (txtName. .ToString().Text = "Number of times for this named session is " + numSessions.Text = ss. // in the form constructor .GetTotalUsage(). " + txtName. if (numSessions < 1){ this.Text = "Sorry.ToString().ss.CookieContainer = cookiePot. label1.Ss ss. System. stranger!"..EventArgs e) { int numSessions = ss. Listing 13. It’s likewise not very difficult to increment a session and display the session and application counts using the GetSession and GetTotalUsage web service methods: private void btnAgain_Click(object sender. } else { this.5.5: The Client Application Form Code using System. To start the service.Text). invoke the StartMe web service method: this. public Form1() { .. } } The complete web service–related code for the form module is shown in Listing 13..Text + "!". label2. The StateService variable is declared at the form level so it is accessible to all the methods in the form class: StateService.Net..

StateService. label1.Text = "Sorry. } .EventArgs e) { this. " + txtName. ss.Empty.Text = String. stranger!"..Text = "Number of times for this named session is " + numSessions.. } else { this. To try this out.Empty. label1. and click Initialize. label2.Text). System.Text = String. System.Empty.CookieContainer = cookiePot. private void btnStart_Click(object sender.Ss = new StateService.GetSession (txtName.Text + "!". int numApps = ss. run the client project. System. label1. . The session has now started (Figure 13. I don't recognize you.Text = ss.Text).Text = "Welcome back.Text = "Total named usage of application is " + numApps.EventArgs e) { int numSessions = ss. if (numSessions < 1){ this. } .Ss(). label2.ToString(). } } private void btnReset_Click(object sender..ResetGlobal()..Text = String. } private void btnAgain_Click(object sender.StartMe(txtName.ToString().5). CookieContainer cookiePot = new CookieContainer().EventArgs e) { ss.GetTotalUsage(). Enter a name in the TextBox.

You can easily verify that.7). while . in addition to the individual session counters. Figure 13. click Invoke Again. the session is restarted and the tracking counter incremented. Next. Now it’s time to fire up a whole bunch of instances of the client application.7: Session tracking tracks individual sessions.Figure 13. Figure 13. the service initializes session tracking.6: Each time the Invoke Again button is clicked.5: Once the user has entered a name. the global application counter is working—and is tracking the total number of times the service has been accessed by any client (Figure 13.6). It’s easy to see that the session number is incrementing (Figure 13.

NET web application as the client for the web service.application tracking records the total number of times the service has been invoked (by any client). it can make this access available to users who are not otherwise connected to the database. don’t worry! You probably have an Access version of the . can access the data in a DataSet if it is exposed by a web service.” a DataSet is a kind of miniature database that resides in memory—it can include relationships. Exposing Data Using a Web Service As I explained in Chapter 12. While the sample application just fills a grid with the data in a table supplied by the DataSet. this time the DataSet will return the Suppliers table in that database (shown in Server Explorer in Figure 13. This implies that a browser .driven web applications. This means that the simple act of exposing a DataSet using a web service can be quite powerful.platform) access to the potentially extensive contents of the DataSet. Note: If you aren’t connected to SQL Server. Once again. from anywhere on the Internet. we’ll use the sample Northwind database that ships with SQL Server.8: You can use Server Explorer to find the tables and fields in the Northwind database.8).NET.based application. and a substantial amount of data. Figure 13. tables. The example I’ll show you uses an ASP. Furthermore. it’s easy to see how you can use this technique to create data . “Working with XML and ADO. it provides remote (and cross .

SqlDataAdapter theAdapter = new SqlDataAdapter (mySelectQuery. Listing 13. [WebMethod (Description = "Returns DataSet containing Northwinds Suppliers table")] public DataSet GetSuppliers() { string mySelectQuery = "SELECT * FROM Suppliers". and then use the SqlDataAdapter’s Fill method to load the Suppliers table into the DataSet: string mySelectQuery = "SELECT * FROM Suppliers".Northwind database that you can use in its place (a copy ships with most versions of Microsoft Office).Fill(theData. theAdapter.WebService { . "Suppliers"). The GetSuppliers web method should return a DataSet. myConnection)..SqlClient.Data. so here’s its declaration: public DataSet GetSuppliers() { . DataSet theData = new DataSet()...NET Web Service project. [WebService(Namespace="http://bearhome.SqlClient.Services." + "initial catalog=Northwind.password=harold.Web.6. Call the SqlDataAdapter’s constructor with the SQL query to be performed and the SqlConnection")] public class ExposeData : System..Data. .. and a SqlDataAdapter. The first step is to add a new ASP.. See Chapter 12 for details. and return the DataSet.. Close the SqlConnection. using System. Within the web service module. name the web service class ExposeData. SqlConnection myConnection = new SqlConnection ("data source=SQLSERVER. create a SqlConnection.user id=sa").6: Using a Web Service to Expose a DataSet . add a using directive to System. } Within the web method. The code for the web method is shown in Listing 13. Next.. a DataSet. .

see Chapter 12. "Suppliers").Open) myConnection. you must modify the connection string to your circumstances.Close(). } . DataSet theData = new DataSet(). as an alternative to the technique here of creating it in code.." + "initial catalog=Northwind. try { SqlDataAdapter theAdapter = new SqlDataAdapter (mySelectQuery. If you run this project. } catch { // Add exception handling and logging } finally { if (myConnection.Fill(theData. myConnection). For more about connection strings. . Warning: My connection string is not your connection string. see Chapter 12. you’ll see the familiar generated test pages for the web service. where I showed you that you can use the Data components to visually create a DataSet using the Properties window. theAdapter.9). the invocation test page for the method will be displayed (Figure 13.State == ConnectionState. For this code to run on your system. and if you click the GetSuppliers link.password=harold.SqlConnection myConnection = new SqlConnection("data source=SQLSERVER.. Note: For a more complete explanation of the code filling the DataSet. } return theData.user id=sa").

The DataSet is returned as XML. starting with the XSD schema representing the Suppliers table. “Consuming the Service on the Web. and continuing through the data in the table (Figure 13. see Chapter 2. Click Invoke. Open a new ASP.9: The Invoke button starts the web method.10: The XML returned by the method includes both a schema and data.”) . Consuming the Exposed DataSet It’s time to create the client application that will demonstrate using this web service.8.NET web application. Figure 13.Figure 13. Note that the schema structure shown in Figure 13.10 mirrors the fields shown in tabular form in the Server Explorer window shown back in Figure 13.NET web applications.10). (For more about the mechanics of working with ASP.

In the Add Web Reference dialog. and click Add Reference. enter the URL for the web service in the Address box at the top?(Figure 13. .Use the Toolbox to add a DataGrid (shown in Figure 13.12). Figure 13. Set the label’s Visible property to False (it will be used for exception handling messages.11) and a Label to the Web Form Designer. Figure 13.12: A reference is added to the web service using its URL. Add a reference to the web service in the normal fashion.11: A DataGrid added to a WebForm can be used to display the DataSet returned by the service. so we don’t need it yet). by selecting Project > Add Web Reference.

The complete code for the Page_Load event. System.DefaultView. you’ll recall that you can change its name in Solution Explorer to anything you’d like.EventArgs e) { if (!Page. Listing 13.DataSource = theData. let’s change the name of the service to “WebService” (Figure 13. DataSet theData = ed. gridSuppliers.IsPostBack){ try { .NET WebForm private void Page_Load(object sender.13: You can change the name of the web service using Solution Explorer in the interests of clarity.DataBind().13).ExposeData().WebService.Once the reference to the web service has been added to the project.7: Using the Exposed Data in an ASP.Tables[0]. is shown in Listing 13.WebService.ExposeData ed = new SybexC46. gridSuppliers. It’s easy now to add code to the Page_Load event of the web form that instantiates the web method and binds the Suppliers table to the DataGrid: SybexC46. To keep the coding of this client transparent.GetSuppliers().7. including exception handling. Figure 13.

Text = "Please check your SQL Server connection " + "and verify the connection string. DataSet theData = ed.DataBind().SybexC46.DataSource = theData. gridSuppliers. lblExcep. } catch (System. } } } If you run the ASP. } catch(IndexOutOfRangeException) { lblExcep.Message.ExposeData(). provided you can connect to SQL Server and the Northwind sample database is loaded. .DefaultView.". Figure 13.WebService.Tables[0]. gridSuppliers.ExposeData ed = new SybexC46.14: The returned DataSet fills the DataGrid.GetSuppliers(). } catch (Exception excep){ lblExcep.SqlException) { lblExcep.SqlClient.Text = "Please check your SQL Server connection " + "and verify the connection string.Visible = true. the returned DataSet will fill the DataGrid (Figure 13. lblExcep.Text = excep.14).".WebService. lblExcep.Visible = true.Data.Visible = true.NET client application.

Note: If the web service cannot connect to a database for whatever reason.” In the context of the enterprise. it is written to still return a DataSet (but one with no content). Figure 13. then both actions are “rolled back. it will not run on your system as written. The example in this section explores a scenario in which you want to be sure that a log file is written to if the related primary table is altered. In this case. you’ll then be able to take advantage of IntelliSense member completion in the Code Editor. because all kinds of issues can come up with database access.” makes sure that all the actions take place—or none of them do. a transaction. and the next one fails. But there are many situations in which you need to make sure that an entire transaction—or none of its components— succeeds. the most likely exception to be thrown is IndexOutOfRangeException (because Tables[0] will not be found in the DataSet). also sometimes called a “two . as noted earlier in this section.15: It’s important to include exception handling in the client application if the web service involves database connectivity. transactions are important.Note: If you rewrite the code in the web service to return a strongly typed DataSet (rather than just a DataSet).stage commit.15). If one of the actions succeeds. In its simplest form. Since the MessageBox method is not available in a web forms application. one way to display exception messages is in a label (Figure 13. Handling Exceptions It’s important to include exception handling in the client application. One example that is becoming increasingly common in enterprise applications that operate . For example. Managing Transactions A transaction groups multiple actions together. unless you change the connection string in the web method.

In addition. Warning: The example in this section does not implement exception handling—which. A single transaction might need to check inventory on an Oracle server. enterprise architecture. You must alter the connection string to suit your circumstances. The idea is this: there’s a database. These tables are shown using the Diagram feature of SQL Server in Figure 13. to get the example to work.16. they must support transactions.NET web services do support transactions. So if web services are to be part of a scalable. you should in the real world. write order records to SQL Server. Figure 13. Fortunately. Implementing a Web Service that Supports Transactions To start with. Deposits. make sure that Distributed Transaction Coordinator is running in SQL Server.16: The two tables in the Deposits database are shown in a Diagram in SQL Server Enterprise Manager. in both tables. I’d like a web method that inserts a deposit amount. once again my connection string is not your connection string. ASP. Also. with two tables. You can do . and write customer information to a DB2 database. to get this example to work. As you’ll see in the substantially oversimplified—but not entirely unrealistic—example in this chapter.16. Note: As a preliminary. The transaction problem is that I want to be sure that the deposit information is inserted in both tables— or rolled back so that it is inserted in neither. of course. implementing transactions is essentially an issue of turning on a switch. MoneyIn and MoneyLog. you need to create the tables shown in Figure 13. you’ll need to create the database that is described in SQL Server.across operating systems and data repositories is a transaction that needs to perform operations using several data sources. along with an ID and a description.

EnterpriseServices library to the project (Figure 13. start a new ASP. add using directives to the System. In the web service’s class module. and use the dialog to add the System.NET Web Service project. Figure 13. start Distributed Transaction Coordinator. expand the Support Services folder (see Figure Distributed Transaction Coordinator to start it (if it isn’t already). Figure 13.18: Use the Add Reference dialog to add the System.17). Select Project > Add Reference to open the Add Reference dialog.Data.this from the Services applet in Administrative tools.SqlClient and .17: If it isn’t already running. Next. or by using SQL Server’s Enterprise Manager. and right .18).EnterpriseServices library to the project. In Enterprise Manager.

8. TransactionOption.EnterpriseServices.EnterpriseServices namespaces. InsertMoneyLog (inID. string desc) { InsertMoneyIn (inID. desc). using System.SqlClient. desc). and TransactionOption..Web. Change the name of the class to Transaction. Add two private methods.RequiresNew)] The TransactionOption web method attribute nominally has several possible values. float amount. which turns on transaction management.RequiresNew)] public void UpDateTables(string inID.. float amount. InsertMoneyIn and InsertMoneyLog. set to TransactionOption. [WebMethod (Description = "Demonstrates two-stage commit". InsertMoneyLog (inID. string desc) { InsertMoneyIn (inID.Data.WebService { .RequiresNew.System. desc). float amount. which insert into the respective tables. } private void InsertMoneyIn (string")] public class Transaction : System. string desc) { . Listing 13. add a web method. but really only two relevant ones: TransactionOption.8: Inserting Two Tables within a Transaction using System. [WebService(Namespace="http://bearhome. is equivalent to omitting the attribute and means that transaction management has been turned off. which is the default.Services. .Disabled. Next. TransactionOption = TransactionOption. UpDateTables. amount. The code for the web service is shown in Listing 13.RequiresNew: [WebMethod (Description = "Demonstrates two-stage commit".. amount. amount. TransactionOption = TransactionOption. } Mark this web method with an attribute.. that invokes both private methods: public void UpDateTables(string inID. desc). amount.

'" + desc + "')". insert.ToString() + ". myConnection. SqlCommand insert = new SqlCommand(sqlStr.string sqlStr = "INSERT MoneyIn (MoneyInID. } . SqlConnection myConnection = new SqlConnection("data source=SQLSERVER... myConnection.user id=sa").19).Open) myConnection. myConnection). '" + desc + "')".Open(). float amount. string desc) { string sqlStr = "INSERT MoneyLog (MoneyInID.State == ConnectionState. Figure 13.ExecuteNonQuery()." + "initial catalog=Deposits. } private void InsertMoneyLog (string inID. a single web service (Figure 13. SqlCommand insert = new SqlCommand(sqlStr." + "initial catalog=Deposits.password=harold. insert. Description)" + " VALUES (' + inID + "'.Close(). if (myConnection.ToString() + ".password=harold. SqlConnection myConnection = new SqlConnection("data source=SQLSERVER. " + amount.ExecuteNonQuery().State == ConnectionState. If you run the web service. " + amount. Amount.Open) myConnection.Open(). as one would expect. Amount. myConnection). Description) " + "VALUES (' + inID + "'.Close().19: The generated test page for the web service shows . if (myConnection. the test page generated for it shows.user id=sa").

You should verify that the test data has been added to the database tables (as shown in Figure method. Figure 13.20) and click Invoke. Figure 13. so that it throws an exception (and no . Testing the Transaction Web Service Now let’s see whether transaction management is really working! Modify the second private method in the web service.20: To test the service. enter values and click Invoke. Use the test pages to enter some values (Figure 13. the one that inserts into the MoneyLog table.21: You can use Server Explorer to verify that the data has been added to the tables.21 in Server Explorer)—demonstrating that both private methods that insert data into the tables are working.

'" + desc + "')". if (myConnection. Amount. Description)" + " VALUES (' + inID + "'. Figure 13. " + amount. . SqlCommand insert = new SqlCommand(sqlStr. run the project’s test pages again. float amount. SqlConnection myConnection = new SqlConnection("data source=SQLSERVER. try it with new data. } insert. } With the code that sabotages the second insert in place. myConnection.9: Throwing an Exception in the Second Insert private void InsertMoneyLog (string inID. string desc) { string sqlStr = "INSERT MoneyLog (MoneyInID.22).9.Close().22: With the code that throws the exception in the second insert in place.user id=sa").Open) myConnection. and enter some more data (Figure 13. as shown in Listing 13.password=harold. Listing 13." + "initial catalog=Deposits.Open().ToString() + ".State == ConnectionState.longer works). try {} finally { throw new Exception ("This is a nasty exception!").ExecuteNonQuery(). myConnection).

---> System.cs:line 78 at SybexC47.Transactions. and you’ll get a message that looks something like this (I’ve excerpted parts): System.CoreProcessRequest() The key things to note are the methods I’ve placed in highlight. TransactionOption mode) at System.23).HttpException was thrown.asmx.End of inner exception stack trace --at System. which indicate that transaction management was started.Protocols. String desc) in c:\inetpub\wwwroot\sybexc47\transaction.Web. TransactionOption mode.TargetInvocationException: Exception has been thrown by the target of an invocation.asmx. You should also verify that the test data has not actually been added to the first table.Web.cs:line 55 --.Click Invoke.Web.Web. Figure 13. and the transaction rolled back.HttpException: Exception of type System. a transaction aborted.Services.Reflection. Boolean& transactionAborted) at System.Protocols.Transaction.Transactions. Single amount.. String desc) in c:\inetpub\wwwroot\sybexc47\transaction.End of inner exception stack trace --.InvokeTransacted(TransactedCallback callback.Web.Util. at System. Note: One of the steps in the transaction doesn’t have to do something as drastic as throwing an .Web. Single amount.UpDateTables(String inID.Services.WebServiceHandler.Exception: This is a nasty exception! at SybexC47. MoneyIn (Figure 13. If all goes according to plan.Transaction.Util..WebServiceHandler.ExecuteTransactedCode() --. an exception will be thrown.InvokeTransacted(TransactedCallback callback.TransactedInvocation.Web.InsertMoneyLog(String inID.23: You can verify that the test data has not been inserted into either table.Util.InvokeTransacted() at System. ---> System.

Create (qpath).Web.exception to cause the step (and the transaction) to")] public class Mq : System.10: Exposing a Message Queue as a Web Service using System. In fact.Exists(qpath)) MessageQueue. I did note in Chapter 11 that a drawback to using the messaging application included with Windows as part of an application’s architecture is that it limits distributed applications to those running on Windows. create a new ASP.NET Web Service project.Services.\Private$\WebServices". Careful consideration of security issues should take place before any distributed architecture.. “Messaging. string subject){ string qpath = @". Name the web service class Mq. The code for the web service is shown in Listing 13.WebService { .. . This objection can easily be overcome by exposing messaging as a web service.” I showed you how to use message queues to create distributed applications. as the example in the section shows.. These distributed applications could start—and stop—activities by themselves sending and receiving messages. For example. Listing 13.10 (for a more complete explanation of programming message queues. even if the primary key used in the second insertion is not a duplicate—thus rolling back the entire transaction. an attempt to use a duplicate primary key will cause the first insertion to fail. These web methods will operate on a private queue named "WebServices. [WebMethod (Description = "Pushes a message on the queue")] public void SendMessage(string theText.. please refer to Chapter 11). or any architecture that exposes system internals. MessageQueue theQ = new MessageQueue(qpath).Messaging. [WebService(Namespace="http://bearhome. if (!MessageQueue. To start." SendMessage pushes a message onto the queue. Warning: Obviously. Exposing a Private Message Queue Using a Web Service In Chapter 11. and add two web methods. there are security perils involved in exposing something like a message queue in any way—including as a web service. . is used. it’s extremely easy to expose private—or public—message queues as a web service. and GetMessage pops the message queue.

theQ.. } [WebMethod (Description = "Pops a message off the queue")] public string GetMessage(){ string qpath = @".24).theQ..Formatter = new BinaryMessageFormatter(). return msg. If you run the project.Messaging.0. subject). Figure 13. if (!MessageQueue.Receive(new TimeSpan (0. System.24: The generated test pages allow you to test pushing a message on the queue. } .Message msg = theQ. MessageQueue theQ = new MessageQueue(qpath).Create (qpath). theQ. you can use the generated test pages to see whether the SendMessage web method actually places a message on the queue (Figure 13.\Private$\WebServices".Label + ": " + msg.Exists(qpath)) MessageQueue.Send (theText. You can open Server Explorer and expand the message queuing nodes to verify that the message was placed on the queue by the web method: .Body.ToString().10)).Formatter = new BinaryMessageFormatter().

the return string from the web method is the subject concatenated with the body of the message. Figure 13. the message that is currently at the head of the queue is popped off and displayed in the title bar of the application (Figure 13. .It’s also worth running the test page for the GetMessage web method to verify that the message is popped off the front of the queue. The code shown in Listing 13.25: The generated test pages let you check that you can pop messages off the queue. Nothing could be simpler than writing a client app that uses the web service to implement messaging (you need.26).26: It’ s easy to use the web service to access the private message queue.25.11 uses the message . When the Receive button is clicked. Figure 13. to include as usual a reference to the web service). of course.queuing web service to place a message on the queue when the Send button is clicked. As you can see in Figure 13.

27) is a portal primarily intended to enable subscription access to this data.Listing 13. this. by choosing Project > Add Web Reference.Mq ws = new webservice. The site www.Text = ws.EventArgs e) { webservice.11: Consuming the Message Queue Service private void btnSend_Click(object is a “vortal.SendMessage (txtText. System. } Getting the Picture with TerraService Microsoft’s TerraServer database is partially intended to demonstrate the scalability of Micro . since it contains terabytes of aerial and satellite image data supplied by the United States Geological Survey (USGS) and others. and topographic maps—is available as a free web service.Mq().Text). You can add a web reference to the TerraService web service to one of your projects.asmx .soft’s database products.” or vertical portal. In addition to the TerraServer (Figure 13. Figure 13. } private void btnReceive_Click(object sender. the WSDL "table of . much of the data—meaning aerial photographs.GetMessage(). if you enter the URL http://terraservice. relief maps. txtSubject.27: TerraServer. primarily offering subscription access to satellite and topographic imagery. In the Add Web Reference dialog. ws.Mq().terraserver.Mq ws = new webservice. e) { webservice.

29: The Object Browser is the best tool in our arsenal for figuring out how the members of the TerraService work. Figure 13. Figure 13. As you can see in Figure 13. .28).29.28: The web service WSDL page shows the TerraService methods available and provides access to XML documentation. Click Add Reference to add a reference to the TerraService web service in your project. Although XML documentation is available through the Add Web Reference dialog.contents"—showing the web methods available— will be displayed (Figure 13. is the Object Browser. using the Object Browser you can determine the parameter and return types of all available web methods. it is still the case that the hardest thing about working with TerraServer is discovery—figuring out what the various methods do and what they return. once you have added the web reference. Your best tool in this discovery process.

. and interpolate from there.” you may be saying to yourself right about now. the TerraService methods must be used to calculate the lat/lon value of the city center. “I’d like to see my house from above. it’s easy to use the TerraService client to display aerial views of places you care about (my home is at the center of the aerial photo shown in Figure 13. read the lat/lon values for the city.mapblast. This interface lets the user choose a map by city and state or by lat/lon (latitude and longitude) coordinate pairs. in order to display a city map. but how do I find its lat/lon coordinates?” As you’ll see in a bit. It’s probably both more precise and easier to go to MapBlast (www. Whichever way you do it. Create a map based on the and enter your address.ocean setting becomes apparent from this aerial view. I’ve set the user interface of this sample application to display those lat/lon values—so one way you can go about it is to enter your city.31).30. Figure 13. “But wait just a second.The user interface for the sample TerraService client application—with an aerial view of downtown Seattle—is shown in Figure 13. and then click the button to print the map.30: The drama of Seattle’s lake . Lat/lon coordinates will be displayed at the bottom of the printable map. I know the address and ZIP code. and view a printable version of the map.and .

IO.Figure 13.Drawing. As a preliminary to implementing the TerraService client application as a Windows forms project. the user gets to choose whether an aerial photograph or a topographic map is displayed. using System. Figure 13.Drawing.32 shows the client application displaying a topographic map.31: Using latitude/longitude coordinates. you can zoom in on areas of particular interest (this photo is centered on the author’s home). In addition to where.Imaging. you should include using directives to System.IO and System. Figure 13.Imaging in the form class module: using System. .32: The TerraService returns topographic maps as an alternative to aerial photos.

12: Consuming the TerraService (Part I. 95). the ListBox initialization code will look something like this: this.Name = "lstScale".". I also wanted to make sure that an item in the Scale ListBox collection was initially selected.AddRange(new object[] { "Scale1m".lstScale.Items..EventArgs e) { pictureBox1.Loading. it would quite likely be removed the next time the project was recompiled by the form auto .lstScale. so I added the following code in the form constructor: this. "Windows Uses Web Services.SelectedItem = this. "Scale32m".WaitCursor.lstScale.Image = null.Location = new System.generator. this. calls the display method.TabIndex = 0.DoEvents(). and handles exceptions.I used a PictureBox control to display the image returned by the TerraServer web service. Getting User Preferences) private void btnDisplay_Click(object sender. "Scale64m".lstScale. gathers user choices.lstScale.Size = new System.Text = ". this." for more of a discussion of hidden form code). You should know that in addition to the code shown in Listing 13..Size(88.. Application.lstScale. this. . "Scale2m".12 shows the portion of the code that manages the user interface.Drawing. "Scale16m". If you expand the hidden area. Cursor = Cursors. so the issue is setting the Image property of the PictureBox control.Items[2]. This means that these values are part of the "hidden" form code (see Chapter 3. Listing 13. "Scale128m"}).12. this.Drawing.lstScale. Note: If I had put this in the hidden ListBox initialization code.Point(24. "Scale4m". 64). System.. I entered the possible string values for the Scale ListBox into its Items collection using the Properties window. Listing 13. Too!. this.

place.Text = point.Text = point. point = ts. // Display the lat/lon used for the city txtLat.Scale theScale = net.Place place = new net.Place ().Text.Text).terraservice. } place.try { net.State = txtState.ToString().Text = "San Francisco". // Get the scale picked by user switch (lstScale. txtLon.City = txtCity.Scale4m.ToString().LonLatPt().terraservice.Photo.Country = "USA".Topo.terraservice.Text). } net.Checked == true){ net.terraservice.terraservice.TerraService ts = new net.terraservice.Theme Theme.terraservice. // No empty cities or states if (txtCity.Text.Lon.terraservice. place.ConvertPlaceToLonLatPt (place).Theme.terraservice.Scale.TerraService ().LonLatPt point. txtState. if (rdoPhoto. // Get the latitude and longitude of the requested city net.ToString()){ case "Scale1m": .terraservice. } else { // by lat/lon point = new net.Checked == true) { Theme = net. point.Text == String.Lat.terraservice.SelectedItem. } else { Theme = net.Lat = Convert.Theme.Empty){ txtCity.Text == String.Lon = Convert.ToDouble(txtLat.Text = "Ca". if (rdoCityandState.ToDouble(txtLon. point. } net.Empty || txtState.

} catch (Exception excep){ MessageBox.Scale8m.terraservice. case "Scale8m": theScale = net.Scale4m. default: theScale = net. break.Scale. break.Scale32m.Scale4m.Services.Scale.terraservice. break. MessageBoxIcon.Scale.Scale.terraservice. "TerraService"." + txtCity.Information). break.Scale64m.Image = GetTiledImage (point.Scale.Scale2m.Web.Text = "TerraService loaded .Message.Show (excep.terraservice. break. theScale.theScale = net. 640.Scale. break. if (rdoCityandState. } finally { Cursor = Cursors.Theme. } // Time to rock and roll .SoapException){ MessageBox. "TerraService". case "Scale4m": theScale = net.Show ("Unable to return requested map or photo".Scale16m.terraservice. MessageBoxButtons.Text + . case "Scale64m": theScale = net.terraservice. case "Scale32m": theScale = net. } catch (System.Protocols. break.let’s get the picture pictureBox1. case "Scale128m": theScale = net.terraservice.Checked == true){ this.Scale1m. 480). MessageBoxButtons.Scale.Scale.OK. break.Exclamation).terraservice. MessageBoxIcon.OK. case "Scale16m": theScale = net.Scale128m. case "Scale2m": theScale = net.terraservice.Scale.Default. break.

txtLon.12. a scale constant.12. The return value of this method. 480).LonLatPt point. theScale.ToString(). photo or map). Obtaining the Tiled Image) private Bitmap GetTiledImage (net. net. once all the user choices have been gathered and the city (if selected) has been converted to a lat/lon point.Scale Scale. 640. In Listing 12.Lat. Lon: " + txtLon. GetTiledImage is passed a lat/lon point. int cx. and a size in pixels.Text. I took advantage of having this information to display it back to the user.Text. net. is loaded into the Image property of the PictureBox.Text = "TerraService loaded .Text = point. } else { this. Listing 13. // Display the lat/lon used for the city txtLat.13.terraservice. Theme. using the AreaBoundingBox structure returned earlier by the web service to determine the corner coordinates for the required image. a theme constant (e.Text + ". } } } Note: that in Listing 12. An iteration routine is needed to piece together an image of the proper size. " + txtState.g.ConvertPlaceToLonLatPt (place). As you can see..13: Consuming the TerraServer (Part II.Lon.Lat: " + txtLat.Theme Theme. the web service method ConvertPlaceToLonLatPt is called to convert the city center to a lat/lon point: point = ts.ToString().Text = point.terraservice. the private method GetTiledImage is called: pictureBox1. int cy) { // Instantiate the TerraServer proxy class .". As you can see in Listing 13. the tricky part about this is that TerraService GetTile method returns a 200?200 pixel image—too small to be much good. which does the actual dirty work of retrieving imagery from the TerraService web service. if the user elects to see a photo or map by city and state.Image = GetTiledImage (point.terraservice.

Offset.y) * tile.Id.Height).NorthWest.NorthWest. Image tile = Image.TileMeta. Conclusion May the circle be unbroken! May the last note be as sweet and elegant as the first note! C# is a powerful.Width .use language—as I hope you’ve seen in this book. pf).Width.Height .terraservice.TerraService ().TileMeta. (x . and easy .TileMeta. cx.Dispose().Y.GetTile (tid))).X.x1) * tile. x++) { for (int y = y1. tile. int y1 = abb. x <= x2. Scale. tile. Theme.NorthEast. and lots of fun too! You can use the application that I’ve shown you in this section as a starting place.Offset. cy. Bitmap bitmap = new Bitmap (cx.terraservice. } The TerraServer web service is amazing.Y.SouthWest.(Int32) abb. y >= y2.Id. Graphics g = Graphics. for (int x = x1. int x1 = abb.NorthWest.Id.(Int32) abb.NorthWest.Y = y.GetAreaFromPt (point. g. tile. intelligent.TerraService ts = new net.DrawImage (tile.FromImage (bitmap).Id.X. .TileMeta. and add aerial photos and maps as appropriate to your own applications.FromStream (new MemoryStream (ts. int y2 = abb. y--) { net. tid. // Compute the parameters for a bounding box net. // Create an image to fit the bounding box PixelFormat pf = PixelFormat. cy). int x2 = abb. (y1 .to .Format32bppRgb.YOffset.TileMeta. } } // Return the resulting image return bitmap.terraservice. tid.AreaBoundingBox abb = ts.NorthWest.Id.X = tid = abb. In the beginning.terraservice.XOffset.

NET Framework’s class libraries.NET were discussed. In the next part. we created Windows user interfaces and started our exploration of the . XML.NET applications using C#. We explored the C# language in detail and looked at arrays and collections of objects—and then looked more at objects in C#. In the final part. the courante.eye view. Files and streams. The dance really heated up in the third part. the gigue. as things strode forward. and observed life from a string’s . we created a simple ASP. In this chapter. we started to close the circle. . and elegant . The end is not quite the same as the beginning: the primary concern in this chapter was how web services fit in as part of a larger picture—in the case of the TerraServer web service. messaging. and ADO. the allemande. we headed back to web services for a last visit. and that it helps you to build the first part of this book. indeed! I hope you’ve enjoyed my book.NET web service. the prelude. a very large picture. then we got to know ASP.NET web forms applications and learned how to consume the web service. literate.

NET’s help system is comprehensive. The Dynamic Help window will open.sensitive help in which a window provides links to help topics. For a programmer with limited time. select Help > Dynamic Help or press Ctrl+F1 on the keyboard. C# .NET help system. and one of the most important skills in an environment as complex as C# . This appendix briefly explains the mechanics of how to use the Dynamic Help. Search.NET is being able to find help when you need it. As you can see in Figure A.1. Contents. the big. it also contains myriad “kitchen sink” topics you will never need. The help system available to C# might be dubbed “the good. Dynamic Help. To activate Dynamic Help. adrift in a vast sea of information.) In addition. the Dynamic Help window provides information about whatever is currently active in the environment . The auto . Dynamic Help may appear as a tab in another window.” because it includes documentation for the entire . overwhelming. But it is still the case that nobody knows everything. Index. and not always well organized.NET Framework as well as the C# language. depending on the selection in the development environment. Along with the precious documentation gems that will help you solve your real . The good news is that one innovative feature. choose Help > Help on Help.which can be very helpful. (To access this topic. In other problems. Dynamic Help and F1 Dynamic Help is a form of context . such as the Properties window. and the ugly. often the only way to find what you’re looking for is to browse through a great many topics. rather than in a separate window. the trick is to efficiently find the buried nuggets of information—which this appendix will help you to do. If this is a . which shows the links in the Dynamic Help window when the MainMenu control is selected in the Toolbox.completion features of the Code Editor make it less likely that you’ll need help with basic language syntax or with knowing the members of an object. can make life a great deal easier. The help system is so complex to use that you may find yourself referring to the “Help on Help” topic to understand how the various features work.Appendix: Using C# . and filtering features of the C# .NET’s Help System Overview No matter how experienced a programmer you are. Note: Depending on how you have your Visual Studio environment set up. sometimes you need help understanding a concept or term or figuring out how a feature works. indeed! There's no doubt that running with Dynamic Help turned on slows down the Visual Studio development environment.

you should probably bear in mind that the F1 key still provides 'good. Select a topic to display it.1: When the MainMenu control is selected. If you are working with Dynamic Help turned off. select Help > Contents. Figure A. Figure A.3 shows an example of the Visual Studio . To open the Contents window. Contents The Contents window is used to drill down to find information using nodes and links.2: The Contents window: its highest level (left) and some expanded nodes (right).fashioned' context . Figure A. old .2.NET topic page. When you press F1. it is likely that you will want to work with Dynamic Help turned off to gain an extra bit of speed.significant concern because you are running on older (and slower) hardware. as in Figure A. Dynamic Help shows topics related to the MainMenu. . Click the nodes to expand them to show further nodes and topics. it will show further links. The Contents window will open with a few top nodes showing.sensitive help. a help topic relevant to the current active object in the IDE opens in a window. Undoubtedly.

From left to right. and vice versa? .3: Selecting a topic in the Contents window takes you to the topic's help page. Since these icons are not available for all topics. Use the Filtered By drop . You'll most likely want to filter information using the Visual C# or Visual C# And Related settings. details platform requirements for the item featured in the help topic-not very useful. This is actually quite convenient. can be filtered by topic. so that it will show only the information related to the filter. For more information. Requirements Represented by a check mark.Figure A. The Contents window.left of the help topic window.down list at the top of the window to choose a filter. helpful for finding other help topic articles that are relevant.NET Framework SDK'). Language Filter Represented by something like a curved letter T. why should you want to see the VB code. Navigational Aids within a Help Topic Window Many help topics have additional features that can be accessed using icons found at the upper.if you are coding in C#. don't be surprised if you do not see them (they are most commonly associated with help topic items that have been filtered by '. see the 'Filtering' section later in this appendix. like the Index and Search windows. the icons enable you to access the following features: See Also Represented by a squiggly up arrow.

Index The Index window allows you to look for specific information. If you select a term in the bottom pane. With the Index window open. by selecting Help > Index Results. Search The Search window works like the Index a listing in the Index Results window. the topic will open for viewing. enter the term you are searching for in the Look For box: A list of indexed terms. the Index Results window will open. based on the phrase you entered. . select Help > Index. showing all the indexed listings for the term: If you double . not just on the indexed titles. To open the Index window. will appear in the bottom pane of the Index window. except that it produces more 'hits' because it searches within help documents. it will open immediately when you select it in the Index window (rather than opening the Index Results window). You can open the Index Results window directly once you have already done a search. Note: If there is only one topic indexed for a given search phrase.

As opposed to the Index Results window. by selecting Help > Search Results. the Search Results window is likely to show a great many hits . Figure A. The Help Toolbar Many of the topics that result from searches are sections of larger articles. I find the ability to search through previous results particularly useful.To open the Search window. You can open the Search Results window directly once you have already done a search.possibly in the hundreds. as you can see in Figure A. For example. you see .4. select Help > Search. The Search window offers four options: Tip: The Search window is the tool I use most frequently to find things.4: Search Results can be very extensive.

down box. Tip: . as though you were writing the WHERE clause of a SQL query (which is likely exactly what you are doing under the covers!). It's part of a larger topic called 'ASP. These let you scan the help in the order of the help contents. select '(no filter)' in this drop .NET Caching Features. Filtering Help Results As I noted in the section on the Contents window.'Caching Versions of a Page' show up in the search results. However. show your Start page by selecting Help > Show Start Page.down list. simply to keep your help searches manageable. Using this window. and it will show you where the topic fits in the grand scheme of help information (by displaying the topic's location in the Contents window). select My Profile.NET Framework SDK is another very useful filter. These appear in the toolbar as an up arrow (Previous topic) and a down arrow (Next topic). Note: You can also set the help filter on your Start page. and set the Help Filter using the drop . it is likely that you will wish to filter by Visual C# or Visual C# And Related. help results can be filtered using the Filtered By drop down list in the Contents. or Search window: To turn filtering off. you can click the Sync Contents icon-a horizontal doubleheaded arrow-in the toolbar. It is an interesting fact that you can customize filters and create new filters using Boolean criteria. first . select Help > Edit Filters. To open the Edit Help Filters window.' but how would you know that? When you have a topic open. To do this. you can edit the definition of a current filter or select New from the menu bar to create a new filter. You'll also find the Previous Topic and Next Topic features helpful. With the Start page open. Index. To edit a filter.

5 shows an example of the Edit Help Filters window with the Visual C# And Related filter selected. Figure A. .NET filter). This gives you an idea of the comparative breadth of each topic (both totals are slightly less than those yielded by the comparable VB .down list. To find out how many topics are included in a given filter. and the Visual C# And Related filter has 53.093 topics. Figure A. you may find it relevant to know that the Visual C# filter has 8.311 topics.6 shows an excerpt from this list.6: Use the attribute list to edit your filter it in the Filter drop . click the Calculate button in the Edit Help Filters window. For example. you'll find a list of the available attributes that can be used together with Boolean operators to create a filter definition. Figure A. Figure A. At the bottom of the Edit Help Filters window.5: You can use the Edit Help Filters window to customize help filters using a Boolean syntax.

Sign up to vote on this title
UsefulNot useful