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.

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

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

If you don't see Solution Explorer. To see all the files in the project. Many of the files that make up the web services project are now displayed in Solution Explorer. the designer for the web service module is displayed. Solution Explorer Visual Studio's Solution Explorer is used to view the different modules that are parts of projects and solutions. open it by selecting View > Solution Explorer. When you are building a Windows or web application. 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.dandy thing indeed.10).9: When the new project is opened. So close the designer. . We won't be needing it.they consist of class members created in code. not having a visual interface at runtime). because you can visually drag and drop controls from the Toolbox onto the designer and have an instance of the control automatically instantiated. click the Show All Files button in the Solution Explorer toolbar (Figure 1. because the act of creating a web service means the creation of classes in code . But this really doesn't buy you much bang for your buck. are not visual . however. to navigate between these modules.Figure 1. onto the designer and have the code that instantiates it automatically generated (a component. the designer for the Windows or web form is a very handy . Most of the time the designer is not used with web services. such as a Timer. Web services. as opposed to a control.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.

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

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

style comments. although you should have a general idea of what you are likely to find when you open one of these modules. the code on the line of the #region directive. I'll provide an overview of how this works towards the end of this chapter. 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. which can be automatically rendered into documentation for programs. C# also supports so . . Lines beginning with three forward slash marks (///) are also comments . Everything between the begin and end marks is a comment. which begin with /* and end with */. not shown in Listing 1. If you are not familiar with C# at all. but any subsequent code up to #endregion is hidden in the collapsed block. #region Component Designer generated code is displayed. you should know that lines beginning with two forward slash marks (//) are comments (this is sometimes known as C++ comment style).but of a special sort. which can span multiple lines.2.11. As you can see in Figure 1.called C . They are designed to contain XML documentation of the code.

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

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();

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

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

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

For example. 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. right click. . select the project in Solution Explorer. To generate an XML documentation file from these tags. For a complete list.formed. This is the answer to a common problem: no one ever has time to document code until it is too late. provide a value for the XML Documentation File property (Figure 1. On the Build page. } this. If you insert some simple XML documentation tags as you go along.Text. These comments can be used to automatically generate XML documentation for a program.1) + strNew. With the property pages for the project open. } } } 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 (///).{ strNew = this. select Configuration Properties. you won't have to worry about this.Substring(i. The next time the project is built. Two of the most commonly used tags are <summary></summary> and <remarks> </remarks>.Text = strNew. reverses it. an XML documentation file with the name specified will be created.20). /// and writes it back to the Text property /// </summary> This summary will then appear in the generated documentation file. provided the XML used in the tags is well . see "Tags for Documentation Comments" in online help. and choose Properties from the context menu.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

clicking the localhost reference and selecting Rename from the context menu. which is also available on the context menu for the reference. Note: It's important to know about the Update Web Reference choice.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.popular sobriquet theService by right . Use this when the web service has changed and you want to update the . Go ahead and change its name to the ever .

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

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

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

} 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 expression that is the same backward and forward . Just to make things a little more compares the two strings input.EventArgs e) { theService.ToUpper() == String2. 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. both . } As you can see. } return true.SybexC2Service(). in case some user wants to make fun of this application. string String2) { if (!(String1. Here's the function that checks whether the input is a palindrome: public bool Palindrome(string String1. this Palindrome function is pretty basic stuff .has been entered. } if (String1.btnRev_Click).EventHandler(this. we're going to check to see whether a palindrome .Click += new System.ToUpper())) { return false.EventHandler(this.Load += new System.ReverseString(txtInString. this.Text). } Next.Length == 1) { return false.Page_Load). return the reversed value of the string entered by the user in a new string variable.SybexC2Service theService = new theService. System. result: string result = theService.private void InitializeComponent() { this. Within the event handler.

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

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

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

GoToSleep(DelayInSec). } } Note: The Thread. it needs to iterate up to the square root of the number).Sleep(DelayInSec * 1000). It's important to make sure that the method is not passed an argument of zero (0).854.Threading. int DelayInSec) { ServiceSupport.destination.036.775. at least if the number is within the bounds of the long data type ( 9. it will throw an error. The web method needs to be changed to invoke the new function: [WebMethod (BufferResponse = false. avoiding this is achieved with the conditional check that DelayInSec > Of course.775. CacheDuration = 0)] public bool IsPrime(long NumToCheck. 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.854. we're going to add a parameter to the IsPrime web service that delays the method's return by the designated number of seconds. 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. return ServiceSupport. so the multiplication by 1000 is needed to convert the delay into seconds. which would suspend the thread indefinitely.Sleep method takes an argument in milliseconds.808 to 9.036.Thread. if the number input is out of bounds.IsPrime(NumToCheck). In order to actually witness any asynchronous behavior. right? Given the efficient nature of the algorithm for determining primality used in the IsPrime web method (at most. } . we're going to add code to the IsPrime web method that will slow it down on demand.

