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

C# ПІДРУЧНИКИ / c# / Apress - Accelerated C# 2005

.pdf
Скачиваний:
81
Добавлен:
12.02.2016
Размер:
2.09 Mб
Скачать

Acknowledgments

Writing a book is a long and arduous process, during which I have received tons of great support, which I greatly appreciate, from friends and family. The process would have been much more difficult, and arguably much less fruitful, without their support. Thank you all!

I would like to specifically call out the following individuals for their contribution to this work. If I have left anyone out, it is purely my mistake and not one I intended. I would like to thank (in no particular order) David Weller, Stephen Toub, Rex Jaeschke, Vladimir Levin, Jerry Maresca, Chris Pels, Michael Pulk, Christopher T. McNabb, Brad Wilson, Peter Partch, Paul Stubbs, Rufus Littlefield, Tomas Restrepo, John Lambert, Joan Murray, Sheri Cain, Jessica D’Amico, Karen Gettman, Jim Huddleston, Richard Dal Porto, Gary Cornell, Brad Abrams, Ellie Fountain, Nicole Abramowitz, and the entire Apress crew, and finally, Shelley Nash.

xxi

Introduction

Visual C# .NET (C#) is relatively easy to learn for anyone familiar with another object-oriented language. Even someone familiar with Visual Basic 6.0, who is looking for an object-oriented language, will find C# easy to pick up. However, though C#, coupled with the .NET Framework, provides

a quick path for creating simple applications, you still must know a wealth of information and understand how to use it correctly in order to produce sophisticated, robust, fault-tolerant C# applications. I teach you what you need to know and explain how best to use your knowledge so that you can quickly develop true C# expertise.

Idioms and design patterns are invaluable for developing and applying expertise, and I show you how to use many of them to create applications that are efficient, robust, fault-tolerant, and exception-safe. Although many are familiar to C++ and Java programmers, some are unique to .NET and its Common Language Runtime (CLR). I show you how to apply these indispensable idioms and design techniques to seamlessly integrate your C# applications with the .NET runtime, focusing on the new capabilities of C# 2.0.

Design patterns document best practices in application design that many different programmers have discovered and rediscovered over time. In fact, the .NET Framework itself implements many well known design patterns. Similarly, over the past three versions of the .NET Framework and the past two versions of C#, many new idioms and best practices have come to light. You will see these practices detailed throughout this book. Also, it is important to note that the invaluable tool chest of techniques is evolving constantly.

.NET 2.0 provides a unique and stable cross-platform execution environment. C# is only one of the languages that targets this powerful runtime. You will find that many of the techniques explored in this book are also applicable to any language that targets the .NET runtime.

For those of you who have significant C++ experience and are familiar with such concepts as C++ canonical forms, exception safety, Resource Acquisition Is Initialization (RAII), and const correctness, this book explains how to apply these concepts in C#. If you’re a Java or Visual Basic programmer who has spent years developing your toolbox of techniques and you want to know how to apply them effectively in C#, you’ll find out how to here.

As you’ll see, it doesn’t take years of trial-and-error experience to become a C# expert. You simply need to learn the right things and the right ways to use them. That’s why I wrote this book for you.

About This Book

I assume that you already have a working knowledge of some object-oriented programming language, such as C++, Java, or Visual Basic (.NET or 2005). Since C# derives its syntax from both C++ and Java, I don’t spend much time covering C# syntax, except where it differs starkly from C++ or Java. If you already know some C#, you may find yourself skimming or even skipping Chapters 1 through 3.

Chapter 1, “C# Preview,” gives a quick glimpse of what a simple C# application looks like, and it describes some basic differences between the C# programming environment and the native C++ environment.

xxiii

xxiv I N T R O D U C T I O N

Chapter 2, “C# and the CLR,” expands on Chapter 1 and quickly explores the managed environment within which C# applications run. I introduce you to assemblies, the basic building blocks of applications, into which C# code files are compiled. Additionally, you’ll see how metadata makes assemblies self-describing.

Chapter 3, “C# Syntax Overview,” surveys the C# language syntax. I introduce you to the two fundamental kinds of types within the CLR: value types and reference types. I also describe namespaces and how you can use them to logically partition types and functionality within your applications.

