Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Visual CSharp .NET Developer's Handbook (2002) [eng]

.pdf
Скачиваний:
35
Добавлен:
16.08.2013
Размер:
4.94 Mб
Скачать

Let's discuss the process of adding entries to the database. The following code shows how the application sends the information contained in two textboxes on the form to the database for processing.

// Create an instance of the ProcessMessageClass. ProcessMessageClass MyMsg = new ProcessMessageClass();

private void btnAddMessage_Click(object sender, System.EventArgs e)

{

// Add new messages to the message list. MyMsg.add_Message(txtSubject.Text, txtTitle.Text);

}

First we need to create an instance of the data handling class, ProcessMessageClass. Whenever the user clicks Add, the application calls add_Message() with the values from the two textboxes. The add_Message() method adds these entries to the ArrayList, Msg.

At this point, we have a delegate, a database, and a means for handing the data off for processing. The handlers come next. A handler is an implementation of the delegate we discussed earlier. The following code demonstrates that it's the form of the handler, not the code inside that's important.

// Create a dialog-based message handler.

private void PrintMessage(String Subject, String Title)

{

// Display the message. MessageBox.Show(Subject,

Title,

MessageBoxButtons.OK,

MessageBoxIcon.Information);

}

// Create a list filling message handler.

private void ListMessage(String Subject, String Title)

{

// Add the output to the list. txtOutput.AppendText(Subject + "\t" + Title + "\r\n");

}

These two handlers don't do anything spectacular, but they do process the data. The first displays a message box for every data element in the database. The second places each data element on a separate line of a multiple-line textbox.

The handler and the data processing elements exist, but there's no connection between them. We need something to fire the event. The following code shows this last part of the puzzle.

private void btnFireMessage_Click(object sender, System.EventArgs e)

{

// Fire the event and put the message handler into action. MyMsg.ProcessMessage(new DisplayMessage(PrintMessage));

}

private void btnFireList_Click(object sender, System.EventArgs e)

{

// Clear the list box before we fill it. txtOutput.Clear();

// Fire the event and put the list message handler into action. MyMsg.ProcessMessage(new DisplayMessage(ListMessage));

}

As you can see, the code calls ProcessMessage() with the handler of choice. Notice how we pass the name of the handler to the delegate as if we're creating a new object. This process is how delegates create the connection. Figure 3.11 shows typical output from this example.

Figure 3.11: Using delegates enables you to fire one or more events that process one or more pieces of data at a time.

Attributes

Attributes augment your applications in a number of ways. In fact, there are so many predefined attributes provided with Visual Studio .NET, that it's unlikely you'll find a single list containing them all. Most developers categorize attributes to make it easier to figure out which one to use.

It's important to know that each attribute affects application functionality at a specific level. For example, some attributes affect the way the compiler looks at the code during the compilation process. Attributes can allow certain types of optimizations to occur that the compiler may not normally use. They can also prevent inadvertent bugs by shutting off compiler optimizations in areas where the optimization may have unexpected results. With this in mind, let's look at the various kinds of attributes that C# will place at your disposal.

Note This chapter doesn't contain a complete list of .NET attributes; it contains an overview of some of the more interesting ones. We'll discuss many more of the predefined attributes as the book progresses. In addition, you'll learn how to create user-defined attributes in some sections of the book, such as the "Creating the Custom Attribute" section of Chapter 6, where you'll also learn the importance of attributes in creating components and controls.

COM

If you've ever had to deal with the vast number of macros required to create a component, you know that they can become confusing. While the C# wizards attempt to write as much of the code for you that they can, there are always additions that you need to make yourself. Unfortunately, figuring out where to make the entries and how to format them is often more in line with the black arts than with science. In addition, if you make a mistake in filling out the wizard entries, trying to overcome the problem can prove difficult, because the wizardgenerated code is difficult to read. The COM attributes overcome these problems by replacing large numbers of macro entries with easier to read attributes. Consider this example:

[ progid("CAgClass.coclass.1"), aggregates(__uuidof(CAgClass) ] class CmyClass

{

void MyMethod(void);

};

The progid attribute specifies the program identifier—the human readable dot syntax name of an object. The compiler uses this information to locate information about the object in the Registry and learn more about it. You'll use this attribute when creating external references to other objects on the client machine.

The aggregates attribute allows you to aggregate an existing component into the current component. In this case, CAgClass will become part of CMyClass. Notice that we use the __uuidof() function to obtain the universally unique identifier (UUID) of the existing class from the Registry. You could also enter the UUID directly.

Tip You can't use the aggregates attribute alone—this attribute is always preceded by the coclass, progid, or vi_progid attributes. (If you use one of the three, the compiler will apply all three.) The progid and vi_progid descriptions appear in Table 3.2. However, it's important to realize that some attributes have dependencies—you can't use them alone. Always check for dependencies before using an attribute in your code.

 

Table 3.2: COM Attributes

 

 

 

Attribute

 

Description

 

 

 

aggregatable

 

Determines whether another control can aggregate this

 

 

control. A component can disallow aggregation, allow both

 

 

standalone and aggregated use, or require aggregated use.

 

 

 

aggregates

 

Shows that this control is aggregating another control.

 

 

 

com_interface_entry

 

Adds an interface to the current class. You define the

 

 

interface separately from the class code. (We'll discuss this

 

 

issue in the IDL section.)

 

 

 

implements_category

 

Identifies component categories implemented by the target

 

 

class—in other words, the areas of functionality that the

 

 

component supports.

 

 

 

progid

 

Specifies the program identifier of a control.

 

 

 

rdx

 

Creates or modifies a Registry key. The parameters for this

 

 

attribute include the key, value, and value type.

 

 

 

registration_script

 

Allows execution of a custom registration script.

 

 

 

 

Table 3.2: COM Attributes

 

 

 

Attribute

 

Description

 

 

 

requires_category

 

Identifies component categories required by the target

 

 

class. In other words, the areas of functionality that a

 

 

container must provide to use this component.

 

 

 

support_error_info

 

Specifies that the component will return detailed context-

 

 

sensitive error information using the IErrorInfo interface.

 

 

 

synchronize

 

Synchronizes access to a method within the target class.

 

 

 

threading

 

Determines which threading model a class uses. Standard

 

 

values include apartment, neutral, rental, single, free, and

 

 

both.

 

 

 

vi_progid

 

Specifies the version independent program identifier of a

 

 

control.

 

 

 

The COM attributes represent one of the more complex attribute groups. They also represent one of the best ways to improve developer productivity when working in mixed language environments. You'll find that these attributes will at least halve the amount of code you write for even simple code and make your code infinitely easier to understand. Table 3.2 provides a list of common COM attributes and associated descriptions.

Compiler

You won't currently find compiler attributes that control every aspect of compiler behavior. However, you'll find attributes that provide access to the functionality of .NET and reduce the complexity of changing some features. Unfortunately, this attribute category only works in a managed code environment. C# does provide a managed code environment by default, but many developers will also use C# to work with unmanaged code in a mixed language environment. In short, the compiler attributes only affect certain compiler operations, but they're very important operations. Many of these attributes appear at the beginning of the file or in conjunction with specialty classes. Table 3.3 contains a list of the compiler attributes.

 

Table 3.3: Compiler Attributes

 

 

 

 

Attribute

 

 

Description

 

 

 

 

emitidl

 

 

Determines if the compiler generates an IDL file based on

 

 

 

IDL attributes. There are four possible emission conditions:

 

 

 

true (the IDL is generated), false (the IDL isn't generated),

 

 

 

restricted (allows IDL attributes without a module

 

 

 

attribute—no IDL file generated), and forced (overrides the

 

 

 

restricted condition and forces the file to contain a module

 

 

 

attribute).

 

 

 

 

event_receiver

 

 

Creates an event receiver. The event receiver can use the

 

 

 

native, COM, or managed models for receiving events. The

 

 

 

managed option will enforce .NET Framework requirements

 

 

 

such as use of the Garbage Collector.

 

 

 

 

event_source

 

 

Creates an event source. The event source can use the

 

 

 

native, COM, or managed models for sending events. The

 

 

 

 

 

Table 3.3: Compiler Attributes

 

 

 

 

Attribute

 

 

Description

 

 

 

 

 

 

 

managed option will enforce .NET Framework requirements

 

 

 

such as use of the Garbage Collector.

 

 

 

 

export

 

 

Places the target union, typedef, enum, or struct in the IDL

 

 

 

file.

 

 

 

 

importidl

 

 

Allows placement of the target IDL file within the generated

 

 

 

IDL file.

 

 

 

 

includelib

 

 

Defines the name of an IDL or H file that you want to place

 

 

 

within the generated IDL file.

 

 

 

 

library_block

 

 

Places a construct, such as an interface description, within

 

 

 

the generated IDL file. This ensures the construct is passed

 

 

 

to the type library, even if you don't reference it.

 

 

 

 

no_injected_text

 

 

Prevents the compiler from injecting text as the result of

 

 

 

using attributes within the code. This option assumes that

 

 

 

the text is either not needed or that you injected it manually.

 

 

 

For example, the /Fx compiler option uses this feature when

 

 

 

creating merge files.

 

 

 

 

satype

 

 

Specifies the data type for a SAFEARRAY structure.

 

 

 

 

version

 

 

Specifies a class version number.

 

 

 

 

OLE-DB Consumer

Database management is a mainstay of most developers. Somewhere along the way, most developers end up spending at least some time working with databases. That's why the inclusion of OLE-DB consumer attributes is so important. Using attributes in a database application can greatly reduce the amount of coding you need to do to perform simple and common tasks. Here's a simple example of what you might see within a method designed to work with databases.

HRESULT MyDataAccess()

{

[ db_source( db_source="DSN=FoodOrdersData", name="FoodOrder",

hresult="hr" ]

if (FAILED(hr)) return hr;

[ db_command(

db_command="SELECT * FROM FoodOrders", name="FoodOrderRowset", sourcename="FoodOrder",

hresult="hr" ]

if (FAILED(hr)) return hr;

return S_OK;

}

In this case, data access consists of a db_source and a db_command attribute. The db_source attribute allows us to gain access to the database connection defined by the FoodOrdersData data source name (DSN). The connection receives a name of FoodOrder and the result of the call appears in hr. Note that we don't create hr—C# automatically creates the HRESULT variable. The db_command attribute accepts a standard SQL command. The results of this command appear in FoodOrderRowset. Access to the database requires a connection, which is supplied by the sourcename parameter.

Microsoft has gone to great lengths to simplify database access in C# by using attributes. In fact, you'll find that most activities require only two or three of the six database attributes. Table 3.4 lists the attributes and describes them for you.

 

 

Table 3.4: DB-OLE Consumer Attributes

Attribute

 

Description

db_accessor

db_column

db_command

db_param

db_source

db_table

Binds the columns in a rowset to the corresponding accessor maps. You must provide an accessor number as the first parameter. The second parameter can contain a Boolean value that determines if the application automatically retrieves the accessor.

Binds a column to a rowset. You must provide a column name or the ordinal number of the column within the rowset to use for binding. Optional parameters include the data type, precision, scale, status, and length of the data.

Allows execution of database commands on an open connection. You must provide a database command as the first parameter. Optional parameters include the rowset name, source name, return value variable name, bindings value, and bulk fetch value.

Creates a connection between a member variable and an input or output parameter. You must provide a column name or the ordinal number of the column within the rowset to use for binding. Optional parameters include the parameter type, data type, precision, scale, status, and length of the data.

Opens a connection to a data source using a data provider. You must provide the name of a data source. Optional parameters include a name for the connection and the name of a return value variable. The connection string can take a number of forms—it's not necessary to use a DSN.

Opens an OLE-DB table. You must provide the name of the table you want to open. Optional parameters include a name for the table that the call returns, a data source name, and the name of a variable to receive the results of the operation.

An Attribute Example

You'll use some attributes more often than you will others. For example, anyone who builds components will want to know how to work with component attributes. One of the attributes you'll use most often is [Description] because it provides visual documentation of methods, events, and properties within your component. Another useful attribute is [Category] because

it helps you organize the properties that a component supports. Here's a quick example of the [Description] and [Category] attributes at work.

//Create the public property used to manipulate

//the data.

[Description("A simple property for saying Hello.") Category("Special")]

public string Hello

{

get

{

return _Hello;

}

set

{

_Hello = value;

}

}

When you apply the [Description] attribute to a property, the component user sees descriptive text when they select the property in the Properties window. If the user also chooses to display the properties in category order, the [Category] attribute determines where the property appears in the list. Figure 3.12 shows how the descriptive text would appear in the Properties window. Note that the string will automatically break at word boundaries if it's too long for the window area. The property also appears in the Special category because the window is in Categorized mode.

Figure 3.12: Some attributes make your components easier for other developers to use.

Where Do You Go From Here?

The essential lesson to learn in this chapter is that C# uses classes for everything. You won't find global variables or functions that sit by themselves. Everything is part of a class and you access the code using objects based on the class template. Consequently, you need to know more about class creation in C# than any other language that you might have used in the past. Fortunately, C# makes creating classes relatively easy. It even does some of the coding for you.

Every class has three hierarchical levels as a minimum that include namespace, class, and method. You can nest levels. For example, an application could include more than one level of methods. Each level can also contain multiple sublevels. For example, a namespace could host more than one class or other namespaces.

This chapter provides a lot of information in a very small space. It provides at least one source on the Internet that you'll want to check out. Make sure you spend time learning the basics. For example, it's important to know how to create properties and work with delegates. You'll also want to spend some time learning about attributes. Using attributes can save you an immense amount of time and effort.

At this point, you should have a better idea of how to build basic classes. These are fully functional classes, but they lack some of the polish you could obtain using advanced coding techniques. You've learned how to pass data, create methods, and handle events. We'll discuss advanced class construction techniques, like sealed classes, in Chapter 4.

Chapter 4: Advanced Class Topics

Overview

As mentioned in other areas of the book, classes are the foundation of creating applications of any type in .NET. Gone are the freestanding bits of code and data that used to permeate applications in the past. Knowing how to work with classes isn't just a nice topic to know in

.NET, it's an essential skill.

In Chapter 3, we discussed some of the basics of creating classes. This chapter builds on those techniques that you learned in that chapter. We'll discuss how you can perform some advanced programming with classes in .NET. For example, you'll learn about new ways to work with the command line using multiple constructors (and then to do something with the data once you receive it). We'll also visit error handling—a required skill that many developers ignore at the user's displeasure. Finally, you'll learn how to create alternative class types, including the mysterious sealed class.

Application Techniques

Many developers begin learning about a new language by creating small applications to test various language features. It turns out, that's the best way to learn about classes as well. Creating small classes that you can build quickly and see the results of instantly helps you learn more about how .NET works.

The following sections demonstrate some class techniques by creating small applications to demonstrate them. For example, the command-line example not only shows you how to enhance your next application with command-line processing, it also shows you how to create multiple constructors for your form class. You'll also learn how to perform various types of parsing within .NET.

Processing the Command Line

Processing the command line is an important feature in all but the smallest applications. At the very least, you want users to have access to the print capabilities of an application without opening it. In addition, creating command-line processing capabilities is an essential part of making your application script and task scheduler friendly. These application environments often need to provide direct input to the application in order to open files and perform other tasks. In short, knowing how to work with the command line is an essential task for everyone to learn.

We created a basic command-line processing example in Chapter 3. In that example, the Main() method determined if the user passed any command-line parameters, then adjusted the method it used to instantiate the Param class to match the input. However, this isn't necessarily the best way to handle the situation. Main() should be able to instantiate the form class without worrying about the command-line parameters.

This section of the chapter shows an example of how you can use multiple constructor overrides to provide a better class interface for your application. Main() won't need to know about the command-line parameters at all. It will simply pass them along and the form constructor will take care of the rest. In addition, this example will demonstrate how to parse and use the command-line parameters to perform useful work.

Note The example in the \Chapter 04\CommandLine2 folder might seem a tad incomplete because it only contains the Open, Print, and Exit commands on the File menu. The focus of this example is the command line, so we don't need to address other filehandling issues until the next chapter.

Parsing the Command Line

One of the difficult considerations for an application that parses the command line and performs some tasks with the input is that the command-line features will almost certainly duplicate menu functionality. You must consider at the outset which parts of the menu portion of the command to duplicate and which portions to place in a common function. For example, the process of reading a file will be the same whether you do it from the command line or from within a menu. The menu version will require a user interface, but you can separate that portion of the command into menu code. On the other hand, printing might require more processing when working from within the application and the command-line version will almost certainly want to exit immediately after the print job is complete. In this case, the command-line version of the code and the menu version of the code could be at odds, making it less time consuming, especially with regard to debugging, to write duplicate code instead.

As previously mentioned, the example will contain two constructors for the form. The first is a simple constructor that doesn't accept any command-line input. The second constructor will accept any number of input arguments, but the code is constructed such that only two parameters will actually do any work. The application will accept the name of an existing file and the /p argument for printing. Anything else is neatly disposed of by the code during parsing. Listing 4.1 shows the two constructors for this example. (You'll find the source code for this example in the \Chapter 04\CommandLine2 folder of the CD.)

Listing 4.1: Multiple Constructors Used for Command-Line Parsing

public Form1()

{

//Required for Windows Form Designer support InitializeComponent();

//We don't want to exit the application. CommandLineOnly = false;

}

// Determines if we're using command line processing alone. private bool CommandLineOnly;

public Form1(String[] args)

 

{

FInfo = null;

// File Information

FileInfo

int

Count;

// Command Line Argument

bool

DoPrint = false;

// Print Document?

PrintDocument

PD = new PrintDocument();

// Document Rendering

//Required for Windows Form Designer support InitializeComponent();

//We don't want to exit the application yet. CommandLineOnly = false;

//Process each command line argument in turn. Look for a

//filename and the /P parameter. Ignore anything else. for (Count = 0; Count < args.Length; Count++)

{

// Check if there is a document to open. try

{

// Only try this until we find a file. if (File2Open == null)

FInfo = new FileInfo(args[Count]);

}

catch

{

}

if (File2Open == null && FInfo != null) if (FInfo.Exists)

File2Open = args[Count];

// Check if the argument is /P. if (args[Count].ToUpper() == "/P")

DoPrint = true;

}

// Open the document if one exists. if (FInfo.Exists)

{

DoFileOpen(File2Open);

// Print the document if requested. if (DoPrint)

{

// Object containing document to print. Doc2Print = new StringReader(txtEdit.Text);