Listing 2.WebService { .exe to create the proxy module.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. namespace SybexC2 { [WebService (Description="Chapter 1 demo program".14).com/webservices")] public class SybexC2Service : System. Listing 2.Web.SybexC2Service using System.Services.bearhome. If you go back to the SybexC2 project developed in Chapter 1 and run it with the revisions.14: The generated test page for the revised web service now shows two parameters. 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. Namespace="http://www.Services. Note: If you've already generated a proxy that references this method in a project. you should delete the current module. you should update it by selecting the web reference in Solution Explorer.4 shows the entire revised web method. and add the newly generated module.4: The Extended IsPrime Web Method // SybexC2. right .Web. run wsdl.clicking. If you used wsdl. Name="SybexC2Service".exe again. and selecting Update Web Reference from the context menu. Figure 2.

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

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

... and EndNamed. methods are intended for use with asynchronous calls. This procedure writes periods (.. 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?). you'll find a BeginIsPrime and an EndIsPrime.16: A synchronous call to the web method easily determines whether a number is a prime. Asynchronous Consumption If you looked at the code for the auto . in addition to IsPrime. method is used synchronously. . The general pattern is that for each NamedWebServiceMethod included in the web service. the proxy class includes three methods: • • • NamedWebServiceMethod BeginNamedWebServiceMethod EndNamedWebServiceMethod The Named... whereas the BeginNamed.2. In the proxy class for the SybexC2 service.. Here's the click event procedure code that invokes BeginIsPrime asynchronously.Figure 2.. The point of the example is to demonstrate something else happening while the web method call is waiting to return.generated proxy module back in Listing 2.

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

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

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

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

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

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

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

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

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

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

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

} #endregion /// <summary> /// The main entry point for the application. } } base. /// </summary> [STAThread] static void Main() { Application.Run(new Form1()). which is the base class for all controls.300).Size = new System. 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. this.Dispose().Form. this.ComponentModel.Drawing.Container().components = new System.protected override void Dispose(bool disposing) { if (disposing) { if (components != null) { components. a class that provides .Size(300. /// </summary> private void InitializeComponent() { this. Form's immediate parent is not modify /// the contents of this method with the code editor.Text = "Form1".Dispose(disposing).Forms. } #region Windows Form Designer generated code /// <summary> /// Required method for Designer support . } } } As you can see in the class declaration in the form code. the newly minted Form1 object inherits from System.

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

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

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

time position of controls.None.5: The Dock property simplifies managing the run .Fill to the Dock property.Top. The Anchor property describes which edge of the container. this. the control is bound to.Right. The Dock property "docks" the border of a control to its container (such as a form).Left.Fill means that the control takes up all unused space in the center of the form. DockStyle. or DockStyle.For example. DockStyle. . The distance between the form edge and the closest edge of the control will remain constant no matter how the form is resized.FormBorderStyle = FormBorderStyle. Figure 3.5) or by assigning a value of DockStyle. since most programs do not change window styles at run time). DockStyle.Bottom. turns the form into a borderless window when it is executed (although it is unusual to execute this code.None means that the control is not docked. DockStyle. such as a form. and DockStyle. 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. Dock can be set visually (Figure 3.

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

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

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

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

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

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

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

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

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

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

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

5. Here are the subject and caption.Show method.Windows.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. let's use it to write an entry to the system logs. add a Button named btnService to a form. or DialogResult for short.Show. These values are the members of System.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. and Cancel buttons Table 3. First. The Click event of btnService will be used to display the message box.Show works. shown in Table 3. and a parameter designating the default button: .YesNoCancel Displays Yes. The elements of the message box will be placed in variables and then displayed using the MessageBox.Forms. string caption = "MessageBox Demo". No. icon.DialogResult. followed by the buttons. Table 3. string subject = "Service Notification by SybexC6".

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

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).click the notification and select Properties from the context menu to view the full text of the log entry (Figure 3.10: The new entry can be viewed using Server Explorer (shown here) or using the operating system's administration facilities.Figure 3. .11). Note: Depending on your operating system. Figure 3. and looking for a recent Application Popup entry in the System log. You can right .11: Expanding the entry in the System log shows the full text of the message box. You can expand the entry in the Event Viewer to find the text of the message box.

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

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

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

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

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

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

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

MessageBoxButtons mbb = MessageBoxButtons. break.Exclamation.Controls) { if (c is RadioButton) { RadioButton rb = (RadioButton) c. break.. if (rb. and Ignore": mbb = MessageBoxButtons. break.Text) { case "Abort.7 shows the complete code for this variation.OKCancel.Checked == true) { switch (rb.OK.AbortRetryIgnore. case "OK and Cancel": mbb = MessageBoxButtons. string caption = txtMsgCaption. case "OK": mbb = MessageBoxButtons.RetryCancel. Listing 3. case "Yes and No": . System.grpButtons. break.OK. break. 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 . OK.completion facility). case "OK and Cancel": mbb = MessageBoxButtons.Text.7: Variation on the Message Box Using a switch private void btnVar1_Click(object sender. Retry.break. case "OK": mbb = MessageBoxButtons. . Listing 3. foreach (Control c in this.Text.OKCancel. case "Retry and Cancel": mbb = MessageBoxButtons.OK. MessageBoxIcon mbi = MessageBoxIcon. break. DialogResult answer.EventArgs e) { string subject = txtMsgText..

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

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

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

for the Form1 class (see Listing 3.1)..Figure the form load event or in the form's constructor. it may not have the normal mechanisms for closure. } .in this example..Forms.. 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.. public Form1() { .. In C#.Windows.Form { . ApplyInitialRegion() . as in the example here. you can find the constructor within the class: public class Form1 : System.. Make sure that you provide a way to close the form (such as a Close button). } You'll find more about constructors in Chapter 8. because the top part of form has been eliminated. . You can open a form with whatever shape you'd like by placing the call to the method that changes the form's shape .15: The Region property of the form can be used to make it any shape you'd like. Warning: Depending on the shape you choose for your form. for example. the constructor is a public procedure within the class with the same name as the class.

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

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

Now you can go ahead and put whatever code you want in for execution while the asynchronous call is completing.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. cb. 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.SybexC2Service cService = new theService. If 10 seconds is too long for you to wait.Show("I can do other things!". MessageBox. don't have to wait. System. Note: Note that the second parameter sent to the BeginIsPrime method is the delay in seconds.SybexC2Service(). 10. instantiate an AsyncCallback object. MessageBoxButtons.EventArgs e) { theService.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. passing it the ServiceCallback procedure as an argument so that it knows where to send the results: AsyncCallback cb = new AsyncCallback(ServiceCallback). let's start coding. With the user interface in place. ServiceCallBack. Next. we'll write some text to a label and display a message box: lblResult. MessageBoxIcon. and rename it theService. "AsyncCallback Demo".. First.OK. In our case. following the process explained in Chapter 2. create a new procedure..BeginIsPrime(Convert. you can replace it with a smaller integer.Text). create a variable to hold an instance of the service: private void btnAsync_Click(object sender. Assign the return value of BeginIsPrime to the IAsyncResult object that is passed to ServiceCallback: IAsyncResult ar = cService. .Information). You must also add the SybexC2 web service to the project as a web reference. cService). and the web reference added.

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

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

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

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

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

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

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

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

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

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

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

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

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

Retrieving by Index You can also retrieve the text of an item by using its index in the Items collection. } } 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.Information). "Here is your text".Show("Checked").ToInt16(txtIndex. MessageBoxIcon.Items [Convert. } else { MessageBox.Show("Unchecked"). the item is added to the new CheckedListBox (and marked checked): . You can do this by iterating through the Items collection of the first CheckedListBox.Text))) { MessageBox.MessageBoxButtons. Each time a checked item is found.Information). System. For example: private void btnRetrieve_Click(object sender. MessageBoxIcon. MessageBoxButtons.ToInt16(txtIndex.Show (checkedListBox1. Convert.5: It 's easy to retrieve selected text.Text)].OK. } } Figure 4.OK.ToString().EventArgs e) { MessageBox. if (IsItChecked(checkedListBox1.

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

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

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

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

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

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

which allows you to edit in place the internal names of the menu items you are adding. By right . Let's use the visual interface to add some menus and menu items.Figure 4. Ma. Delete. I'll just look at the first menu. and then check out the autogenerated menu code that Visual Studio has created on our behalf. .8 is in the normal place of a File menu. and ListBox. Table 4. no code' theme of the menu designer. To keep things simple. Retrieve. the menu insertion is performed in Edit Names mode. The application will have four menus running along the top of the form: Add. you can edit the menu visually (shown here in Edit Namesmode). 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.8: When you add a MainMenu control to the tray.9).clicking and selecting Edit Names from the context menu. The menu item names and text are as shown in Table 4. the Add 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.1 (with access keys ignored). and so on. which as you can see in Figure 4. 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.

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

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

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

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

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

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

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

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

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

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

Figure 4.13: The Font common dialog is used to set the font and related attributes of the selected text. The OpenFileDialog control lets the user select a file for opening using the Open dialog. 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.Figure 4. To say it once again: designating the file is all these dialogs do. We'll let the RichTextBox do the lifting here. they don't do any of the actual work of saving or loading.14: Changes to the text formatting are displayed in the RichTextBox. for more general .

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

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

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

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

Right. Open frmParent in its designer. Use the Properties window to set the Anchor property of btnShowChild to Bottom. use the Properties window to set the IsMDIContainer property of frmParent to true (Figure 4. Next. . In the Add New Item dialog. Figure 4. The new parent form will be added to the project.16). to the right side of the panel (the bottom . Add a panel to the bottom of the form. 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. make sure Windows Form is selected.cs. btnShowChild.right of the form). Choose Project > Add Windows Form to open the Add New Item dialog (Figure 4. and a button. and click Open.17).

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

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

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

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

switch (mnu. Listing 4. Menu.Text) { . case "&Tile Horizontal": this.MenuItems. . Note: that the menu shown in Listing 4.. // Tile Vertical MenuItem mnuTileV = new MenuItem("Tile &Vertical").LayoutMdi(MdiLayout.Add(mnuTileH). mnuTileV.9. The LayoutMdi method of the parent form is then used with the appropriate argument to arrange the child windows as shown in Listing 4.8 does not implement functionality in child window arrangements.MenuItems. shown in Listing 4. it merely invokes a common event. EventArgs e) { MenuItem mnu = (MenuItem) sender. The MDIMenu_Click event handler. Menu. break. case "Tile &Vertical": this. break.Add(mnuTileV).MenuItems[0].Add(mnuArrange). 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.LayoutMdi(MdiLayout.9: Implementing MDI Window Functionality void MDImenu_Click(object sender.Cascade).mnuTileH.TileVertical).Text) { case "&Cascade": this.Click += new EventHandler(MDImenu_Click).9..Click += new EventHandler(MDImenu_Click). break.MenuItems[0]. for each choice.MenuItems. Menu. switch (mnu.LayoutMdi(MdiLayout.MenuItems[0].TileHorizontal).Click += new EventHandler(MDImenu_Click). mnuArrange. // Arrange Icons MenuItem mnuArrange = new MenuItem("&Arrange Icons"). MDImenu_Click.

Visual Studio. } } If you run the application. you'll see that it is fully functional within normal parameters for an MDI application (Figure 4. and the . more generally. 'Reflecting on Classes.NET Framework.based applications? What is the vocabulary used to notate these patterns and how are they recognized? .NET Framework to create functional and exciting user interfaces. break. Conclusion This chapter . are there patterns of practice that are useful in constructing class .case "&Arrange Icons": this.19: It's easy to create MDI applications. Figure 4.LayoutMdi(MdiLayout.19).and the previous one .has explored using Visual C#.' we'll move on to have a look at the organization of the classes within the . In Chapter 5. 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.ArrangeIcons).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

The Members Pane The Members pane shows the members of an object selected in the Objects pane.such as the classes that it is based on .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. see Chapter 8. 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. constants.will be displayed in the Objects pane.Drawing shows the Color class. This information isn't the same for all objects. so expanding System.Drawing. In addition. (For more on this topic.) The Description Pane The Description pane provides a great deal of information about the object currently selected in the Objects pane. 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.) If you fully expand an object in the Objects pane. Members mean properties. and enumeration values. variables. events. A different icon indicates each different kind of member.Drawing. Here is an example of the Objects pane with System. one other icon. means the member is protected. Clicking the + or . a key. . (Color is a member of System. a great deal about the object . methods.Color selected and some of the members of Color visible in the Members pane. They are immensely valuable for quickly gathering information about an object.

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

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

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

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

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

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

} } Invoking the Class Members Open a new Windows forms project. shown in Figure 5. e). Figure 5. } } m_Age = value. public event BirthDayEventHandler OnBirthDay. as explained earlier in this chapter.EventArgs e). . To do so. } // Event declarations public delegate void BirthDayEventHandler(object sender. open the Add Reference dialog (Project > Add Reference).right corner. } } // Method public string GetSound(){ return "Waaah!". System. is used to locate the assembly containing the class library. In the Select Component dialog. 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. locate SybexC12 and click Open. and click the Browse button in its upper .OnBirthDay(this.16. opened from the Add Reference dialog.16: The Select Component dialog.

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

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

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

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

} . and expressions. methods. objects. quite wondrous. operators. glued together using a distinctive syntax. string DEEFLAT = "DEEFLAT". and DEEFLAT are all different. DeeFlat. Identifiers Identifiers are names used to denote variables. in fact. and so on. C. the three variables deeFlat. O brave new language! The introduction of a new language is a rare event. While C# borrows from Java. in the opinion of this author. C++. C# identifiers are case sensitive. wonder!' exclaims Miranda in Shakespeare's play The Tempest. Nor. System. variables. it's especially important to pay attention to the rules of the language. does it have the pharmacopoeia of Aldous Huxley's famous dystopia. C# does have things like types. a brave new language: C#. Brave New World. Variables!".EventArgs e) { string deeFlat = "deeFlat". and since language and syntax are the blocks on which programs are built.and. as running this click procedure will show: private void btnClickMe_Click(object sender.OK). It has no 'creatures' in it. for that matter. An identifier begins with a letter or an underscore and ends with the character just before the next white space. string DeeFlat = "DeeFlat". '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 even Visual Basic and Pascal. constants. "Hello. MessageBox. Since C# is new. types. it is a truly new language . This chapter reviews the nuts and bolts of the C# language.Show(deeFlat + " " + DeeFlat + " " + DEEFLAT. MessageBoxButtons. For example.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.

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