Chapters 4 through 13 provide in-depth descriptions on how to employ useful idioms, design patterns, and best practices in your C# programs and designs. I’ve tried hard to put these chapters in logical order, but occasionally one chapter may reference a technique or topic covered in a later chapter. It is nearly impossible to avoid this situation, but I tried to minimize it as much as possible.

Chapter 4, “Classes, Structs, and Objects,” provides details about defining types in C#. You’ll learn more about value types and reference types in the CLR. I also touch upon the native support for interfaces within the CLR and C#. You’ll see how type inheritance works in C#, as well as how every object derives from the System.Object type. This chapter also contains a wealth of information regarding the managed environment and what you must know in order to define types that are useful in it. I introduce many of these topics in this chapter and discuss them in much more detail in later chapters.

Chapter 5, “Interfaces and Contracts,” details interfaces and the role they play in the C# language. Interfaces provide a functionality contract that types may choose to implement. You’ll learn the various ways that a type may implement an interface, as well as how the runtime chooses which methods to call when an interface method is called.

Chapter 6, “Overloading Operators,” details how you may provide custom functionality for the built-in operators of the C# language when applied to your own defined types. You’ll see how to overload operators responsibly, since not all managed languages that compile code for the CLR are able to use overloaded operators.

Chapter 7, “Exception Handling and Exception Safety,” shows you the exception-handling capabilities of the C# language and the CLR. Although the syntax is similar to that of C++, creating exception-safe and exception-neutral code is tricky—even more tricky than creating exception-safe code in native C++. You’ll see that creating fault-tolerant, exception-safe code doesn’t require the use of try, catch, or finally constructs at all. I also describe some of the new capabilities within the .NET 2.0 runtime that allow you to create more fault-tolerant code than was possible in .NET 1.1.

Chapter 8, “Working with Strings,” describes how strings are a first-class type in the CLR and how to use them effectively in C#. A large portion of the chapter covers the string-formatting capabilities of various types in the .NET Framework and how to make your defined types behave similarly by implementing IFormattable. Additionally, I introduce you to the globalization capabilities of the framework and how to create custom CultureInfo for cultures and regions that the .NET Framework doesn’t already know about.

Chapter 9, “Arrays, Collection Types, and Iterators,” covers the various array and collection types available in C#. You can create two types of multidimensional arrays, as well as your own collection types while utilizing collection-utility classes. You’ll see how to define forward, reverse, and bidirectional iterators using the new iterator syntax introduced in C# 2.0, so that your collection types will work well with foreach statements.

I N T R O D U C T I O N xxv

Chapter 10, “Delegates, Anonymous Functions, and Events,” shows you the mechanisms used within C# to provide callbacks. Historically, all viable frameworks have always provided a mechanism to implement callbacks. C# goes one step further and encapsulates callbacks into callable objects called delegates. Additionally, C# 2.0 allows you to create delegates with an abbreviated syntax called anonymous functions. Anonymous functions are similar to lambda functions in functional programming. Also, you’ll see how the framework builds upon delegates to provide a publish/subscribe event notification mechanism, allowing your design to decouple the source of the event from the consumer of the event.

Chapter 11, “Generics,” introduces you to probably the most exciting feature added to C# 2.0 and the CLR. Those familiar with C++ templates will find generics somewhat familiar, though many fundamental differences exist. Using generics, you can provide a shell of functionality within which to define more specific types at run time. Generics are most useful with collection types and provide great efficiency compared to the collections of previous .NET versions.

Chapter 12, “Threading in C#,” covers the tasks required in creating multithreaded applications in the C# managed virtual execution environment. If you’re familiar with threading in the native Win32 environment, you’ll notice the significant differences. Moreover, the managed environment provides much more infrastructure for making the job easier. You’ll see how delegates, through use of the I Owe You (IOU) pattern, provide an excellent gateway into the process thread pool. Arguably, synchronization is the most important concept when getting multiple threads to run concurrently. This chapter covers the various synchronization facilities available to your applications.

