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

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

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

Unlike most of the properties, the HeaderLevel property requires some custom handling. You need to ensure the input value makes sense, so this property performs some range checking in the set() method. This is a good example of how you can get around problems with invalid input. The code could also include an error message, but setting the property to a valid value is usually enough.

The Render() method is where all of the processing for this control takes place. The code begins by testing the _Absolute variable. If the user has set the AbsolutePosition property true, then the control will output CSS-compliant code. Otherwise, the control will output generic HTML code.

We'll discuss the CSS-compliant code generation first. This section of the code begins by outputting a <span> tag. This tag consumes most of the code section because it contains all of the information required to format the text of the control. The <span> tag begins with a definition of the width of the label.

One of the more interesting problems you'll face is discovering the values contained in some C# enumerations. For example, the WebControl.Style enumeration is one that you'll commonly use. Unfortunately, the content of this enumeration might not precisely match the input from your web page. Some items are distributed. The easiest way to discover how an enumeration is going to work is to create a quick listing of the members. For example, the code in Listing 6.5 shows how to work with the WebControl.Style enumeration.

Tip Sometimes you don't need to use all of the CSS values in your control. The while loop code in Listing 6.5 will return the key value and associated CSS value input from the web page. The key value is case sensitive, so this technique saves time spent playing with various permutations of spelling and capitalization for the Style[index] value. Right-click the resulting web page and use the View Source command to display the key values. Cut and paste the keys directly into your code as needed.

The rest of the CSS portion of the code works with font attributes. In most cases, all you have to do is detect the font value, then output a keyword as needed. The text-decoration attribute requires special handling because it can contain any of three values (including combinations of all three values) for overline, underline, and strikethrough. The remainder of the code completes the <span> tag. Notice the use of a newline (\r\n pair) character in the output. If you don't include a newline character, the code is almost unreadable when you use the View Source command in Internet Explorer to view the results of your handiwork.

The HTML code portion of the Render() method outputs an HTML version of the CSS section. The match isn't exact because HTML offers fewer capabilities. However, the HTML version does offer some nice formatting features, and all you need to do to use it is change the value of the AbsolutePosition property. The same label works in both cases.

Web Controls require a few usability tweaks before they're friendly additions to your toolkit. When you add a Web Control to an ASP .NET Web Application, it relies on a tag to identify the control. The Microsoft-supplied controls use the <asp:> tag, but your controls will need to use something else to avoid confusion. Defining a tag for your application is easy. Just add the following code (or something similar) to the AssemblyInfo.CS file for your project.

// Special for Web Controls.

[assembly: TagPrefix("MyWebControl", "MyWebControl")]

The first argument for the [TagPrefix] attribute is the name of the namespace for your control. The entire namespace will use the same prefix. The second argument is the name of the tag. In this case, I used the same value for both namespace and tag, but this isn't a requirement.

The second addition to your control is a bitmap that will appear in the Toolbox when you add the control to the IDE. Create a new 16 × 16-pixel bitmap and give it the same name as the control class. The example uses SpecialHeader.BMP because that's the name of the class. Draw something interesting for the bitmap. Highlight the bitmap entry in Solution Explorer and select Embedded Resource for the Build Action property. This option forces the compiler to include the bitmap within the control.

Compile the control and you're ready to go. All you need to do is add the control to the toolbox. Right-click the Toolbox and choose Customize Toolbox from the context menu. You'll see the Customize Toolbox dialog box. Select the .NET Framework Components tab. Click Browse and you'll see an Open dialog box. Locate your Web Control, highlight it, then click Open. C# will add the control to the Toolbox.

Add the control to the ASPX file of your application. The first check you need to make is whether Visual Studio added the tag for your control to the top of the Web page automatically. You also need to check whether the tag value is correct. Here's what the tag entry looks like for the test application. (Note that this tag normally appears on one line.)

<%@ Register TagPrefix="mywebcontrol" Namespace="MyWebControl" Assembly="MyWebControl" %>