Variables A variable combines a type with a way to store a value of the specified type.) The value of a variable can be assigned. (I discuss types later in this chapter.Table 6. C#' using the online help's Index facility. The online help topic is then hyperlinked to the definition of each keyword. and that value can also be changed programmatically at any point.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. A variable is created in C# by declaring its type and then giving it an identifier. but you are certainly already familiar with some of the C# types. . string deeFlat. For example: int theInt. such as int [integer] and string.

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

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

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

OK. as shown in Figure 6. MessageBoxButtons.Figure 6. MessageBox.2.Exclamation).completion feature of the Code Editor supplies the members of an enumerator list. Figure 6.Show(companyMotto. "Sir!".1: The auto .2: The members of an enumeration constant list are shown in the Object Browser. You can also find member information for an enumerator list in the Object Browser. . MessageBoxIcon. 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!".

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

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

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

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

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

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

ToInt16(rb. and the value 6 is displayed: . The value of theFloat will be rounded up and off to 4 when it is added and assigned to the integer X.NET (with Option Strict disengaged).5 Dim X As Integer = 2 X = X + theFloat MessageBox.the 'Explicit Conversion' section later for information about how to do this). Dim theFloat As Double = 3. Type conversion can get pretty convoluted.ToString()).Convert object A conversion to type string using the ToString method inherited by all objects from System.Show(X) the program will run without syntax errors. Next. in the message box statement. For example.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. and can involve multiple conversions within a single statement. 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. the integer argument X is automatically converted to a string type.Tag. If you run the following code in VB . the statement mbb = (MessageBoxButtons) Convert.

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

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

or decimal int. ulong. float. or decimal short. double. long. double. F = X. no special syntax is required to perform implicit conversion. ulong. float. ulong. long. or decimal float. float. // implicit conversion Legal Implicit Conversion To short. or decimal long. double. uint. int. As its name implies. There are no implicit conversions allowed between the floating . or decimal implicitly (and successfully) converts an int value to type double. uint.point types and the decimal type. For example. ushort. double. long. int X = 2. double. int. or decimal types. long. or decimal int. uint. float. double. or decimal ushort. float. double. 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. or decimal double float. float. including assignment statements and method invocations. which can take place in several situations. Here's another example that implicitly converts an int to a long in a method invocation (the method takes a long as its argument): . int. double F. ulong. There are no implicit conversions allowed to the char type. double. long. double. double. or decimal long.Table 6.

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

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

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

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

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

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

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

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

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

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

Less than or equal. Greater than or equal.| ^ ! ~ && || 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. != < > <= >= . Comparison == Equality. which compares two operands. Greater than. Increment. Decrements operand by 1 (see following text). Inequality. Less than. Decrement ++ -Increments operand by 1 (see following text). should not be confused with the assignment operator (=). Note that this operator.

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

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

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

Flow to with goto The lowly goto statement is alive and well in C#. Order indicated by parentheses takes precedence over operator driven order. order indicated by parentheses. goto statements are useful in a special situation . Flow Control Statements In Chapters 3 and 4. ~.and use this section to explain some other C# flow control statements. +. <= .=. the goto.x *.and which are fairly straightforward in any case . decrepit granddaddy of all flow control statements. however. < . So it's worth having a look at the ancient.. except that of the member access operator. +=. you should not rely on the order of operator precedence if this might be confusing . foreach.. goto statements are easy to use and direct. x++. In C#.. as ==. A goto statement causes an immediate. one that surely gets no respect. != & ^ | && || ?: =. %=. /. > . . >=. % +.. <<=. >>=. . 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 .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 (. new. &=. if…else. ^=. /=. x . . |= As a matter of good programming practice and to make code clearer to read. over . which is an identifier followed by a .with switch statements. typeof Cast operator. use parentheses to make the evaluation order explicit and clear. *=.). as is well known. is. ++x. unconditional jump to a label. on them leads to unmanageable "spaghetti" code. and switch statements. for. !. I showed you how to work with break.instead.

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

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

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

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

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

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

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