Chapter 13, “In Search of C# Canonical Forms,” is a dissertation on the best design practices for defining new types and how to make them so you can use them naturally and so consumers won’t abuse them inadvertently. I touch upon some of these topics in other chapters, but I discuss them in detail in this chapter. This chapter concludes with a checklist of items to consider when defining new types using C#.

C H A P T E R 1

■ ■ ■

C# Preview

Since this is a book for experienced object-oriented developers, I assume that you already have some familiarity with the .NET or Java runtimes, and even if you don’t, that you can get comfortable with the .NET runtime on your own. Nevertheless, it’s important to look at some of the similarities and differences between Visual C# .NET (C#), Visual C++ .NET (C++), and Java, and then go through an elementary “Hello World!” example for good measure. If you already have experience building

.NET applications, you may want to skip this chapter. However, you may want to read the section “Overview of What’s New in C# 2.0.”

Differences Between C#, C++, and Java

C# is a strongly typed object-oriented language whose code visually resembles C++ and Java. This decision by the C# language designers allows C++ and Java developers to easily leverage their knowledge to quickly become productive in C#. C# syntax differs from C++ and Java in some ways, but most of the differences among these languages are semantic and behavioral, stemming from differences in the runtime environments in which they execute.

C#

C# source code compiles into managed code. Managed code, as you may already know, is an intermediate language (IL) because it is halfway between the high-level language (C#) and the lowest-level language (assembly/machine code). At run time, the Common Language Runtime (CLR) compiles the code on the fly by using Just In Time (JIT) compiling. As with just about anything in engineering, this technique comes with its pros and cons. It may seem that an obvious con is the inefficiency of compiling the code at run time. This process is different from interpreting, which is typically found in scripting languages such as Perl and JScript. The JIT compiler doesn’t compile a function or method each and every time it’s called; it does so only the first time, and when it does, it produces machine code native to the platform on which it’s running. An obvious pro of JIT compiling is that the working set of the application is reduced, because the memory footprint of intermediate code is smaller. During the execution of the application, only the needed code is JIT-compiled. Unused code, such as printing code, is never JIT-compiled if the user never prints a document. Moreover, the CLR can optimize the program’s execution on the fly at run time. For example, on the Windows platform, the CLR may determine a way to reduce page faults in the memory manager by rearranging compiled code in memory, and it could do all this at run time. Once you weigh all the pros together, you find that they outweigh the cons for most applications.

1

2 C H A P T E R 1 C # P R E V I E W

Note Actually, you can choose to code your programs in raw IL while building them with the IL Assembler (ILASM). However, it will likely be an inefficient use of your time. Very rarely can high-level languages not provide a certain capability that you can achieve only with raw IL code.

C++ and Java

Unlike C# and Java, C++ code traditionally compiles into native code. Native code is the machine code that’s native to the processor for which the program was compiled. You can transform C++ into managed code by using the managed extensions of Visual Studio 2003 or by using C++/Common Language Infrastructure (CLI) in Visual Studio 2005. However, for the sake of discussion, assume that we’re talking about natively compiled C++ code. If you want your native C++ application to run on different platforms, such as on both a 32-bit platform and a 64-bit platform, you must compile it separately for each. The native binary output is generally not compatible across platforms.

IL, on the other hand, is compatible across platforms, because it, along with the CLI upon which the CLR is built, is a defined international standard.1 This standard is rapidly gaining traction and being implemented beyond the Microsoft Windows platform. (I recommend you check out the work the Mono team has accomplished with regards to creating alternate, open source Virtual Execution Systems [VESs] on other platforms.2) Included in the CLI standard is the Portable Executable (PE) file format for managed modules. Therefore, you can actually compile a C# program on a Windows platform and execute the output on both Windows and Linux without having to recompile, because even the file format is standardized.3 This degree of portability is extremely convenient and was in the hearts and minds of the COM/DCOM designers back in the day, but for various reasons, it failed to succeed across disparate platforms at this level.4 One of the major reasons for that failure is that COM lacked a sufficiently expressive and extensible mechanism for describing types and their dependencies. The CLI specification solves this nicely by introducing metadata, which I’ll describe in Chapter 2.

CLR Garbage Collection