Try various control options. You'll normally see any changes you make within the Designer. However, there are some situations where the output from your website won't quite match the output in the Designer. For example, the size of the browser window will make a difference, as will the capabilities of the browser. Figure 6.15 shows some sample output from this control. The first label relies on standard HTML, while the second relies on CSS formatting.

Figure 6.15: The SpecialHeader control works equally well in CSS or HTML mode.

Debugging Web Control Library Projects

One of the most common problems with debugging a Web Control Library project is gaining access to the remote server. The first problem that many developers experience is not setting the startup project as we did in the previous section. The second problem is one of rights to the server. The developer must belong to the Debugger Users group or the debugger won't work properly, even if you're a member of the Administrator group (just why this happens is

something that Microsoft hasn't answered). You'll also experience fewer problems if you log into the domain rather than the local machine.

Tip A Web Control renders within a browser, which means that not all browsers provide full support for them. This means you have to design the control for maximum compatibility. The runtime environment performs part of this work for you by mapping some commands and providing overrides for others, given a specific browser client. However, it pays to test your control using several browsers to ensure it will work as predicted.

Another connection problem occurs when you have the project configured for localhost debugging and you attempt to use a two-machine setup. If you can start the application using the Debug Start Without Debugging command, but not with the Debug Start command, it's likely that you have a configuration problem in the Web.CONFIG or Global.ASAX file. For example, several TCP/IP addresses in Web.CONFIG are set to 127.0.0.1, which is the localhost setting. Make sure you wait long enough for the project to start. It takes an incredibly long time for the debugger to set up all of the required connections, even with a simple project.

Note The Global.ASAX file is the ASP.NET replacement for the Global.ASA file used in previous versions of Visual Studio. Make sure you take this change into consideration when moving your applications from ASP to ASP.NET.

Once the program has started, you'll immediately stop at any breakpoints set within the application. In some cases, you'll need to click Refresh in the browser to force the application to reload the page before a new breakpoint will do anything. For example, you need to click Refresh every time you want to check code within the Render() method.

Creating Components

Components serve as the nonvisual building problem of application development. A component can reside on the client or the server, work in the background or the foreground, and require user input or act without it. You'll never place components in your Toolbox; you'll always refer to them and then use them within your application. In short, components are the invisible building blocks of the Visual Studio .NET environment.

The following sections discuss several component issues, and we'll create two component examples. The first example is a managed component used in a managed environment. You'll find that using components in a managed environment is extremely easy compared to experiences you might have had in the unmanaged environment. The second example will show you how to move the component from the managed environment to the unmanaged environment and use it within a native executable. As far as the native executable is concerned, it's just like using any other COM component. Of course, the ability to move between environments can't be overemphasized.

Class Library Example

The .NET Framework provides a number of text manipulation classes. However, it seems like there are always more ways to manipulate text than there are prebuilt methods to do it. The example in this section will add a few new text manipulation methods to your arsenal and show how to build a simple component as well.

You'll begin with the Class Library project. The example uses a project name of TextChange and you'll find the associated code in the \Chapter 06\TextChange folder on the CD. (The example also includes a test application as part of the project.) The project is empty when you create it, except for a bare skeleton containing a sample namespace and class name. Listing 6.6 shows the source code for this example.

Listing 6.6: The TextChange Component Methods Modify Your Text

///<summary>

///A class for manipulating text.

///</summary>

public class Manipulate

{

public Manipulate()

{

}

///<summary>

///A method for reversing the text.

///</summary>

///<param name="Input">Requires an input string to reverse.</param>

///<returns>A string containing reversed text.</returns>

public string Reverse(string Input)

{

string

Temp = "";

//

Temporary holding string.

int

Counter;

//

Loop control variable.

// Create the reversed string.

for (Counter = 1; Counter <= Input.Length; Counter++)

{

Temp = Temp + Input.Substring(Input.Length - Counter, 1);

}

// Return the string value. return Temp;

}

///<summary>

///Return the last character of the string.

///</summary>

///<param name="Input">The input string.</param>

///<returns>The last character of the input string.</returns> public string GetLast(string Input)

{

return Input.Substring(Input.Length - 1, 1);

}

///<summary>

///Return the last <em>n</em> characters of the string.

///</summary>

///<param name="Input">The input string.</param>

///<param name="NumChars">The number of return characters.</param>

///<returns>The last <em>n</em> characters of the string.</returns> public string GetLast(string Input, int NumChars)

{

//Validate the input.

if (NumChars >= Input.Length) return Input;

else

return Input.Substring(Input.Length - NumChars, NumChars);

}

///<summary>

///Return the last character of the string.

///</summary>

///<param name="Input">The input string.</param>

///<returns>The last character of the input string.</returns>

///<remarks>Used without an object.</remarks>

public static string GetLastChar(string Input)

{

return Input.Substring(Input.Length - 1, 1);

}

}

As you can see, the code for this component is relatively simple. However, you should notice a few features of this code. The first feature is the use of the XML-like entries at the beginning of each method. These entries are exceptionally important when working with components because they help you document the component code. When you look at the method in the Object Browser, the additional entries help identify what you need to work with the method. Figure 6.16 shows an example of how these entries look when you view them in Object Browser.

Figure 6.16: Using the <summary> and other tags helps you document your component. Tip You can use common HTML formatting tags within the comments for your methods and

class. The formatting won't appear in Object Browser, but it will appear in other environments. For example, you can create documentation from the source code by filling these tags out. The generated document will retain the formatting you put in place.

The second feature of note is the use of two overrides for the GetLast() method. It's common to provide multiple overrides when it makes sense. In this case, it's reasonable to assume the user will want both overrides. If the component user doesn't specify the number of returned characters, the first form of the method is executed and the user receives just one character in return.

The GetLastChar() method is also special. Notice that this method is declared as static. The static keyword enables you to use the method without creating an object (an instance of the method) first. However, you can't use static methods with object references and you can't override an existing method with a static version. The second limitation means you can't create a static version of GetLast().

COM Component Example

What happens when you want to use your lovely new managed component with an older application? The component is definitely useful in the unmanaged environment, but you need to make a few minor changes first. Remember from the unmanaged component and control examples from the beginning of the chapter that you need to create a strong name to move from one environment to the other. The same holds true for unmanaged components. You'll still type SN -k MyKey at the command prompt to create a key pair for your component. However, unlike those previous examples, you'll compile the key directly into the component by adding it to the AssemblyInfo.CS file as shown here:

[assembly: AssemblyKeyFile("MyKey")]

When you recompile the component, it will have a strong name that won't affect its functionality. In fact, creating the strong name improves flexibility, because you can register the component in the GAC and make it generally available to any application that needs it. The GAC doesn't remove the need to create a reference to the component, but it enables CLR to find the component when called upon by an application. The following steps show how to make the TextChange.DLL accessible from an unmanaged application environment using COM.

1.Type RegASM /tlb:TextChange.TLB TextChange.DLL and press Enter at the command prompt. This action will register the component and create a type library that unmanaged environments can use to access the component. In some cases, you might want to run the program a second time using the /regfile:<Filename> switch to create a registry file for the application as well. The registry file makes it easier to move the component to other systems.

2.Type GACUtil -i TextChange.DLL and press Enter at the command prompt. The unmanaged application will call upon CLR to load and run the TextChange.DLL file. The problem is that CLR won't know where to find the component unless you make it part of the GAC.

At this point, the component is ready for use. However, you need to verify that the component is registered properly and that you can access it from an unmanaged application. The first step is easy. Open the OLE/COM Object Viewer. Open the .NET Category folder, then locate the TextChange.Manipulate entry. Figure 6.17 shows an example of what you'll see.

Figure 6.17: The OLE/COM Object Viewer will help you locate the COM entry for your component.

Performing the second test means creating an application in an unmanaged programming language environment such as Visual C++. You'll find a Visual C++ application that tests the component in the \Chapter 06\UnmanagedApp folder on the CD. Note that this example relies on the Dispatch ID method for instantiating the object, so it's not for the faint of heart.

Where Do You Go From Here?

This chapter has shown you how to work with managed and unmanaged controls and components in a number of ways. You've learned how to convert unmanaged components and controls for use in your managed applications and how to perform the process in reverse for your older unmanaged applications. In this chapter, you also learned about the basic component and control types. However, we'll build on that knowledge as the book progresses.

Because components and controls form the building blocks of modern applications, knowing how to create them is an essential part of your training as a developer. It's important to spend time learning to build components and controls of various types so you can learn about the intricacies of the .NET and Win32 environments. One of the ways you can learn more about both components and controls is to create a toolbox of components and controls you can use later. It's also important to begin converting your older components and controls now so you have them when you begin building larger managed applications. Likewise, you might want to develop all new components and controls using managed programming techniques, so knowing how to convert the new components and controls to work with older applications is critical.

Chapter 7 will introduce you to the concept of threads under .NET. A thread is one execution path within a process. Every process contains at least one, but perhaps more, threads of execution. We'll discuss how threads can make your applications more efficient and when too many threads can become a problem. In some cases, using fewer threads will result in better performance. You'll also learn about the thread types supported within the .NET Framework and when you should avoid using them in transitional projects.

Chapter 7: Working with Threads

Overview

Threads are a performance-enhancing feature of an application when they're developed correctly and used under the right circumstances. On the other hand, working with threads can become a mind-numbing exercise in application bugs, wasted developer time, and wasted resources. A thread (short for thread of execution) is a series of instructions executed independently of any other instructions within an application. An application always has at least one thread of execution, but may have many more.

This chapter provides you with a wealth of information about how threads work and how you can use them in your applications to make things run more efficiently. It's important to understand how threads get used in various types of projects. We'll also explore safety considerations for using threads. For example, we'll consider how critical sections ensure that two calling processes don't try to use the same portion of code at the same time. This chapter also explores the things you need to consider to ensure thread safety when working with local libraries. We'll spend time working through several example programs that demonstrate how threads work in different environments.

An Overview of Threads

Developers are constantly looking for ways to use machine resources more efficiently without spending more time developing applications. One way to do this is to write an application so that it can perform more than one task at a time. Windows provides this ability using threads. A thread is essentially a single subject of execution within an application. For example, using threads, an application could print and spell check a document in the background, while the user is typing in the foreground.

Threads don't perform any kind of magic. They won't make the user's machine any faster and the processor still won't be able to perform more than one task at a time. In other words, threads don't allow a machine to perform more than one task simultaneously unless that machine has the resources (i.e., multiple processors) to do so. However, threads can make use of resources that would otherwise go unused. A background thread can continue working while a foreground thread waits for user input. Another way to look at the issue is that the background thread frees the user interface so the user can get back to work more quickly.

Note You actually need to be aware of two entities when talking about threads. The term thread describes one set of contiguous instructions for performing a single task. A process, on the other hand, describes the application as a whole. Every executing application has at least one thread and one process. There's never more than one process for an application, but applications can always produce more than one thread. The distinction between processes and threads will become clear as the chapter progresses.

In some cases, an application requires threads for purposes other than efficiency. For example, a server application might need to service more than one user at a time. In this case, each thread would contain a separate user request. The threads enable the main application thread to continue accepting requests while pending requests are serviced in the background. The use of threads, in this case, ensures server availability but might not affect server performance.

Sometimes threads are simply inappropriate. For example, if an application spawns two threads, both of which require constant user interaction, the application will tend to run more slowly because the users have to divide their attention between the two threads. Likewise, adding a disk-intensive thread to an application that's already disk intensive is unlikely to increase system performance. The new thread will compete for resources with existing threads. Finally, adding threads to an overburdened server is unlikely to enhance server availability—quite the contrary—the server might cease to work at all.

Now that you have a little more information about threads, let's look at them in more detail. The following sections will help you understand how to use threads, what types of threads you can create, and how to use threads safely.

Uses for Threads

Theoretically, you can use threads in any C# application, including something as small as a component or control. Using threads in the managed environment is similar to working in the unmanaged environment in that you have the same thread types to consider and the underlying process is the same. In short, threads are a nearly universal solution to some types of problems.

Threads don't necessarily need to be large or complex either to make an application more efficient and responsive to user needs. In fact, you can use threads to perform small maintenance tasks in the background at regular intervals—something that you may not want to interrupt your main application to do. A thread can also replace timer-related tasks in some cases. In other words, threads aren't limited to performing any particular task.

Tip One of the easiest ways to add a timed thread to your application is to use the Timer control. The Timer continues to execute in the background while your main application continues to work. Of course, this type of thread only works for tasks that execute at a regular interval, such as updating a clock or checking the status of the application environment.

However, you do need to consider some issues before you start using threads for every small task that your application may need to perform. It's important to use threads correctly in order to avoid some common problems that developers seem to face with them. The following list provides you with some guidelines on what you should think about before using threads.

Debugging The biggest consideration from a developer perspective is that threads greatly increase the difficulty of debugging an application. A thread can actually hide bugs, or, at least, make them more difficult to find, since you'd now have to watch more than one thread of execution at a time.

Development Time Most developers are used to thinking about application programming in a linear fashion. In other words, given a specific event, the application will perform a series of steps to handle it. Using a multiple thread approach forces the programmer to think about application processes in parallel, rather than in a linear fashion.

True Efficiency While it's true that placing some tasks into background threads can make use of idle time in the application, there are situations when there isn't any idle time to exploit. In this situation, you'll find that the application is actually less efficient than before, because

there's a certain amount of overhead and housekeeping associated with using multiple threads. In other words, only use threads in situations when you anticipate there will be some amount of idle time to exploit.

Reliability Multiple threads of execution don't necessarily make an application failure prone, but there are more failure points to consider. Any time you add more failure points to anything, it becomes less reliable. There's a greater probability that the application will break simply because there are more things that can go wrong with it.

Unexpected Side Effects No matter how carefully you craft a multithreaded application, there are going to be side effects that you have to deal with, especially if the threads in the application interact. Even if you make your application thread safe and use critical sections, there's a chance that two threads will try to access the same variable at the same time in an unanticipated way. Not only do these unexpected side effects increase development and debugging time, but they make it more likely that a user will come across a problem that you can't duplicate with your setup. In other words, multithreaded applications will more than likely increase application support costs.

Now that you have a good overview of the way in which you can use threads in general, let's look at some specific multithreaded usage types. The following sections will explore the four most common ways that you'll see multiple threads in use: applications, DLLs, system services, and server applications. Each of these areas represents a major application type. We'll explore some of these multithreaded usage examples later in the chapter.

Applications

We've already explored this topic to some extent. Applications can benefit from multiple threads of execution in a number of ways. In fact, some of those ways will seem quite natural from a programming perspective, because the tasks in question can be broken from the main thread of execution quite easily. The following list will give you some ideas on how you can use multiple threads with applications.

Printing This one major task can always benefit from multiple threads in any application. Queuing a print job takes time, which means that the user is sitting at their desk, staring at the screen, doing nothing at all. In fact, some print jobs could take enough time that the user will give up trying to use the computer at all and do something else while waiting. Printing in the background in a separate thread is always an efficient way to handle this task.

As the User Types There are many tasks that fall into the "as the user types" category, but the two most common are spelling and grammar checks. Many applications offer the ability to check the user's spelling and grammar as they type, which reduces the need to check the whole document later. Of course, there are a lot of less common tasks that fall into this category as well. For example, you could check the validity of an equation as the user types it or make sure that a database entry is correct. For that matter, you could even suggest (as some applications do) a completed entry for the user, based on past input.

Repetition Repagination and other repetitive tasks can always occur as background threads. There isn't any need to take up the foreground task's time with things like updating the application clock. Most repetitive, continuous tasks can be relegated to background threads.