it is a better idea to use specific catch clauses to handle certain types of exceptions. MessageBoxButtons. MessageBoxIcon. MessageBoxIcon. 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. go here and take care of it.7: The DivideByZero . } } In the shown in Figure 6.which would cause a division by zero . the catch filter will catch all errors.} catch (DivideByZeroException){ MessageBox.OK.Show("You are naughty to even think of dividing by zero!". A . In some situations.Error).7." Often. you can use this as a centralized error . MessageBoxButtons.OK.Exception (left) and FormatException (right) are caught. however. catch deals with the situation if the user enters zero .processing mechanism along the lines of "if any error happens.Show("Please enter a number!". As you can also see in the figure. 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.Error). "Exceptions".ToInt16. Figure 6. "Exceptions". } catch (FormatException){ MessageBox.

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

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

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

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

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

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

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

but it should still make sense to deal with your exception as a generic exception (meaning.Figure 6. This means that . Gets a message that describes the current exception. A string that contains the stack trace immediately before the exception was . Table 6. (An important exception to this is SqlException.7 shows some of the commonly used properties and methods of the Exception object. It's worth thinking a little about the nature of an exception object.11: This 'cockroach' exception illustrates that you can throw and catch custom exceptions. which is thrown when SQL Server returns a rather specific warning or and large . no matter how much information you add.) The implication is that if you have a compelling reason to add additional information to the exception class.the presence of the exception signals the error like a canary falling ill in a mine.the only difference between the parent and child classes is the name of the class. you should still implement a meaningful "message" property so that programmers can throw and catch it in the normal fashion).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. exceptions have been used only as sentinels . because they can be used for more than this. 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. Table 6. Exception Objects So far in this discussion. you certainly can.

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

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

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

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++)

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

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

for (int i = lower bound. column = "". i++) { column = column + " " + jaggedA [3] [i] + " ".Items.dimensional arrays to create an array with six elements where the index of the first element is five .Add(column). you need to create two integer arrays. i < 2. } 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 . because arrays created in the way shown in this example do not have many of the conveniences .but you could use multidimensional arrays to create an array with different .Add(column). column = "". } lstMulti. 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). column = "".Items.such as square bracket notation . Here goes! Warning: You probably won't want to use non . i < 40. i++) { column = column + jaggedA [5] [i]. } lstMulti.that we've come to expect when working with arrays. i++) { column = column + jaggedA [6] [i].column = "".Add(column). i++) { column = column + jaggedA [4] [i].zero lower bounds very often in C#. } lstMulti. } lstMulti.Add(column). In this example. First. i < 4. for (int i = 0. I've used one . for (int i = 0.Items. The second array stores the lower bound for each dimension. i < 86. for (int i = 0.

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

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

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

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

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

) Table 7. 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.classes has quite a few members (properties and methods) that you will need to know about to successfully use the class. only inherited. properties. but if you are programming an object based on one of these classes. without the implementation specifics for these members.2. familiar way.3: Useful Collection Classes Class ArrayList Description Used to create an array .for example. and can contain abstract (or nonimplemented) membersfor acollection. the syntax of an interface looks like the signatures for a bunch of methods. when a class implements an interface. events. I discuss interfaces further in Chapter 9.1 and 7. it tells any object that uses the class that it will support the methods. by convention. In other words.NET structure whose size is dynamically altered as items are added and removed (see example later in this chapter). CollectionBase Provides the abstract base classmeaning the class cannot be instantiated. etc. . and indexers of the named interface. 'ArrayList Members' or 'Queue Members'). first out collection of objects (see example later in this chapter). comfortable. (Most of these classes have members comparable in extent and utility to the Array members shown in Tables 7. 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. start with a capital I . Collection Interfaces An interface provides a binding contract with any class that uses the members specified in the interface. properties. This means that objects based on classes that implement the interface all work in the same. Hashtable Queue SortedList Used to create a collection of key/value pairs that are organized based on the hash code of the key. first out collection of objects (see example later in thischapter).style collection of key/pairs. Interfaces in the . 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). Used to create a last in. Used to create a first in. DictionaryBase Provides the abstract base class for a dictionary . Internally.

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

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

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

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

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

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

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