One of the key facilities in the CLR is the garbage collector (GC). The GC frees you from the burden of handling memory allocation and deallocation, which is where many software errors can occur. However, the GC doesn’t remove all resource-handling burdens from your plate, as you’ll see in Chapter 4. For example, a file handle is a resource that must be freed when the consumer is finished with it, just as memory must be freed in the same way. The GC handles only memory resources directly. To handle resources other than memory, such as database connections and file handles, you can use a finalizer (as I’ll show you in Chapter 13) to free your resources when the GC notifies you that your object is being destroyed. However, an even better way is to use the Disposable pattern for this task, which I’ll demonstrate in Chapters 4 and 13.

1.You can find the CLI standard document Ecma-335 at www.ecma-international.org. Additionally, Ecma-334 is the standard document for the C# language.

2.You can find the Mono project on the Internet at www.mono-project.com.

3.Of course, the target platform must also have all of the dependent libraries installed. This is quickly becoming a reality, considering the breadth of the .NET Standard Library. For example, check out www.go-mono.com/docs/ to see how much coverage the Mono project libraries have.

4.For all the gory details, I recommend reading Don Box and Chris Sells’ Essential .NET, Volume I: The Common Language Runtime (Boston, MA: Addison-Wesley Professional, 2002). (The title leads one to believe that Volume II is due out any time now, so let’s hope it’s not titled in the same vein as Mel Brooks’ History of the World: Part I.)

C H A P T E R 1 C # P R E V I E W

3

Note The CLR references all objects of reference type indirectly, similar to the way you use pointers and references in C++, except without the pointer syntax. When you declare a variable of a reference type in C#, you actually reserve a storage location that has a type associated with it, either on the heap or on the stack, which stores the reference to the object. So, when you copy an object reference in one variable into another variable, you end up with two variables referencing the same object. All reference type instances live on the managed heap. The CLR manages the location of these objects, and if it needs to move them around, it updates all the outstanding references to the moved objects to point to the new location. Also, value types exist in the CLR, and instances of them live on the stack or as a field of an object on the managed heap. Their usage comes with many restrictions and nuances. You normally use them when you need a lightweight structure to manage some related data. Value types are also useful when modeling an immutable chunk of data.

C#, much like Java, allows you to develop applications rapidly while dealing with fewer mundane details than you have to deal with in a C++ environment. At the same time, C# provides a language that feels familiar to either C++ or Java developers.

Example of a C# Program

Let’s take a 20,000-foot look down at a C# program. Consider the venerable “Hello World!” program that everyone knows and loves. A console version of it looks like this in C#:

class EntryPoint { static void Main() {

System.Console.WriteLine( "Hello World!" );

}

}

Note the structure of the C# program. It declares a type (a class named EntryPoint) and a member of that type (a method named Main). Java programmers will be very familiar with this. This differs from C++, where you declare a type in a header and define it in a separate compilation unit, usually a .cpp file. Also, metadata (generated transparently by the C# compiler) removes the need for the forward declarations and inclusions as required in C++. In fact, forward declarations don’t even exist in C#.

Both Java and C++ programmers will find the static Main method familiar, except for the fact that its name begins with a capital letter. Every program requires an entry point, and in the case of C#, it is the static Main method. C++ programmers will notice some further differences. For example, the Main method is declared inside of a class (in this case, named EntryPoint). In C#, you must declare all methods inside a class or struct. There is no such thing as a static, free function as there is in C++. The return type for the Main method may be either of type int or void, depending on your needs. In my example, Main has no parameters, but if you need access to the command-line parameters, your Main method can declare a parameter (an array of strings) to access them.

Note If your application contains multiple types with a static Main method, you can select which one to use via the /main compiler switch.

You may notice that the call to WriteLine seems verbose. I had to qualify the method name with the class name Console, and I also had to specify the namespace that the Console class lives in (in this case, System). .NET (and therefore, C#) supports namespaces to avoid name collisions in the vast global namespace. However, instead of having to type the fully qualified name, including the namespace, every time, C# provides the using directive, which is analogous to Java’s import and C++’s using namespace. So, you could rewrite the previous program slightly, as Listing 1-1 shows.

Соседние файлы в папке c#