EventArgs e) { theQueue.Text = (string) theQueue. "No more waiting in line!". } private void btnPeek_Click(object sender.OK. txtIn. else MessageBox. the code for queuing..EventArgs e) { if (theQueue. lstQ. MessageBoxIcon. private void btnEnqueue_Click(object sender. and Peeking .Peek().Clear().Count > 0) txtOut.12: Queuing. For now.Count > 0) { string str = (string) theQueue. System.Enqueue(txtIn.Items.Figure 7. Listing 7. MessageBoxButtons. System.12. System. foreach (string s in theQueue) { lstQ.Show("Nothing on the queue to dequeue!". and peeking is shown in Listing 7.Exclamation).EventArgs e) { if (theQueue. enqueuing. . } } private void btnDequeue_Click(object sender.Text = "".Items. We'll be discussing queues further in Chapter 11 in the context of messaging..Dequeue().9: It's important to check that something is on the queue before attempting to dequeue (or peek). Queue theQueue = new Queue(). Dequeuing.Text).Add(s). to display a single element by index and how to display all ArrayList elements. Figure 7..more or less for purposes of verifying that the structure is working .10: In design mode. } . This ErrorProvider . Tip: VB6 programmers will welcome this functionality as comparable to the ReDim statement. MessageBoxIcon.. MessageBoxButtons.Exclamation).Exclamation). The demonstration application in this section shows you how to add and remove objects from an ArrayList. the ErrorProvider sits on the 'tray' at the bottom of a form. MessageBoxIcon. and .in a (hopefully) profitable detour from the main topic of this section . } else MessageBox.OK.MessageBox. "You peeked!".OK. "You peeked!".Show("'" + str + "' is at the head of the queue!".10 shows the user interface (in design mode) that we'll use for this purpose.Show("Nothing on the queue to peek at!". Note: that an ErrorProvider component has been added from the Toolbox to the 'tray' at the bottom of the form. Figure 7. except that it's dynamically resized depending on how many elements are actually stored in it. ArrayList The ArrayList works like an array.

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

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

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

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

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

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

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>

you must have the Class View window open. . The C# Class Wizard The C# Class Wizard is a visual interface that gives you a jump start in creating a class definition.such as form modules. it does provide the framework for a class constructor (see "Constructors" later in this chapter). In Class View. a using System directive. 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. Significantly. To start the Class Wizard. The first panel of the wizard. will open. exactly. But it is also perfectly reasonable to add short classes to existing modules . it makes it very easy to find classes in your source code. If you put each class in its own module file (. 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).cs file) and name each file with the class name. right . If you'd like aid from the IDE in creating a class.public class MyClass { public MyClass() { // // TODO: Add constructor logic here // } } } What. but the overall answer is. it can aid the clarity of the overall program architecture to put each substantive class in a module by itself. 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. And. shown in Figure 8. a business object class along with the collection class that groups them together. Class modules are good places to start class libraries. not much.for example.6. The default class code gives you some comments. as a matter of style. It is also common and effective organization to group related classes in one module .click a namespace and select Add > Add Class from the context menu. In addition. It does not provide a base class for the newly created class.

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

System. Suppose you have a class named Surprise. perhaps in a click event like so: private void btnGet_Click(object sender. it defaults to private access.Show (surprise. MessageBox. Table 8.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. However. declare them using the private access modifier. At the risk of getting a little bit ahead of ourselves. } . let's make the concept of protected access a little more concrete.hiddenValley). you should explicitly mark the access level of your class members: if they are local in scope to the class. with a single member.modifier controls the scope of the member .in other words. where it is 'visible' and can be used. as a matter of good programming style.EventArgs e) { Surprise surprise = new Surprise(). } } You might then attempt to instantiate an object based on Surprise and access hiddenValley. the protected string hiddenValley: namespace SybexC22 { public class Surprise { public Surprise() { } protected string hiddenValley = "How green was it?".

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

MessageBox. } .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?". MessageBoxButtons.EventArgs e) { UnWrap unWrap = new UnWrap(). MessageBoxIcon. System.Figure 8.GetSurprise(). Listing 8.Exclamation).OK.Show (unWrap. "Got it". } } namespace SybexC22 { public class UnWrap : Surprise { public UnWrap() { } public string GetSurprise(){ return hiddenValley.7: A protected member can be accessed via public methods of a derived class. } } } // in class Form1 private void btnGet_Click(object sender.

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

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

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

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

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

public const int totalPlants = 7. Here's the Main method added by default to Form1 when you open a new C# Windows application: static void Main() { Application. Every C# class must have one or more constructors .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. public const int generation = 20. The Main Method Is Static As you may have noticed.the place where the program starts execution. 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. A constructor is invoked when a class is instantiated. } } 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. If the class were named MyClass. the important parts looked like this: public class MyClass { public MyClass() . declare a method within the class whose name is the same as the class that it constructs.Run(new Form1()). To define a class constructor in C#. static members cannot directly access instance members of a class . The Main method is always static.1).generation.

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

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

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

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

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

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

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

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

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

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

I created a new instance of frmTribe. So I passed the current instance to the instance method: gop.CreateForm (this)..Text = tr. it had to be done from an instance method within that class).) For this to work.. .tribeText. But this couldn't be done from Form1 itself because of the facade functionality of the GroupOfPeople class (in fact. . (MDI applications are explained in Chapter 4.tribeName. 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. And then I could go merrily about populating the form: frmTribe.BackColor = tr.Name = dlg.Windows.Name = tr. this. } 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. The newly instantiated frmTribe's MdiParent property was assigned to the instance value of the main form passed into the method: frmTribe.TribalColor.Form parentForm) Within the method.Within the CreateNewTribe method. the MDI child form: frmTribe = new frmTribe().MdiParent = parentForm.Text = dlg. this.Forms. The CreateForm method was declared with a Form as its argument in a parameter named parentForm: public void CreateForm (System. the instance of frmSettings is used to populate member values: public void CreateNewTribe(frmSettings dlg) { tr = new Tribe().

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

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

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

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

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

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

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

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

if (m_Population >= veryBig) { m_Population = long. .OnMaxPop += new GroupOfPeople. the event handlers are usually named with the firing class name.GroupOfPeople_OnExtinction). gop. followed by the name of the event method (e. such as Fred. any valid identifier could be used.. GroupOfPeople_OnExtinction) . 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). OnMaxPop (this. private void GroupOfPeople_OnExtinction (object sender. e).EventArgs e) { } By convention. System.EventArgs().ExtinctionEventHandler (this.1.GroupOfPeople_OnMaxPop).EventArgs e){ } private void GroupOfPeople_OnMaxPop (object sender.but.. and the event would still work.EventArgs e = new System.MaxPopEventHandler (this. System. then there's no real point in going to all this trouble... this.OnExtinction += new GroupOfPeople. 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(). System. } Note: The MaxValue field of a numeric type represents the largest value that can be stored in that type. Adding Code to the Event Handlers If you don't place any code within the receiving event handlers. gop.MaxValue .isAlive = false. in fact. followed by an underscore.g.

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

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

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

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

Population + 1000000){ return true. } } public static bool operator < (GroupOfPeople a.Population > b. } else if (a.Population + 1000000)){ return true. Listing 8.Bacteria + 100000)){ return true. } else if (!(a. GroupOfPeople b){ } Listing 8.Bacteria > b. } else if (a.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.Guns > b.Guns > b.Bacteria > b.11 shows the complete static method within the GroupOfPeople class that overloads the operators. } else { .Bacteria + 100000){ return true. GroupOfPeople b){ } public static bool operator < (GroupOfPeople a. GroupOfPeople b){ if (a. GroupOfPeople b){ if (!(a.11: Overloading the < and > Operators in the GroupOfPeople Class public static bool operator > (GroupOfPeople a. } else if (!(a.Population > b.Guns + 10000)){ return true. } else { return false.

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

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

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

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

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

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

2). } m_Population += (long) (increment * 1. } As the culture starts to really race for the stars in the Civilization class.1.5){ if (m_Guns < veryBig .1) { m_Guns = m_Guns + Convert.MaxValue . } if (m_Bacteria < veryBig . and Steel application. Germs.18: The GetNewPopulation Method in the Civilization Class public override long GetNewPopulation () { const long veryBig = long.ToInt64(m_Guns * growthRate).MaxValue / 2.if (m_Bacteria < veryBig . } if ((growthRate * 2) >= 0. Creating a Custom Collection All along in the Guns. return m_Population.2). } } m_Population += (long) (increment * 1. return m_Population. float growthRate = GetGrowthRatePerGeneration(). } As these overrides of the GetNewPopulation method show. . if (m_Population >= veryBig) { m_Population = long. long increment = (long) (growthRate * m_Population). return m_Population. things pick up still more.1) { m_Bacteria += m_Bacteria. 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. Listing 8.1) { m_Bacteria += m_Bacteria. I've had the issue of how to deal with all the instances of GroupOfPeople. These instances need to be easily available for programmatic reference in several circumstances.18). with the introduction of the guns and technology index if the population growth rate is sufficiently high (Listing 8.

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

} Globals.globalTime += Globals.aGoP.generation. the concepts explained in this chapter are extremely important.ProgressTheTribe(). I also hope that I've given you some sense of how OOP concepts might be used in real . . and object . } Conclusion Objects. Although in some respects. classes.oriented programming in C# are fun and heady stuff! In a fully OOP language and environment. this chapter has touched only the surface of this topic. important material has been explained. 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 projects.

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

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

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.

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

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

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

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

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

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

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

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

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

IComparable and IEnumerable are the most important. an interface identifier always starts with a capital I. 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. Note: The Object Browser will also tell you the members that the interface 'contract' requires to be implemented. Interface implementation also provides a good way for a team leader to deliver project specifications: the project lead writes the interfaces. and define those members for you.6). You can determine the interfaces implemented by this class (or any other .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. By convention. Each of these interfaces is documented in online help. Of the String class interface implementations. and IEnumerable. the creators of the . IConvertible. followed by another capital letter. Figure 9. and specifies mandatory implementation within classes.NET Framework) and another set will be using the classes (you and me). using a statement like this: . String Interfaces The String class implements four interfaces: ICloneable.6: You can find the interfaces implemented by the String class by using the Object Browser. IComparable.

objects of the class work with the BinarySearch and Sort methods of the Array class. we need to add a CompareTo method to the Dinosaur class.CompareTo (dino.time error. zero if the two instances are the same...9.. Next. .CompareTo Method" in online help. 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. and greater than zero if the current instance is greater that its comparison instance. } 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.foreach (char chr in str) { ..Name. You can read more about the implementation details by looking up "IComparable. Once this method is implemented.Name). The long and the short of it is that Dinosaur instances will be sorted in arrays based on their Name property. In the meantime. CompareTo has a fairly complex int return type that must be less than zero if the current instance is less than its comparison instance. return this. you will get a compile . You can see the code for this method in Listing 9. Listing 9. 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. 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). public int CompareTo (object obj) { Dinosaur dino = (Dinosaur) obj.9: Implementing IComparable in the Dinosaur Class public class Dinosaur : IComparable { .

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

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

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

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

9). Figure 9.Add(doc.Items.Name + " the " + chomper.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.8 shows the results of running this code displayed in the ListBox at the top of the form. if (chomper.GetType().Add(chomper.ToString()).8: The ICarnivore interface determines who has eaten whom. lstDino.Add("for lunch. .Name + " the " + doc.10). lstDino.").Diplodocus doc = new Diplodocus ("Doc". } } Figure 9.9) and also in the Object Browser (Figure 9.GetType().ToString()).Items.Add("has eaten").CanIEatU (doc)){ lstDino.Items. lstDino.

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

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

Group The results of a single capturing group. . shown in the Object Browser. 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.11: The Regular Expression classes. Match The results of a regular expression matching operation. The Matches method of the Regex class returns a MatchCollection object. Capture A single subexpression match.Groups property. MatchCollection A sequence of nonoverlapping successful matches.Figure 9. Instances of Group are returned by the Match. Escape and Unescape) that can be used with other classes. which contains a collection of Capture objects. The Match method of the Regex class returns a Match object. It also provides a few static methods (for example.

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

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

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

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

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

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

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

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

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

. So I'll start by showing you a few somewhat complicated regular expression patterns that may be useful . is shown matching this pattern in Figure 9. Regular Expression Examples The full power of regular expressions becomes apparent when you start to use their pattern . Pattern Matches To match a date in mm/dd/yyyy format. But regular expressions can do a lot for you more or less on their own.3}|i[vx])$) MDCCLXXVI.3}|c[dm])(l?x{0.3}|x[lc])(v?i{0.matching abilities in combination with string and regular expression class methods in your programs.and that you can use to test your understanding of the syntax of regular expressions. For example.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. To match a string by itself on a line. start the regular expression with a caret (^) and end with a dollar sign. the regular expression ^Prospero$ matches the string Prospero if it is alone on a line. 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. This regular expression defines a word to include apostrophes and hyphens but not digits or underscores (so 85th would fail the match).13. or 1776.

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

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

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

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

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

SpecialFolder enumeration.SpecialFolder enumeration (see Table 10. GetFolderPath Method gets the fully qualified path to the special folder identified in the Environment. SystemDirectory Property gets the fully qualified path of the system directory.1 (it is more common to define the enumeration directly within the namespace. Figure 10. Table 10. Table 10.1: The SpecialFolder enumeration is defined within the Environment class.2: Values of the Environment. GetLogicalDrives Method returns an array of strings containing the names of the logical drives on the system.SpecialFolder Enumeration Constant Special Folder Description . as you can see in the Object Browser in Figure 10.2 shows the possible 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). Table 10. System).. e. Table 10.look up "Environment Members" in online help. It is unusual that this enumeration is defined within the Environment class.1 shows some of the static Environment methods and properties that are related to the file system.g.

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. The correct formulation is 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. non .time syntax error.GetFolderPath(SpecialFolder.ProgramFiles).SpecialFolder.GetFolderPath(Environment.ProgramFiles). . Hence String str = Environment. produces a compile .

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

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

Directory and File Classes Four parallel classes are designed to let you work with (and perform discovery on) directories and files. root information. The classes . if you expect to use members multiple times. If you need only a few items of information or to perform only a few operations. The GetFiles method of DirectoryInfo returns an array of FileInfo objects that are the files in a given directory. File. so you must instantiate a DirectoryInfo object to use it. DirectoryInfo. • File. Selected members of FileInfo are shown in Table 10. which require a string argument representing a file name. so you must obtain an instance to use it. or both for the specified path . The Directory method returns a DirectoryInfo object that is an instance of the parent directory of the FileInfo object. Table 10. and FileInfo.5.which is my stylistic preference in any case.7. Here's some more information about these classes: • Directory contains only static members for manipulating directories. provides static methods used for manipulating files. such as a Path string (see Table 10.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.are Directory.6.4 for class methods). it probably makes more sense to instantiate DirectoryInfo and FileInfo objects . Directory and File.IO namespace and all sealed so they can't be inherited . DirectoryInfo inherits from the abstract class FileSystemInfo (as does FileInfo). it's probably easiest to use the static classes. • DirectoryInfo contains no static members. Selected File members are shown in Table 10. or DirectoryInfo and FileInfo. Selected members of the DirectoryInfo class are shown in Table 10. • FileInfo contains no static members. It is more or less the case that one can achieve comparable functionality using either parallel pair Directory and File. However. which require an argument that is a directory. 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.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. providing the option to specify a new file name Method opens a file with various read/write and sharing privileges Method creates a read .only FileStream Method creates a StreamReader that reads from a text file Method creates a write .only FileStream .

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

in fact. • On the other side of the fence. derived from the abstract TextReader/TextWriter pair. you'll find yourself using FileStream most of the assume that a file is just plain binary. use an instance of any class derived from Stream. many times one knows that one is operating on a text file. .when in doubt . make reading and writing text files a breeze: • The most commonly used of these two class pairs for text files is StreamReader/ StreamWriter.6 should help to make it clearer. The relationships between objects based on the classes derived from Stream and the "Readers/ Writers" can be a bit confusing at first .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. • The StringReader/StringWriter class pair is an alternative . but unless you have specific requirements for something like buffering or cryptography. 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.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). matched up as typeReader and typeWriter: • Although it is wise .) The specific classes come generally in reads from and writes to a file using the StringBuilder class as the underlying data type. (You can.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

PeekCompleted += new PeekCompletedEventHandler(MyPeekCompleted).EventArgs e) { lblResults. .17: After the other program has been initialized. mq.Text = "Status: Ready". // 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.9 uses the familiar syntax to create or join that queue. mq. so I named the message queue Race.Exists(qpath)) MessageQueue. Listing 11.\Private$\Race". The code shown in Listing 11. private void btnStartMe_Click(object sender.9: Initializing a 'Node' and Asynchronously Waiting to Peek MessageQueue mq.Figure 11.Empty. mq. it signals that it is asynchronously waiting in standby mode for the start message. System.BeginPeek().Create (qpath).Formatter = new BinaryMessageFormatter(). mq = new MessageQueue(qpath). mq.Text = String.Purge(). if (!MessageQueue. string qpath = @". lblResults.

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

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

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

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

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

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

centric viewpoint whenever this is a plausible approach. 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 . and much more.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. you can’t do anything very sophisticated or useful without the ability to save and retrieve data between program sessions.NET.related capabilities are built into the . In the long run. . A great deal of XML and XML . configuration. the . reading and writing to files as explained in Chapter programs are designed to query databases to populate the .NET Framework. In large part. and Visual Studio rely on XML as the underlying facilitator of interoperability. There are literally hundreds of classes you can use to generate and manipulate XML in C# . I am emphasizing the fact that in today’s world it is an intelligent move to regard data from an XML .NET Framework. While there are a variety of ways you could go about doing this—for example.NET and databases • Connection strings • Managed providers and data components • Working with DataSets Under the hood.Working with XML and ADO. the great majority of real . 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 programming structure finds XML the best tool to use for application integration. “Working with Streams and Files”—for many developers today.NET. look up “XML in Visual Studio” in online help. This chapter provides an overview of some of the ways you can work with XML using C# . input/output essentially means working with a database.NET languages.NET Framework and development environment. In effect. By starting the chapter in this book that treats data with a discussion of XML.

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

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

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

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

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

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

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

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

many of them Microsoft .1: An XSD XML Schema <?xml version="1. The xmlns:xs="" declaration says that all tags should be interpreted according to the W3C defaults.<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.1 shows the same XML schema rendered a little more formally.xsd" xmlns:xs="http://www. you’ll probably find that some additional attribute declarations. and that the xs prefix will be used before elements. Note: The xs:schema declaration says that the text represents a schema. Listing 12. have been added to the schema tag. 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).specific. 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).org/2001/XMLSchema"> <xs:element name="Product">" ?> <xs:schema targetNamespace="http://www.w3.tempuri.

0" encoding="utf-8" ?> <Product xsi:ProdId="123" xsi:ProdType="Candy" xmlns="http://www.xsd" xmlns:xsi="<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.xsd"> <Name>Purple Wheeler</Name> <Desc>Tastes like grape</Desc> <SubPart>Wheel</SubPart> . Going back to our original XML fragment. If there are no validation errors.formed” and if the XML schema shown in Listing 12. then the schema is “well . it can be validated by giving the designer the focus and selecting Schema ¨ Validate. we can now enter it in an XML Designer and connect it to its intended schema: <?xml version="1.1 is copied into an XML Schema Designer in Visual Studio. If you’ve left something off—for instance. a closing bracket—validation will tell you.tempuri.

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

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

Name = "SubPart".Value = "Candy". ct.Add(aProdId).Add(f2). f1.SchemaType = st.Value = "Toy". eSubPart. .SchemaTypeName = new XmlQualifiedName("integer". The ProdType attribute is a little more complicated. XmlSchemaEnumerationFacet f1 = new XmlSchemaEnumerationFacet(). aProdId.w3. 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(). "http://www.MaxOccursString = "unbounded".w3. "http://www. Next.Content = res. the attributes are added to the complex type’s Attributes collection: XmlSchemaAttribute aProdId = new XmlSchemaAttribute(). "http://www. aProdType. XmlSchemaEnumerationFacet f3 = new XmlSchemaEnumerationFacet().org/2001/XMLSchema").org/2001/XMLSchema").SchemaTypeName = new XmlQualifiedName("string".Name = "ProdId". aProdId.XmlSchemaElement eSubPart = new XmlSchemaElement().Add (eSubPart). f3. st. eSubPart. eSubPart. aProdId.MinOccurs = 0.BaseTypeName = new XmlQualifiedName ("string". res.Required.Facets."). res.w3.Facets. f2. XmlSchemaEnumerationFacet f2 = new XmlSchemaEnumerationFacet().Items.Use = XmlSchemaUse. XmlSchemaSimpleTypeRestriction res = new XmlSchemaSimpleTypeRestriction().Add(f1). res.Attributes. ss.Value = "Gadget".

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

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

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

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

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

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

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

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

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

you’ll probably prefer to get your XML data from databases.10. as shown in Figure 12. if you switch to the Data tab of the XML Designer. files. In order to use this feature.Creating an XML Data File Before we get to some more of the interesting things you can do reading and writing XML. Once you have entered your data in tabular fashion.11. This approach is a great deal faster than hand constructing each XML data element—although in real life. shown in Figure 12. you can view it as straight XML by clicking the XML tab. For example. it’s worth emphasizing that you can use the Data tab of the Visual Studio XML Designer to add element data against XML. 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. or code generation.10: XM L data can be entered using a table once the element has been constructed. Figure 12. . you can start entering your data using the table provided.

Note: In many cases. create an XML fragment. 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 DataSet will do the best it can to create a schema on the fly from an XML data file. Use it to auto . It is a little bit harder to serialize multiple elements of the same type under a single root node—but not . It’s a neat feature that you can auto .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. right .click in the XML Designer and choose Create Schema from the context menu.11: Dat a entered in the table can be viewed as “straight” XML. you might want to mirror the sequence I’ve used in this chapter. To do this.Figure 12. Tweak the schema. The Deserialize method lets one "re . The XML required to connect the XML file to the schema will automatically be added to the XML file. The newly generated XSD file will appear in the Solution Explorer with the same name as the original XML file but with a .xsd file extension.generate an XML schema based on your XML data file. If you don’t set up a schema. As I mentioned earlier in this chapter. 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. Serializing XML Let’s have some fun with serializing a class to XML! Actually. Then distribute the schema for validation of longer XML documents. First. you don’t need to create an XML schema.generate a schema.

m_Title = title.Empty. } public Employee(){ } private string m_Fname = String.Empty. 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. as well as a parameter less constructor. The class provides first name.Empty. } } public string Lname { get { return m_Lname. Listing 12. private string m_Lname = String. Here’s how it works. it has a class constructor that supplies these values. m_Lname = lname. This will be the member information used for serialization into XML. For convenience sake. and title properties for each employee.4. last name.4: A Class Ripe for Serialization to XML public class Employee { public Employee(string fname. private string m_Title = String. public string Fname { get { return m_Fname. string lname. } set { m_Fname = value.that difficult. . string title) { m_Fname = fname. It would have worked just as well from the viewpoint of serialization to use public class fields. Let’s start with the Employee class shown in Listing 12.

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

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

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

Show (excep. using the typeof method to pass it the type of the OurTeam collection.12. it will appear along the lines of Figure 12.MessageBox.Clear(). A new XmlSerializer is created. ourteam.Close(). If you try this out and examine the file that results. the Boolean false.Flush().xml in the application directory. tells the StreamWriter to overwrite a file with the given path (rather than append to it). } finally{ sw. the XmlSerializer’s Serialize method is used to connect the collection class and the StreamWriter. Finally. 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. } } This code is pretty straightforward.Message). sw. Note: The second argument to the StreamWriter constructor. If you open the file created by the XmlSerializer in an XML editor. . A StreamWriter is connected to the file employee.

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

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

Listing 12.14: Users can enter as many element/value pairs as they want. So the structure of the XML document will look like this. Listing 12. </UserChoice> Creating the Class and Collection Class If the user can enter multiple element/value pairs before the XML document is generated. <UserChoice>. all the element/value pairs entered by the user will fall under one root XML node. the XML document is created. Once again. when all the pairs have been entered.. the application must have some way to store the information..<element> value </element> Figure 12. In the interests of simplicity.9: An Element/Value XML Class and a Collection Class Used to Store Element/Value Pairs .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.9 shows the bare . with each element/value combination chosen by the user: <UserChoice> <element> value </element> <element> value </element> . 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).

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

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

theXml.Close().Empty..Text = String.WriteEndElement(). System...Text = String.. the closure code in this example (and in some others in this chapter) should be placed in the finally block of a try. .10.@value). txtValue.Indented.foreach (Xml aXml in theXml){ myXmlTextWriter.Text. null). applications. } private void btnWrite_Click(object sender.Text. // Declared at form level . myXmlTextWriter. Listing 12.10: Creating a Collection of Element/Value Pairs and Writing Them to an XML Document using System.Add (aXml).. } Finally.WriteElementString(aXml. aXml. Note: I just wrote “finally” close the root element and document. theXml = new XmlCollection().EventArgs e) { XmlTextWriter myXmlTextWriter = new XmlTextWriter("doc.element.finally structure. 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.. myXmlTextWriter. close the root element and document: myXmlTextWriter.Empty..Formatting = Formatting. as you may be thinking..EventArgs e) { Xml aXml = new Xml().xml". aXml. 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 .@value = txtValue. XmlCollection theXml. // in Form1 constructor .Xml. . Well.element = txtElement. System. private void btnAdd_Click(object sender.

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

Read()){ if (myXmlTextReader. myXmlTextReader. 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". } Within the loop. the XmlTextReader should be closed: myXMLTextReader.Close() The code. and exiting only when the end of the document or the </UserChoice> tag is reached: while (myXmlTextReader. placed in the Display button’s click event.. . you can use the ReadOuterXml method to read tags and values into a multiline text box: while (myXmlTextReader.Read()){ if (myXmlTextReader.Name == "UserChoice") break.Text += myXmlTextReader. .. } Finally. txtReader.Name == "UserChoice") break..ReadStartElement("UserChoice"). is shown in Listing 12.11.<UserChoice> and is followed by elements and values: <UserChoice> <element> value </element> <element> value </element> .xml"). You could then set up a loop reading the rest of the document. </UserChoice> If you are interested in only the elements and values.

Name == "UserChoice") break. } myXmlTextReader.11: Reading an XML File private void btnDisplay_Click(object sender. myXmlTextReader. txtReader.ReadStartElement("UserChoice"). as they say.Empty.EventArgs e) { txtReader. System. I leave it.15). XmlTextReader myXmlTextReader = new XmlTextReader("doc. 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. while (myXmlTextReader.xml"). 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. the elements and values in the doc.Text = String.xml file contained between the beginning <UserChoice> tag and the ending </UserChoice> tag will be shown in the text box (Figure 12.Read()){ if (myXmlTextReader.ReadOuterXml() + "\r\n".Close(). .Text += myXmlTextReader. } If you run the project and click the Display button.Listing 12.

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

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

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

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

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

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

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

13. to the project.14. shown in Listing 12.NET Web Application.w3.For a simple example of how this works.14: The XSLT Transformation <?xml version='1. and a file containing an XSLT transformation. 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. Add an XML file.13: The XML File <?xml version="1.0"> <xsl:template match="/"> <html> <body> <table cellspacing="3" cellpadding="8"> .0'?> <xsl:stylesheet xmlns:xsl="http://www. Listing 12. shown in Listing" version="1.

xsl. 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. or by creating the files in an external editor and then selecting Project > Add Existing Item.<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.xml. The XSLT file shown in Listing 12. they will appear in Solution Explorer: .13 Many_Prod.14 is called Trans_Prod. Once the files have been added to the project.

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

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

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

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

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

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

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

22: The properties of servers shown in Server Explorer can be browsed to determine connection strings.23: Opening a .udl. the ubiquitous Data Link Properties dialog will open (Figure 12. rename it to change its file extension to . You may also be interested to know that if you save an empty text document in Windows on the file in Windows Explorer. you’ll find the connection string corresponding to your entries in the .udl file allows you to set data link properties. 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. Figure 12.Figure 12. Once you’ve completed the entries on the Provider and Connection tabs of this dialog and clicked OK.23). and double . Provided that you are connected to a data source.

drag it from the Toolbox to a form. Like the other nonvisual components.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 . as shown in Table 12.NET DataAdapter components (OleDbDataAdapter and SqlDataAdapter) each support four properties. Adding Data Components The Data components are found on the Data tab of the Toolbox: To use a Data component. Table 12. you can also instantiate these components in code without using the visual interface provided by dragging and dropping. Setting DataAdapter Properties ADO.6. Note: Of course.underlying text file. respectively. which are Command objects of type OleDbCommand or SqlCommand. if you prefer. Data components sit on the tray beneath the form.6: Command .

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

as shown in Figure 12. . follow these steps: 1. Figure 12.25: The Data Link Properties dialog allows you to create a new data connection.25. Click the New Connection button in the Data Adapter Configuration Wizard. with the Connection tab active. 2.26 shows the Provider tab with the Microsoft OLE DB Provider for SQL Server selected.Creating a New Data Connection To create a new connection. Figure 12. The Data Link Properties dialog will open. 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.

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

Figure 12.29.28. You can add multiple tables to your query by right . Once you have added a table. 3. The Query Builder lets you add a table or view. . as shown in Figure 12. The Add Table dialog appears. In the new tables that you added.28: You can add tables and views using the Query Builder. 4.2. Select a table and click Add. select the fields you would like to include.clicking in the upper pane and selecting Add Table from the context menu. as shown in 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.

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

For example.warnings about anything left to do. 8. . you can restart the wizard by right . 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. The wizard will complete its work. 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. if you’ve forgotten what your SQL query was (or wish to modify it). Click Finish. Note that the properties of the SqlDataAdapter are accessible in the normal fashion in the Properties window. Working with the Data Adapter After you’ve added the DataAdapter component.clicking the SqlDataAdapter and selecting Configure Data Adapter.

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

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

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

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

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

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

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

When you click Update.38 shows the addition of comments about Harold Davis and his liking for food added to the ProductName column.37: Our grid is populated with the contents of Products. To set this up. Figure 12. Note: Of course. a variation on this would be to fill a grid automatically without user intervention— for example. The grid will be populated with data from the Products table. as seen in Figure 12. the changes that a user makes within the grid only impact the DataSet—until the Update method of the DataAdapter is invoked. the ListBox in the upper left of the form. load the data into the grid. these changes are also written to the database. "Products"). This happens without updating the database. Figure 12.37. and make some changes (for example. 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. as you . in the form constructor. This being the case. changes made in the grid are instantly reflected in controls that are bound to the same DataSet. since both the ListBox and the grid are taking their data from the same DataSet. System. } Run the project again. However. to the Product Name data). for example. I started this chapter by noting that a DataSet is a miniature database stored in memory.Update (dataSet1. 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.

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

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

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

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

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

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

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

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

if (numSessions < 1){ this.Text = "Welcome back..ToString(). int numApps = ss. The StateService variable is declared at the form level so it is accessible to all the methods in the form class: StateService. To start the service..Text = ss.Text).StartMe(txtName. public Form1() { . label2. I don't recognize you. } } The complete web service–related code for the form module is shown in Listing 13. // in the form constructor .Net.EventArgs e) { int numSessions = ss. label1. } else { this.Text = "Total named usage of application is " + numApps. stranger!". Listing 13.CookieContainer = cookiePot.Text = "Sorry. invoke the StartMe web service method: this.GetSession ( System.Text = "Number of times for this named session is " + numSessions.Text). 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.ToString()...GetTotalUsage(). " + txtName.5: The Client Application Form Code using System. .Text + "!".Ss ss.

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

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

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

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

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

“Consuming the Service on the Web. Click Invoke. and continuing through the data in the table (Figure 13.9: The Invoke button starts the web method. Figure 13.Figure 13. (For more about the mechanics of working with ASP. starting with the XSD schema representing the Suppliers table. see Chapter 2.”) .10).NET web application. Open a new ASP. Note that the schema structure shown in Figure 13.8.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.10 mirrors the fields shown in tabular form in the Server Explorer window shown back in Figure 13. The DataSet is returned as XML.NET web applications.

Figure 13.12: A reference is added to the web service using its URL. In the Add Web Reference dialog.Use the Toolbox to add a DataGrid (shown in 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. Add a reference to the web service in the normal fashion.12). so we don’t need it yet). by selecting Project > Add Web Reference. enter the URL for the web service in the Address box at the top?(Figure 13.11: A DataGrid added to a WebForm can be used to display the DataSet returned by the service. and click Add Reference.

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

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

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

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

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

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

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

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

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

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

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

} . If you run the project.Formatter = new BinaryMessageFormatter(). theQ.Create (qpath).\Private$\WebServices". 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: .theQ.24).24: The generated test pages allow you to test pushing a message on the queue. } [WebMethod (Description = "Pops a message off the queue")] public string GetMessage(){ string qpath = @".Messaging.Message msg = theQ. Figure 13. subject). return msg.Body. MessageQueue theQ = new MessageQueue(qpath).Send (theText.0..Formatter = new BinaryMessageFormatter(). theQ.Exists(qpath)) MessageQueue. if (!MessageQueue.10)).Label + ": " + msg.ToString().. you can use the generated test pages to see whether the SendMessage web method actually places a message on the queue (Figure 13.Receive(new TimeSpan (0. System.

Figure 13.26: It’ s easy to use the web service to access the private message queue.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.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 return string from the web method is the subject concatenated with the body of the message.queuing web service to place a message on the queue when the Send button is clicked. to include as usual a reference to the web service). As you can see in Figure 13. .25.26). Figure 13. When the Receive button is clicked. The code shown in Listing 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.11 uses the message . of course.

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

Although XML documentation is available through the Add Web Reference dialog.29. using the Object Browser you can determine the parameter and return types of all available web methods. once you have added the web reference.contents"—showing the web methods available— will be displayed (Figure 13. As you can see in Figure 13. Your best tool in this discovery process. . Figure 13.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.28). 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. Figure 13.29: The Object Browser is the best tool in our arsenal for figuring out how the members of the TerraService work. is the Object Browser.

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

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

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

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

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

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

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

we created Windows user interfaces and started our exploration of the . we started to close the circle. and ADO. the gigue.NET web forms applications and learned how to consume the web service. . the prelude. the allemande.NET applications using C#. 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. and observed life from a string’s . In the next part. then we got to know ASP.NET Framework’s class libraries. messaging. as things strode forward. Files and streams. and elegant . the courante. In this chapter. XML.eye the first part of this book. we headed back to web services for a last visit.NET were discussed. literate. and that it helps you to build beautiful. 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 dance really heated up in the third part. we created a simple ASP.NET web service. a very large picture. indeed! I hope you’ve enjoyed my book.

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

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

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

select Help > Index. To open the Index window. it will open immediately when you select it in the Index window (rather than opening the Index Results window). by selecting Help > Index Results. not just on the indexed titles. based on the phrase you entered. 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. enter the term you are searching for in the Look For box: A list of indexed terms. . showing all the indexed listings for the term: If you double . If you select a term in the bottom pane.Index The Index window allows you to look for specific information. the topic will open for viewing. With the Index window open. except that it produces more 'hits' because it searches within help documents. Search The Search window works like the Index window. will appear in the bottom pane of the Index a listing in the Index Results window. the Index Results window will open.

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

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

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

Sign up to vote on this title
UsefulNot useful