Anda di halaman 1dari 352

i

PURE

C#

William Robison

Pure C#
Copyright 2002 by Sams Publishing
All rights reserved. No part of this book shall be reproduced, stored in a retrieval system, or transmitted by any means, electronic, mechanical, photocopying, recording, or otherwise, without written permission from the publisher. No patent liability is assumed with respect to the use of the information contained herein. Although every precaution has been taken in the preparation of this book, the publisher and author assume no responsibility for errors or omissions. Nor is any liability assumed for damages resulting from the use of the information contained herein. International Standard Book Number: 0-672-32266-8 Library of Congress Catalog Card Number: 752063322666 Printed in the United States of America First Printing: December 2001 04 03 02 01 4 3 2 1

ASSOCIATE PUBLISHER
Jeff Koch

ACQUISITIONS EDITOR
Neil Rowe

DEVELOPMENT EDITOR
Susan Hobbs

MANAGING EDITOR
Matt Purcell

PROJECT EDITOR
George E. Nedeff

COPY EDITOR
Barbara Hacha

INDEXER
Larry Sweazy

PROOFREADER
Plan-it Publishing

Trademarks
All terms mentioned in this book that are known to be trademarks or service marks have been appropriately capitalized. Sams Publishing cannot attest to the accuracy of this information. Use of a term in this book should not be regarded as affecting the validity of any trademark or service mark.

TECHNICAL EDITORS
Mattias Sjogren Chris Crane

INTERIOR DESIGNER
Karen Ruggles

Warning and Disclaimer


Every effort has been made to make this book as complete and as accurate as possible, but no warranty or fitness is implied. The information provided is on an as is basis.

COVER DESIGNER
Aren Howell

PAGE LAYOUT
D&G Limited, LLC

Overview
Introduction 1

PART I
1 2 3 4

CONCEPTUAL REFERENCE
Language Elements 5

Working with Applications 47 The Base Class Libraries 73 Variables and Types 97

PART II
5 6 7 8 9 10 11

TECHNIQUE REFERENCE

113

Classes and Components 115 Memory Management and C# 131 Advanced Program Control 147 Unsafe Code 165 Using Metadata and Reflection 177 Configuring Components and Applications 193 Using the SDK 205

PART III
A B

REFERENCE

219

The C# Language 221 Key Types Quick Reference 259 Index 327

Contents
INTRODUCTION 1

PART I
1

CONCEPTUAL REFERENCE

LANGUAGE ELEMENTS 5 Program Structure ....................................................................................5 Types and Declarations............................................................................7 Built-In Value Types ..........................................................................7 Class Types ......................................................................................11 Interface Types ......................................................................................21 Managing Flow of Control ....................................................................23 Normal Execution ............................................................................23 Delegation ........................................................................................28 Exceptions ........................................................................................29 Inheritance..............................................................................................36 Unsafe Code ..........................................................................................40 Calling External Functions ..............................................................40 Writing Unsafe Code........................................................................41 Preprocessor Directives ........................................................................42 Summary ................................................................................................46 WORKING WITH APPLICATIONS 47 Intermediate Language and the Common Language Runtime..............47 Intermediate Language ....................................................................47 The Common Language Runtime ....................................................48 Executables, Assemblies, and Components ..........................................49 Assemblies........................................................................................49 Fusion ..............................................................................................50 Components ......................................................................................51 Component and Assembly Attributes ....................................................52 Development Tools ................................................................................55 Compiling C# with csc ....................................................................55 Managing Compilation with nmake ................................................58 Assembly Building with sn and al ..................................................61 Managing Assemblies with gacutil ..............................................66 Debugging in .NET................................................................................67 Debugging with DbgCLR ..................................................................68 A View Inside Compiled Assemblies ..............................................71 Summary ................................................................................................72

THE BASE CLASS LIBRARY 73 Architecture and Profiles ......................................................................73 Strings and Regular Expressions ..........................................................74 Collections ............................................................................................78 Serialization ..........................................................................................82 Input and Output ....................................................................................85 Network Communication ......................................................................88 Sockets..............................................................................................89 Communicating with Sockets ..........................................................89 Network Helper Classes ..................................................................94 Summary ................................................................................................96 VARIABLES AND TYPES 97 Simple Data............................................................................................97 Instantiation and Use ........................................................................97 Strings and String Conversions ........................................................98 Conversions and Casts....................................................................103 Classes..................................................................................................105 Interfaces..............................................................................................108 Structures ............................................................................................110 Enumerated Types................................................................................111 Summary ..............................................................................................112

PART II
5

TECHNIQUE REFERENCE

113

CLASSES AND COMPONENTS 115 Defining Entities and Classes ..............................................................115 Methods................................................................................................117 Properties ............................................................................................121 Namespaces..........................................................................................128 Summary ..............................................................................................130 MEMORY MANAGEMENT AND C# 131 Memory Management in the .NET Framework ..................................131 IDisposable ..................................................................................134 Finalizers ........................................................................................137 Using Weak References..................................................................142 Using Memory in C# ..........................................................................143 The fixed and using Statements..................................................144 Effective Memory Management ....................................................145 Summary ..............................................................................................145 ADVANCED PROGRAM CONTROL 147 Threads ................................................................................................147 Synchronization ..................................................................................151 Delegates..............................................................................................156 Events ..................................................................................................160 Summary ..............................................................................................163

vi

UNSAFE CODE 165 Pointers ................................................................................................165 The Problem with Pointers ............................................................165 A Solution ......................................................................................166 Using Memory with Platform Invoke ............................................167 Unsafe Contexts ..................................................................................170 Unsafe Language Elements ................................................................171 Memory Management in Unsafe Code................................................174 Summary ..............................................................................................175 USING METADATA AND REFLECTION 177 Using Attributes ..................................................................................177 Creating Custom Attributes ................................................................180 Reflection and Dynamic Binding ........................................................183 Reflection on Statically Bound Elements ......................................183 Dynamic Loading and Binding ......................................................184 Summary ..............................................................................................191 CONFIGURING COMPONENTS AND APPLICATIONS 193 Configuring Assemblies ......................................................................193 Configuration Levels ......................................................................193 Managing Configuration Files........................................................194 Managing Resources............................................................................197 Using Culture-Neutral Resources ..................................................198 Using Culture-Specific Resources..................................................200 Summary ..............................................................................................204 USING THE SDK 205 Compiling and Linking........................................................................205 Basic Compilation Steps ................................................................205 Integrating with COM+ ..................................................................210 Debugging and Inspection ..................................................................215 Deploying Your Solution ....................................................................216 Summary ..............................................................................................217

10

11

PART III
A

REFERENCE

219

THE C# LANGUAGE 221 Structural Elements..............................................................................221 Coding Elements..................................................................................233 KEY TYPES QUICK REFERENCE
ApplicationException

259 Class ..............................................................259 ApplicationException Constructor ..............................................259 ArgumentOutOfRangeException Class ................................................259 ArgumentOutOfRangeException Constructor..................................260 Properties ........................................................................................260

vii

Class ................................................................260 Constructor ................................................260 Array Class ..........................................................................................260 Properties ........................................................................................260 Methods ..........................................................................................261 Attribute Class ..................................................................................263 Properties ........................................................................................263 Methods ..........................................................................................264 AttributeTargets Enumeration..........................................................265 AttributeUsageAttribute Class ........................................................266 Properties ........................................................................................266 BitConverter Class ............................................................................266 Fields ..............................................................................................266 Methods ..........................................................................................266 Boolean Structure ................................................................................267 Fields ..............................................................................................267 Methods ..........................................................................................267 Byte Structure ......................................................................................268 Fields ..............................................................................................268 Methods ..........................................................................................268 Char Structure ......................................................................................269 Fields ..............................................................................................269 Methods ..........................................................................................269 Console Class ......................................................................................271 Properties ........................................................................................271 Methods ..........................................................................................271 Convert Class ......................................................................................272 Fields ..............................................................................................272 Methods ..........................................................................................272 DateTime Structure ..............................................................................274 DateTime Constructors....................................................................274 Fields ..............................................................................................275 Properties ........................................................................................275 Methods ..........................................................................................275 Operators ........................................................................................278 DayOfWeek Enumeration ......................................................................279 DBNull Class ........................................................................................279 Fields ..............................................................................................279 Methods ..........................................................................................279 Decimal Structure ................................................................................279 Decimal(Type val) (overloaded) ..................................................279 Fields ..............................................................................................280 Methods ..........................................................................................281 Operators and Type Conversions....................................................283
ArithmeticException ArithmeticException

Class ....................................................................................284 Constructor ....................................................................284 Properties ........................................................................................284 Methods ..........................................................................................284 Operators ........................................................................................285 Double Structure ..................................................................................285 Fields ..............................................................................................285 Methods ..........................................................................................286 Environment Class ..............................................................................287 Properties ........................................................................................287 Methods ..........................................................................................288 Environment.SpecialFolder Enumeration ........................................289 EventArgs Class ..................................................................................289 Fields ..............................................................................................289 EventHandler Delegate........................................................................289 Exception Class ..................................................................................289 Exception Constructors..................................................................289 Properties ........................................................................................290 Methods ..........................................................................................290 FlagsAttribute Class..........................................................................290 GC Class ................................................................................................291 Properties ........................................................................................291 Methods ..........................................................................................291 IComparable Interface..........................................................................292 Methods ..........................................................................................292 Int16 Structure ....................................................................................292 Fields ..............................................................................................292 Methods ..........................................................................................292 Int32 Structure ....................................................................................293 Fields ..............................................................................................293 Methods ..........................................................................................293 Int64 Structure ....................................................................................294 Fields ..............................................................................................294 Methods ..........................................................................................294 MarshalByRefObject Class ..................................................................295 MarshalByRefObject Constructor ..................................................295 Methods ..........................................................................................295 Math Class ............................................................................................296 Fields ..............................................................................................296 Methods ..........................................................................................296 MulticastDelegate Class ....................................................................298 MulticastDelegate Constructor ....................................................299 Methods ..........................................................................................299 Operators ........................................................................................299
Delegate Delegate

Class ..........................................................299 Class ........................................................................................299 Methods ..........................................................................................299 ObsoleteAttribute Class ....................................................................300 ObsoleteAttribute Constructor (overloaded) ..............................300 Properties ........................................................................................300 OperatingSystem Class........................................................................301 Properties ........................................................................................301 Methods ..........................................................................................301 Random Class ........................................................................................301 Random Constructor (overridden) ....................................................301 Methods ..........................................................................................301 SByte Structure ....................................................................................302 Fields ..............................................................................................302 Methods ..........................................................................................302 SerializableAttribute Class ............................................................303 Single Structure ..................................................................................303 Methods ..........................................................................................303 String Class ........................................................................................305 String Constructor ........................................................................305 Fields ..............................................................................................305 Properties ........................................................................................305 Methods ..........................................................................................306 Operators ........................................................................................311 ThreadStaticAttribute Class ............................................................311 TimeSpan Structure ..............................................................................311 TimeSpan Constructor ....................................................................311 Fields ..............................................................................................311 Properties ........................................................................................312 Methods ..........................................................................................313 Operators ........................................................................................314 TimeZone Class ....................................................................................314 Properties ........................................................................................314 Methods ..........................................................................................315 TypeCode Enumeration ........................................................................315 UInt16 Structure ..................................................................................316 Fields ..............................................................................................316 Methods ..........................................................................................316 UInt32 Structure ..................................................................................317 Fields ..............................................................................................317 Methods ..........................................................................................317 UInt64 Structure ..................................................................................318 Fields ..............................................................................................318 Methods ..........................................................................................318
NonSerializedAttribute Object

x
Uri

Class ..............................................................................................319 Constructor ..............................................................................319 Fields ..............................................................................................319 Properties ........................................................................................320 Methods ..........................................................................................321 UriBuilder Class ................................................................................322 UriBuilder Constructor (overridden) ............................................322 Properties ........................................................................................323 Methods ..........................................................................................323 UriHostNameType Enumeration............................................................324 UriPartial Enumeration ....................................................................324 Version Class ......................................................................................324 Version Constructor ......................................................................324 Properties ........................................................................................324 Methods ..........................................................................................325 Operators ........................................................................................325
Uri

INDEX

327

About the Author


William Robison is Director for Enterprise Applications Engineering with the Enterprise Social Investment Corporation in Columbia, MD. A Microsoft Certified Systems Engineer, Mr. Robison has fourteen years of experience in information systems design and development, during which he has served in a range of management and technical roles for the Air Force and in private industry. Mr. Robisons career has featured a range of platforms, including desktop PCs and workstations, NT and Unix servers, and IBM mainframes. To this book he brings more than ten years of experience programming with C++, Java, and now C#. Mr. Robisons professional interests include distributed systems, modeling, simulation, and visualization.

Dedication
For Marc, Nick, and Brandi and For the people of the Enterprise Family, proving that what we can envision, we can achieve.

Acknowledgments
It takes a lot more than research and typing to make a book happen. To start with, you need something of interest to write about, and the .NET platform is all of that. The people at Microsoft have done a great job with it, and I appreciate the time they took to share their thoughts with us. My personal thanks are due to Connie Sullivan for her help with the book (and a cool outing in Seattle!); I wish you the best in whatever comes next. For helping to bring this particular work to fruition, Im pleased to thank Neil Rowe, who got everything out of the gate and kept it moving. The effort that Susan Hobbs, Barbara Hacha, Mattias Sjgren, and George Nedeff put forth to clean and focus the content was invaluable. Thank you allworking with you has been great. Even with all the above, I wouldnt have made it without the support of my family and friends. Special thanks to Brandi Spitzer for the extra mile she went to help meet the schedule, and for putting up with the months I spent sequestered in my office. This wouldnt have happened without your help. Lastly, I wish to note my continuing appreciation to Debby, Teresa, Jim, Ed, and Helen. I am always grateful for your support and the example you set.

Tell Us What You Think!


As the reader of this book, you are our most important critic and commentator. We value your opinion and want to know what were doing right, what we could do better, what areas youd like to see us publish in, and any other words of wisdom youre willing to pass our way. As an Associate Publisher for Sams Publishing, I welcome your comments. You can fax, e-mail, or write me directly to let me know what you did or didnt like about this bookas well as what we can do to make our books stronger. Please note that I cannot help you with technical problems related to the topic of this book, and that because of the high volume of mail I receive, I might not be able to reply to every message. When you write, please be sure to include this books title and author as well as your name and phone or fax number. I will carefully review your comments and share them with the author and editors who worked on the book. Fax: E-mail: Mail: 317-581-4770
feedback@samspublishing.com

Jeff Koch Associate Publisher Sams Publishing 201 West 103rd Street Indianapolis, IN 46290 USA

Introduction
Hello, and welcome. By opening this book, youve opened a window into the future of Microsoft-based computing. The C# language is a component of a family of technologies, the .NET platform, that Microsoft has dedicated to building for the future. The technology environment Microsoft has created is undeniably powerful and forward looking, and the C# programming language is a fundamental part of it. This book presents C# in three parts. Part I: Conceptual Reference provides a lean, no-nonsense explanation for the language itself. C# is similar to C++ and Java, but its quite different below the surface. This part shows you why. Part II: Technique Reference is the meat and potatoes of the book. This part delivers a code-heavy reference to techniques you can apply in programs youre building. As you work, you can use this chapter to look up programming snippets that apply to the specific type of coding you need to do. Part III: Reference is a conventional reference to the technology. This part contains references to the language and to commonly used components in the Base Class Library (C#s runtime). You can use this quick reference for quick answers when youre programming. This book will not teach you programming nor the .NET platform, and it certainly isnt C# for Java Programmers. I assume that you already know how to program in some other language, that you already grasp the elementary concepts, and that we can go forward with the new things you need to learn. I discuss some aspects of .NET because the language exists within it and was built to exploit it, but where I could choose between including more information about .NET or more about C#, Ive chosen more C#. I hope youre reading this after purchasing the book, of course. If so, thanks for buying it! Youre going to get a lot of value from it, and its going to be on your desk for a long time. If youre in the bookstore trying to choose a book on C#, I hope that this is the one you choose. You wont find a more fat-free, useful reference to programming with C#. Bill Robison Fall 2001

PA R T I
CONCEPTUAL REFERENCE
C# is a revision and extension of C++; however, even a hardcore C++ programmer has a lot to learn before reaching a productive level of expertise with C#. Part I helps you get there by introducing the basics youll need to work with C#. Chapter 1, Language Elements, shows the syntax of the language and how to structure basic elements in C#. Chapter 2, Working with Applications, describes the tools in the .NET Framework SDK that you use to compile and link programs and libraries. Rounding out the picture, Chapter 3, The Base Class Library, describes the runtime class library that ships with .NET for your application to use. 1 Language Elements 2 Working with Applications 3 The Base Class Libraries 4 Variables and Types

Language Elements

CHAPTER 1
Language Elements
C# is a C++-based language, so its likely that you can pick up much of it through demonstration. However, its hard to find a single, reasonably encompassing description of the basic elements of the C# language. This chapter will help you to bridge from your current language into C# by summarizing the language and setting the stage for later chapters. Microsoft presents C# as a simple, modern, object-oriented, and type-safe language, intended to provide a highly productive tool that enables developers to exploit the capabilities of the framework. The C# development team has largely achieved those goals and delivered a powerful language competitive with any similar technology.

Program Structure
C# uses a C++-ish syntax. Lets start by looking at the basic language stream. Listing 1.1 shows a simple C# program that prints out a text message. Listing 1.1 A First Look at C#

1: using System; 2: 3: /// <summary> 4: /// Demonstrates simplest C# structure. 5: /// </summary> 6: class SimpleStart 7: { 8: static void Main(string[] args) 9: { 10: // send some text to the screen 11: Console.WriteLine(This is a pretty simple program.\n); 12: } 13: }

6 Chapter 1: Language Elements

C# code is composed of a stream of statements delimited by semicolons; some statements can contain other statements, grouping the contained items in braces. The class SimpleStart {} statement in Listing 1.1 is one such statement. For the most part, whitespace between statements is irrelevant, but you cant have whitespace within a keyword, identifier or other similar language elements. For example, the following are valid:
int x = 4444; string y = This is a string.;

However, the following are not valid:


int x = 44 44; string y = This is a string.; // whitespace in token is Bad Thing // newline in string constant is Bad Thing, too.

As you see in the preceding example, you can include comments within your code using a double slash (//). You can also use the traditional C form (/* comment */), or a triple slash for XML documentation. Statements can be declarative, like the class declaration at line 6 in Listing 1.1, and establish an element of the programs structure. They can alternatively be imperative and cause an action to be performed at runtime, such as the Console.WriteLine(...) statement at line 11. C# uses the concept of a namespace to organize symbol definitions in applications. Any item you refer to in your code must be declared within the namespace where you refer to it, in a namespace you have identified with the using statement, or you must refer to it using a fully qualified identifier. In the latter two cases, the namespace might be in your program or in an assembly that is available to your program via .NETs fusion process. Because the example uses the System.Console object at line 11, the first line in the program links the System namespace from the CLR components in the global assembly cache. A point where C# departs from C++ is that it requires all variables, functions, and other instancing declarations to be within a class declaration. No global constants, forward function definitions, or similar constructs exist. C# dispenses with header files, too, because they are not needed in the .NET environment.

NOTE
Saying that no global constants or declarations exist is OK to a point. The reality, though, is that you cant have unscoped global declarations. With the capability to declare static members of classes, you can still declare constants and general utilities and have them available throughout your code. The runtime is littered with static declarations of constants and functions. These declarations are placed within class declarations, however, limiting the likelihood of name collisions.

Ty p e s a n d D e c l a r a t i o n s

Types and Declarations


C# has two kinds of types: value types and reference types. Reference types are object values and are allocated on the heap. Value types optimize simple types, such as integers and floating point values, by storing them on the stack and dispensing with object initialization and destruction. All value types can be used as if they were objects, however, through a process called boxing.

Built-In Value Types


With two exceptions, the value types are the atomic, fundamental types of the system. Value types are allocated on the stack for fast creation, access, and disposal. Table 1.1 lists the value types currently defined. Table 1.1 C# Value Types Type Keyword Values
sbyte byte short ushort int uint long ulong float double decimal bool char enum struct

Runtime Type
SByte Byte Int16 UInt16 Int32 UInt32 Int64 UInt64 Single Double Object (Decimal) Boolean Char Int32 varies; default Int32

signed 8-bit int unsigned 8-bit int 16-bit signed int 16-bit unsigned int 32-bit signed int 32-bit unsigned int 64-bit signed int 64-bit unsigned int 32-bit float 64-bit float 128-bit float Boolean value wide char user defined user defined

Intrinsic Operators
The C# operators that apply to value types are listed in Table 1.2, with the operator types shown in decreasing order of precedence. Anyone familiar with C++ will be comfortable immediately with the operators in C#. A glaring omission, of course, is the absence of the pointer operators (*, ->) and the class scope operator (::). C# uses only the dot operator (.) for member selection, whether the member is contained in a value type, in a reference, or is a class-static member. Although I think that this loses something in clarity, it should reduce mistakes, particularly for programmers less familiar with the language.

8 Chapter 1: Language Elements

Table 1.2 Type


Primary

C# Operators Operator
. [] () a++, a-new typeof (un)checked

Effect
Member selection (for example, myObj.member) Array or indexer subscript Function call (for example, MyFunc( aParam )) postincrement/decrement Allocation Runtime type discovery Turn (off) on bounds checking Absolute sign Boolean NOT Bitwise NOT preincrement/decrement Explicit type cast (for example, (int)f) Multiply, divide Modulo divide Add, subtract Bitwise shift left, right Less than, greater than, less or equal, greater or equal Runtime type discovery Safe type conversion Test for equality Result has bit set where both operands have the corresponding bit set Result has bit set where only one operand has the corresponding bit set Result has bit set where either operand has corresponding bit set Result is true if both operands are true Result is true if either operand is true Chooses one or another expression based on a conditional or equality expression:
boolExp?trueAction(): falseAction()

Unary

+, ! ~ ++a, --a (Typename)a

Multiplicative Additive Shift Conditional

*, / % +, <<, >> <, >, <=, >= is as

Equality Bitwise AND Bitwise XOR Bitwise OR Boolean AND Boolean OR Boolean select

==, != & ^ | && || ?:

Assignment

= *=, /= %= +=, -= <<=, >>= &=, ^=, |=

Assigns right-hand side (RHS) to left-hand side Multiply (divide) LHS by RHS and assign the result to LHS Modulo divide and assign Add (subtract) and assign Shift left (right) and assign Bitwise operation and assign

Ty p e s a n d D e c l a r a t i o n s

Many objects in C# can be manipulated with operators, but some simply dont make sense. A bitwise shift, for example, cant be applied to a string. Furthermore, you can define operators of your own in your code as you require. In any operation, because C# is a strongly typed language, all variables in an operation have to match according to the rules of polymorphism. In other words, if you assign an object to another, as in
a = b; b

must be either the same type as a, or derived from the same type, or an implicit conversion must be defined that can take an object of bs type and create an object of as.

Working with Variables


Declaring and using variables is straightforward. You declare an instance of a type using the type name, followed by the name for the variable youre declaring. You can optionally follow the type name with an initializer:
int myIntVar = 5;

You can also declare an array by using square brackets (C#s subscript delimiter):
int[] myArrayVal = new int[] { 1, 2, 3, 4 };

These examples both initialize the resulting variable as part of the declaration. Although I included it in the preceding code, you can omit the new typename part when you initialize an array at declaration:
int[] myArray = { 1, 2, 3, 4, 5 };

You dont have to initialize the array at declaration, but if you leave off the initializer, you must initialize the variable later before you use it, as in the following:
int myIntVar; int[] myIntArr; ... myIntVar = new int(5); myIntArr = new int[25];

// creates the int with value 5 // creates the array and zeroes elements

In this example, I use the new operator to create new instances of the appropriate types (an int and a 25 element int array). Whatever initialization you choose, you must initialize any variable before its used. The compiler checks code paths and will not allow your code to compile if an execution path can result in using an uninitialized value. Microsoft calls this enforcement positive initialization and almost completely discards the conventional practice of implicitly initializing variables to zero. The only exception is in the case of arrays. When creating a new array, each element is initialized using its default value if its a value type, or null if its a reference type.

10 Chapter 1: Language Elements

struct
structs

and enum

and enums are the odd players in the value types. Enumerated types are declared using the enum keyword and serve mostly to assign names to constant values in an easy manner. Using enumerated types, however, is not as easy as it could be. For example, consider the following declaration:
enum Direction { Up, Down, Left, Right }

This declares integer tags Up, Down, Left, and Right that you can use to make your code clearer. With the preceding declaration, you can declare and use values from the enumeration:
Direction steerDirection = Direction.Up;

The other user-defined value type is a struct. A struct is a small, low-overhead type used to group small sets of related information. A struct is a transition object, falling between the value types and reference types, which can be used to optimize objects that dont require reference type support. The following demonstrates defining a struct type:
public struct Vertex { public int x, y, z; public Vertex( int newX, int newY, int newZ ) { x = newX; y = newY; z = newZ; } }

In this example, the Vertex struct is declared with three members and a constructor. Instances of this struct will be handled as a value type, in that it will be stored on the stack. Unlike other value types, however, you can declare new instances without using the new operator. Simply declaring the variable is sufficient. In common with complex reference types, you can supply a constructor, as this example does, which you can invoke when instantiating the struct variable.

TIP
A constructor is a special method of a type that is called automatically when an instance of the class is created. Ill talk more about them later in this chapter.

The following shows both methods of instantiating a Vertex struct:


Vertex Vertex v1( 1, 1, 1 ); // initializes using constructor v2; // initializes using default

Ty p e s a n d D e c l a r a t i o n s

11

The first declaration, v1, invokes the constructor supplied in the declaration and initializes the fields of the struct with the supplied values. The second declaration relies on the compiler, which generates a default constructor (that is, a constructor with no parameters) that zeroes out all fields in the structure. In both declarations, the variables are considered initialized immediately after the declaration. The generated default constructor, by the way, is not something you can override; if you declare a constructor with no parameters on a struct, the compiler generates an error. The syntax used to access members of a struct is simple and always the sameusing the variable name, a period, then the name of the member. To access the value of the members of a variable myVar that was an instance of the Vertex struct, youd use the notation myVar.x, myVar.y, and myVar.z. As I mentioned previously, value types can be used as objects through the boxing mechanism. Logically, value types are actually objects, too, derived from the System.Object class. However, unless you ask for it, the additional structure necessary to access their objectness isnt created; only their value is stored. Each simple value type has a corresponding class type, though. When you use a class method, such as calling ToString() on an int to get a string representation, C# boxes the variable by creating an instance of its corresponding class type, initializing the object instance with the value from the basic type, and then invoking the appropriate method on the object variable. By this just-in-time instantiation of object wrappers, the program achieves a substantial reduction in overhead compared to a pure, object-based approach; the object is created only if you use it.

Class Types
The term reference type usually refers to a class in C#, a definition of a type of item that can be instantiated as an object by your program. In much of the documentation, in fact, you can replace the term type with class without changing the meaning at all. This situation arises because nearly everything in .NET, and in C# as a result, is a class of some kind. Therefore, Ill begin by exploring a class declaration. A class declaration can define the following: Attributes Member-Hiding Modifier (new nested classes only) Visibility Modifiers (public, protected, private, internal) Inheritance Modifiers (sealed or abstract) Type name Base classes and interfaces Member variables (called fields in the C# lexicon) Constants Properties Events Operators Indexers Constructors and destructors Member functions (called methods in C#)

12 Chapter 1: Language Elements

Instead of presenting the formal definition of a declaration (you can see that in Appendix A, The C# Language), this section looks at a sample type declaration. Ill be referring to Listing 1.2 in the following discussion. Listing 1.2
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42:

Class Declaration Example

[Obsolete(Use something else)] sealed class MyClass : Object, IDisposable { private int myField = 5; // private initialized variable private int[] myArray; // private array variable public const int myConst = 30; protected int Multiply( int param ) { return myField * param; } public void Dispose() { myArray = null; GC.SuppressFinalize(this); } public MyClass() { myArray = new int[25]; } ~MyClass() { if ( myArray != null ) myArray = null; } public int Field { set { myField = value; } get { return myField; } } // public constant // protected method

// public method

// constructor

// destructor

// public property

public int this[int ind] // public indexer { get { return myArray[ind]; } set { myArray[ind] = value; } } }

Ty p e s a n d D e c l a r a t i o n s

13

Class Declaration
A class declaration consists of, in order: attributes, modifiers, the keyword class, the type name, the base class list, and the class body. The class keyword, type name, and body are required, but the other elements are optional and applied as appropriate to the type youre declaring. Listing 1.2 uses all these elements, as the following paragraphs describe. Line 1 demonstrates using a class attribute. An attribute is a customizable modifier on a declaration, and it usually targets the item immediately following it. Attributes are contained in square brackets as shown (the Obsolete attribute I used is for marking items that should not be used anymore). Attributes are discussed more fully in Chapter 9, Using Metadata and Reflection. Line 2 begins the class declaration itself. A class declaration contains the keyword class, followed by the name of the class. This declaration modifies the class with the sealed keyword, which indicates that the class cannot be inherited from. You can also use the abstract modifier to indicate that the class must be subclassed for use. Obviously, you cant use both on the same class. Inheritance and interface implementation are indicated by following the class name with a colon, then a comma-separated list of zero or one base class, and zero or more interface names. In this case, MyClass inherits from Object and implements the IDisposable interface (all types implicitly derive from Object, but its sufficient to demonstrate the syntax). Unlike Java, no explicit implements keyword is supplied. When a class inherits from another class, it gains all the members of the parent class. Implementing an interface, however, constitutes a promise to implement the members defined by the interface with identical semantics. This is similar to the contract-based model in COM, but at its core, COM allows only member functions in an interface; C# interfaces can contain any combination of methods, properties, indexers, and events. C# allows interfaces to multiply inherit, as well; although multiple inheritance in implementation classes is arguably fragile, libraries such as the C++ Standard Template Library have demonstrated their value in interface specification. The remainder of the class declaration contains examples of most of the members for which you can provide a class. Each member declaration begins with an access modifier to specify what code can use that member. Table 1.3 lists the allowed access modifiers. Table 1.3 Modifier
public protected private internal protected internal

C# Access Modifiers Accessibility


Anywhere visible Only by the class or subclasses of it Only by this class Only in this project Only in this class or subclasses

Applies to members of:


class class class class class

or

struct

or or or

struct struct struct

14 Chapter 1: Language Elements

If no access modifier is specified on a member, the member is private by default.

Value Fields
Line 4 of Listing 1.2 shows how to declare a simple value field. Ive included an initial value for the field to demonstrate the syntax, but initializers are optional for plain values. You must, however, initialize the variable before its first use. Without a storage modifier, the field is an instance field; that is, a copy of the field is created for each instance of the class. You can also use the static modifier to create a class field, of which one copy is to be shared among all instances of the class, or readonly, which prohibits changing the value after it is initialized. Line 5 defines an uninitialized array. Like simple data, initialization in the declaration is allowed, but optional. The array must be initialized before use, as usual, so in this case I initialize it in the class constructor at line 22. Line 7 demonstrates declaring a constant field using the const modifier. This declares a value, usually public, to record an unchanging value like pi or Plancks constant. You must supply an initializer for const fields because the language prohibits modifying them after they are created.

Methods
Lines 9 to 18 of Listing 1.2 declare two methods. Multiply() is protected and therefore is usable only within the current class or its subclasses. Dispose() is public, so its usable by any code to which it is visible. The Dispose() method, in fact, is the implementation required by the IDisposable interface; this method satisfies the contract made in the class declaration. A general method declaration takes the same form as most C/C++ derived languages; the basic declaration consists of the return type, method name, parameter list, and then the block of code that is the body of the method. The code is executed when a caller invokes that method. Modifiers applicable to a method include static, virtual, abstract, and override. The static modifier declares the method with class scope; this means that it is not associated with any particular instance and can be called through a reference to the class type. It cannot access any instance members. A virtual method implements the plumbing necessary for a derived class to override the method, but it doesnt require an override. The abstract modifier, used without a method body, indicates that the method must be overridden by subclasses. I discuss the behavior of these modifiers in more detail in the section Inheritance later in this chapter. Methods take zero or more parameterseach of which can be identified as a value, a reference, or an output parameter by using an appropriate modifier. A parameter with no modifier is a value parameter, which will contain a copy of the value the caller provided when invoking the method. Although a method might modify a value parameter, the changes affect only the copy, and the callers value remains unchanged.

Ty p e s a n d D e c l a r a t i o n s

15

A reference parameter is identified by the modifier invocation, as in the following:


...

ref,

both in the declaration and

// declare a method with a reference parameter public void DoSomething( ref int a ) {} ... // invoke the method with myVar mcVar.DoSomething( ref int myVar ); ...

A reference parameter is not copied when invoking the method, and the callers copy is affected by modifications made within the body of the function. Because not knowing this can lead to unexpected side effects, the language requires you to include the ref modifier when you call the function, not just when you declare it. In this way, you positively indicate that you know what the implications are. You must initialize the variable you are passing into a reference parameter before invoking the method. Output parameters are almost the opposite of a reference parameter. A reference parameter might be modified by a method, but many other reasons exist to use reference parameters. An output parameter, on the other hand, must be initialized by the method taking it. Output parameters provide a method for you to pass out a number of values from a method instead of just its return value. The only drawback is that you cannot leave the method without initializing every out parameter; in the code that invoked the method, the variables passed as out parameters are considered positively assigned after the method returns. You identify a method as an output parameter by using the out modifier. Methods can be overloaded, providing different access methods to user code, by declaring multiple methods with the same name. Each such method must have a different parameter list (the name of the method and the number and types of parameters make up the methods signature). The modifiers and type of the method are not considered in resolving which method to useonly the signature. The runtime will decide which method to use by matching up the parameters supplied by the caller and the signatures of the methods that could be used. It will invoke the method whose signature most closely matches that supplied by the caller. C# will not allow identically named methods with identical parameter lists and thus identical signatures.

Constructors and Destructors


Two special kinds of methods you can define are constructors and destructors. These function as usual: A constructor is called when an instance is created, before any other members can be accessed, and a destructor is called when the instance is destroyed. A class can supply both class and instance constructors. A class constructor, marked by the static modifier, is executed before any static fields are used and before any instance of the class is created. The class constructor is useful for initializing any nonconst static members. The instance constructor is used to initialize instance fields.

16 Chapter 1: Language Elements

A class can overload instance constructors with different parameter lists. The same resolution and uniqueness rules apply as for any other method overload. Programmers often choose to implement multiple constructors to allow users of a class to supply just the amount of information they want in creating the instance, and the class supplies reasonable defaults for other values. Listing 1.3 shows one such use of overloaded constructors. Listing 1.3 Using Progressively More Specific Constructors

class Simple { private int myA; private string myS; public const int defaultA = 0; public const string defaultS = S; public SimpleStart() : this(defaultA, defaultS) {} public SimpleStart( int a ) : this(a, defaultS) {} public SimpleStart( int a, string s ) { myA = a; myS = s; } } // end class Simple

In Listing 1.3, you see that the three constructors allow the user to specify none, one, or both parameters. If it were appropriate, you could also implement a constructor that took only the string parameter, too. With this approach, you can give the user of your class the flexibility they want, but you ensure that your class is always instantiated in a defined state, and you keep the actual initialization code in just one place. Note, however, that unlike C++, you cannot just call into another constructor; you can only invoke it through the initialization list on the constructors header.

Custom Operators
You can declare custom operators in your classes; these operators are another type of special method that enables you to tailor the operators to appropriate operations in your code. This is often useful when writing mathematical code (the classic example is implementing vector and matrix classes). Like methods, this redeclaration of already defined operators is called overloading. Most operators are identified as either unary or binary, depending on whether they operate on one object or two, respectively. For example, in the code x = -y; the = operator is binary, and the - (negation) operator is unary (it acts only on the variable y). The operators you can overload in C# are listed in Table 1.4.

Ty p e s a n d D e c l a r a t i o n s

17

Table 1.4 Operator


+, ++, --

C#s Overloadable Operators Type Normal Semantic


unary unary arithmetic positive, negative increment, decrement (note that in C# you cannot overload separate prefix and postfix increment and decrement operators.) logical inversion binary inversion application dependent; requires inverse addition, subtraction multiply, divide modulo divide bitwise shift left, right less than, greater than, less or equal, greater or equal; requires inverse test for equality, inequality; requires inverse binary AND binary XOR binary OR

! ~ true, false +, *, / % <<, >> <, >, <=, >= ==, != & ^ |

unary unary unary binary binary binary binary binary binary binary binary binary

||,

You cannot overload the assignment operators or logical binary operators such as && or but you can overload the operators on which they are based. For example, the add and assign operator += depends on the addition operator +, so you can influence the operation of += by implementing operator +. In addition to the traditional operators, you can define conversion operators to enable casting from one type to another. These can be marked either implicit or explicit, depending on what is appropriate.

Overloading operators comes with a few restrictions. First, operator declarations are static and take either one or two parameters if the operator is unary or binary, respectively. A conversion operator takes one parameter, of the classs type, and returns a value of the type for which the conversion is declared. You cannot define operators for predefined types, although you can define operators in which one of the elements is a predefined type. For the comparison operators, you must define them in pairs; for example, if you define operator >, you must define operator < (the operators requiring inverses are marked requires inverse in Table 1.4). Finally, if you define the equality operators (== and !=), you should also override Object.Equals() and Object.GetHashCode() to ensure your class consistent participation in the type system. Listing 1.4 illustrates the three types of operators:

18 Chapter 1: Language Elements

Listing 1.4
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47:

Using Progressively More Specific Constructors

public class CppInt32 { public int val; public CppInt32( int i ) { val = i; } public CppInt32() { val = 0; } public static bool operator true( CppInt32 i ) { if ( i.val != 0 ) return true; else return false; } public static bool operator false( CppInt32 i ) { if ( i.val == 0 ) return true; else return false; } public static CppInt32 operator -( CppInt32 ci ) { return CppInt32( -ci.val ); } public static CppInt32 operator +( CppInt32 ci, int i ) { return ci + i; } public static CppInt32 operator *( CppInt32 ci, int i ) { return ci * i; } public static bool operator ==( CppInt32 ciA, CppInt32 ciB ) { if ( ciA.val == ciB.val ) return true; else return false; } public static bool operator !=( CppInt32 ciA, CppInt32 ciB ) { if ( ciA.val != ciB.val )

Ty p e s a n d D e c l a r a t i o n s

19

Listing 1.4
48: 49: 50: 51: 52: 53: 54: 55: 56:

continued
return true; else return false; } public static implicit operator CppInt32( int i ) { return new CppInt32(i); }

Listing 1.4 implements a class with some of the same behaviors as an integer in C++, including the capability to be used alone in a conditional expression. This capability is gained by the operators defined in lines 12 to 25, operator true() and operator false(). Definitions of these operators return a bool value; what the concepts true and false mean to a class vary, of course. In this case, nonzero is true, and zero is false. Lines 26 to 29 implement a unary operator to negate the CppInt. This operator takes only one parameter, a CppInt32, and returns another containing the arithmetic inverse of the one that was passed in. Lines 30 to 37 define two basic arithmetic operators. No particular requirements exist for these, beyond taking the two parameters of a binary operator and returning an appropriate type. These return CppInt32 and facilitate operations such as ci = ci * 2;. The equivalence operator in lines 38 to 44 contains no surprises either, comparing the values of its two parameters and returning true or false as appropriate. Implementing operator == requires implementing operator !=, so it follows the definition of operator ==. Finally, lines 52 to 55 implement a conversion operator to make a CppInt32 from an int. I use the implicit modifier to allow a simple assignment without explicit casting. Conversion operators like this, between types that associate closely, can simplify your life dramatically.

Complex Fields: Properties and Indexers


Lines 30 to 34 of Listing 1.2 show declaration of a property. One nice feature of C# is that it includes intrinsic support for the get/set pattern for properties, where the field itself is private, and code using the class has to manipulate it through accessor functions (usually called getters and setters in the Microsoft documentation). Visual Basic has similar support, but because C# is C++ derived, its a lot less verbose in its syntax. Line 30 is similar to a normal variable definition, but following it with a statement body containing the set and get blocks marks it as a property, instead. When user code assigns a value to the property, the compiler provides an automagically generated variable, value, that holds the assigned value for the code in the set block to use. The get block, on the other hand, is run when user code retrieves the value. Whatever get returns is the value returned to the user code.

20 Chapter 1: Language Elements

Lines 36 to 40 declare an indexer on the class. An indexer you provide allows user code to access the class using array syntax:
MyClass mcVar; ... myIntVar = mcVar[5];

An indexer should be declared with at least the access, type, keyword this, and the type and name of the index parameter(s). The public int this[int ind] declaration is appropriate in this case because I am creating an indexer that originates in this class; if I were implementing an indexer for an interface that this class is implementing, I would preface this with the name of the interface requiring the indexer, as in the following:
class MyClass: IIndexedInterface { object IIndexedInterface.this[int index] { ... } }

You can have multiple index parameters and multiple types, so you can impersonate, for example, multidimensional arrays, dictionary tables, and data maps. The capability can be overused, however. If all you are going to do is duplicate the functionality of one of the system-provided collections, its better to use the built-in capability rather than duplicate it on your own. Indexers can be overloaded by declaring multiple indexers. The same rules apply for resolving the correct indexer to invoke, except that the name of the indexer is not considered (they always have the same name: this). You cannot have two indexers with the same signature.

Events
An event is a class member, declared with the event modifier, that lets clients of the class receive notification when something interesting happens to an instance of the class. When used with a delegate, events replace C++ function pointers and Visual Basics OnXXX methods as a way for objects to register their interest in an event. Events are not a new concept. The most common example is in graphical user interface (GUI) programming; the form containing a button is interested in the event of a user clicking the button. However, GUIs are not the only application of this model. Events are happening all the timethe clock ticks, a packet arrives on the network, and so forthand C# is the first language to provide built-in, elegant, and direct support for handling them. Because events and delegates are tightly coupled, Ill defer giving an example to the discussion of delegates in the section Managing Flow of Control later in this chapter.

I n t e r f a c e Ty p e s

21

Interface Types
C# is an object-oriented language that has the expected support for inheritance and polymorphism. An aspect of programming not addressed by traditional OO descriptions, however, that has become more and more important in modern systems is that of the interface. Establishing and using a standard template for a class is tightly woven into most current programming models, so C#s rich support for interfaces is no surprise. It is this support, in fact, that makes the single inheritance restriction work. You declare an interface much like you do a class. Like classes, an interface can contain methods, properties, indexers, and events. However, an interface has the following important differences from a class: An interface cannot inherit from an object; it can inherit only from other interfaces. An interface cannot contain fields or indexers. An interface cannot declare the body code for members that you would write code for in a class definition, such as properties and methods. An interface cannot declare operators. An interface cannot restrict access to membersbecause its intended to stereotype external attributes, it wouldnt make sense. An interface cannot have constructors or destructors. You use an interface to define how other types are going to look. Types that derive from the interface must conform to the interface to compile correctly; the class must also implement any interfaces the interface it is deriving from inherits. Code that manipulates objects of that type can then manipulate them as an instance of the interface. Polymorphism is thus possible without requiring multiple inheritance. Its important to remember that you are not declaring a class type, only describing other types. The runtime library uses a number of interfaces to facilitate your codes interaction with it. For example, an important feature of the runtime is its garbage collector. However, because the garbage collector is free to clean up at its own convenience and it isnt assured that objects will be freed at any particular point in time, the runtime supplies the IDisposable interface. If you derive a class from IDisposable, you must supply a method with the following form:
public void Dispose() { // free resources here }

By implementing IDisposable, you give users of your class the capability to tell your class when they no longer need it; thus, you can release resources that you would otherwise free in the class destructor instead of waiting for the garbage collector. Declaring and using interfaces is simple. You start off by declaring the interface itself. For a simple example, suppose that I wanted to hold a set of items in a linked list, and

22 Chapter 1: Language Elements

I wanted each node in the list to be able to write itself to a file so I could read the list back later. Not wanting to restrict myself to just one type of object (lets look to the future here!), I can define an interface containing the members necessary to interact with a linked list node, as in Listing 1.5. Listing 1.5
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:

Declare an Interface to Prototype Classes

public interface IListNode { // this property stores the reference to the next node. IListNode next { get; set; } // Call this method to write the object to outStream. void Save( ref FileStream outStream ); // call this method to read the object back from inStream. void Read( ref FileStream inStream ); }

In Listing 1.5, the interface IListNode defines a next member for a singly linked list and two methods, Save() and Read(), that take a FileStream object as a parameter, for writing and reading from a disk file. Now I can create classes that derive from this interface and use instances of them in my program. Notice that the get and set for the node have no body, nor do the methods. Listing 1.6 demonstrates implementing the IlistNode interface. Listing 1.6
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:

Derive and Implement Interfaces to Support Polymorphism

class IntNode : IListNode { protected int nodeValue; protected IListNode nextNode; // list node interface members public IListNode next { get { return nextNode; } set { nextNode = value; } } public void Save( ref FileStream outStream ) { BinaryWriter bWriter = new BinaryWriter( outStream ); bWriter.Write( nodeValue );

Managing Flow of Control

23

Listing 1.6
16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 37: 38: 39: 40: 41: 43: }

continued

public void Read( ref FileStream inStream ) { BinaryReader bReader = new BinaryReader( inStream ); nodeValue = bReader.ReadInt32(); } // intNode value property public int val { get { return nodeValue; } set { nodeValue = value; } } } . . . IntNode iNode = new IntNode(); FileStream fs = new FileStream( aFileName, FileMode.Create ); iNode.val = 5; iNode.Save( ref fs ); fs.Seek( 0, SeekOrigin.Begin ); iNode = new IntNode(); iNode.Read( ref fs );

In lines 1 to 30, a class derived from the IlistNode interface is declared. An IntNode implements the next property with lines 4 to 11, and then the Save() and Read() methods in lines 12 to 22. Specific to the IntNode class is its data. For the example, I gave it a single int property. With the interface implemented, I can now use the class in my program, as demonstrated in lines 34 to 43.

Managing Flow of Control


Flow of control refers to the language constructs that we use to control what code is executed. These include both the branching and looping constructs used in normal execution, delegates for callbacks and broadcast calls, and the exception-handling statements for dealing with unexpected or invalid program state. In normal execution and exception, C# is again very C++-ish, but delegates are a new and useful addition.

Normal Execution
Statements used in normal flow in C# include branching, looping, and selection instructions. The statements available in C# are listed in Table 1.5.

24 Chapter 1: Language Elements

Table 1.5 Keyword


break continue do for foreach goto if return switch while

C# Control Statements Purpose


Jump out of containing block Skip to next iteration or enumeration in a looping statement Loop with postcondition Loop with iteration Loop with enumeration from a collection Jump to label or switch case Boolean selection Exit method (optionally returning a value) Case selection Loop with precondition

The two selection statements, if and switch, provide the capability to make simple decisions or to select from a list of possible actions, respectively. if consists of the keyword if, followed by a conditional expression in parentheses, and then a statement or block of statements to execute if the condition is true. You can optionally include an else block after the affirmative block, containing code that is executed if the condition is false. Unlike C++, the selection expression must evaluate as a Boolean result; it cannot evaluate to an integer or any other type. The following example shows the if statement:
if ( myInt > 0 ) CallSomeFunc(); else { myInt = 1; CallSomeOtherFunc(); }

The example demonstrates using either a simple statement for the body of the statement (CallSomeFunc() in the affirmative case) or a statement block. A switch statement consists of the switch keyword, followed by a selection expression in parentheses, and then a set of cases enclosed in braces, as in the following:
switch ( myIntVal ) { case 1: Console.WriteLine(The number is one.); break; case 2: Console.WriteLine(the number is two.); break; default: Console.WriteLine(The number is not one or two.); break; }

Managing Flow of Control

25

As illustrated by the example, the cases within the switch are declared by using the case keyword, a possible value from the selection expression, a colon, and then code to execute if that value matches the selection expression. You can include a default case, as I did in the example, which is selected if no other case matches the selection expression. In the example, the effect of the switch is to execute the code in the first case if myIntVal is one, the second if it is two, and the default code if neither is true. C# looks to find the first case that is correct, and then begins executing the instructions after the case label. You can include multiple statements within each case, and you must terminate the case with a jump statement (I used break in the example) that takes execution out of the switch. Other jump statements include continue, goto, and return. C# defines four iteration statements: for, foreach, do, and while. for is equivalent to C++s for statement, with the form
for (init_exp; term_exp; control_exp ) statement

where
init_exp is an expression, usually an assignment, that sets up the loop control variable(s) (LCV). control_exp is an expression to modify the LCV as appropriate for the iteration. term_exp is a Boolean expression that controls when the loop is terminated.

The init_exp is executed first to do any setup needed by the loop. Then, before the code in the body of the statement is executed, C# checks the term_exp to see if it is true; if it is, the body of the for loop is executed. After each time the body of the statement executes, the code in the control_exp is evaluated and the term_exp is checked again. The loop is executed as long as evaluation of the term_exp results in a Boolean true. Consider the following example:
int a; int[] b = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; for ( a = 0; a < 10; a++ ) { b[a] /= 2; }

This loop begins by setting the LCV (a) to 0 in the initializing expression. The terminating expression (a < 10) is checked, and because it is true, the body of the statement is executed. After the body runs, the control expression (a++) executes, incrementing the value of a; this cycle repeats until a reaches 10, at which point execution passes to the next statement after the closing brace of the for statements body. The loop control variable neednt be declared outside the for loop, by the way. If all that is needed is a temporary counter variable, you can declare it within the initialization of the loop, as in the following:
for ( int c = 0; c < 10; c++ ) b[c] /= 2;

26 Chapter 1: Language Elements

This example is functionally equivalent to the previous one, except that the variable c is declared within the initialization. When the loop finishes and execution passes on, c goes out of scope and out of existence. The for statement is powerful, but for certain cases, it can take more setup than is really necessary, and carelessness can produce errors. In the preceding example, for instance, the limiting expression a < 10 is a common source of fencepost errors; a Visual Basic programmer, used to 1-based arrays, might write a <= 10, causing the code to run off the end of the array. To help with this very common task of iterating through an array or collection, C# has the foreach statement. The foreach statement has the following form:
foreach ( type item in collection ) statement foreach will execute statement once for every element in the collection. Item is a temporary reference to the current element in the collection, of type type, that statement can manipulate. With the same variable declarations as in the for example, you could sum the members of the array with the following code: a = 0; foreach (int x in b) a += x;

In this example, C# will execute the statement a += x once for each element in the array. In the first iteration, x refers to b[0]; in the second, to b[1], and so forth. This is much easier than the standard for expression. The last looping statements, do and while, are nearly identical, differing in operation only when their loop control is evaluated. They have the following form:
do statement while ( expression ) while ( expression ) statement

Both will execute statement as long as expression is true; however, as implied by the order of the statements, do evaluates the expression after the body statement, whereas while evaluates it before each iteration. The while statement executes with a precondition, whereas the do operates with a postcondition. This distinction is useful in matching the code you write to the problem you need to solve, but remember that although the while statement will not execute its body statement if the expression is false to start with, do will always execute its body at least once. The example that follows shows each used in code.
do { a = a - 1; } while ( a > 1 ); while ( a < 10 ) a++;

Managing Flow of Control

27

With each of the looping statements, you can use break and continue to interrupt the normal, statement-by-statement execution. If you want to get completely out of the containing statement (such as a for, a switch, or other complex statement), you would use a break statement. Youve already seen break in the example for switch, so I wont demonstrate it again. With continue, you can jump out of the current iteration and go directly to the next iteration without exiting the enclosing statement, as in the following:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: public void ChangeArray( Object [] a ) { foreach ( Object o in a ) { // if the object at this element is null, then // skip it and go on to the next one. if ( o == null ) continue; // processing array element goes here } }

In this example, ChangeArray() takes as a parameter an array of objects with unspecified length. I used a foreach statement to iterate on the array because C# knows how big it is, and I really dont need to (if you do, you can query the arrays Length property). For this application, I dont want to go through all the processing if the current element is empty, so on line 7, I check whether the current element is null. If it is, the continue on line 8 jumps directly back up to the foreach (line 3), o is moved to the next element in the array, and the statement executes again. Whether you like it or not, C# includes the goto statement. You use goto to jump to a labeled location in your code, as in the following:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: public int FindInt( int val, int [] ia ) { int i; // look for val in ia for ( i = 0; i < ia.Length; i++ ) if ( ia[i] == val ) goto done; // if we got here, it wasnt found return -1; done: return i; }

28 Chapter 1: Language Elements

In this example, the FindInt()function searches an array for a value and returns the index of the value in the array, or -1 if it is not found. This is a good candidate for a goto because all the other options for controlling the search (sentinel values, a separate found variable, and so on) are more complex and less clear. The main searching loop in lines 6 to 8 checks each value in the array; when it finds it, the goto in line 8 jumps to the return in line 14 (the first statement following the done: label). If the loop executes all the way to the end, it falls out and line 11s return value is -1 to the caller to indicate it wasnt successful. This code also illustrates the last normal-mode flow of control statement, return. It can be used, as shown in the example, to return to the caller and specify the return value of the function, or it can be used without an argument if a return value is not appropriate (such as a function declared with a void return type).

Delegation
Delegation in C# is a language feature that explicitly supports the callback model required in many modern situations. The GUI question that I mentioned previously is only one of them. In one project I worked on, a complex processing application needed a central point where it could register different modules to be called when new data became available, when the system state changed, and so forth. With the language I used then, C++, I got to write my own registration code. If C# had been available, the delegation feature would have saved me the extra effort. Specifically with events, the premise is that an object will be influenced by things that occur over time. This object will usually be instantiated by code that wants to respond to some subset of the things that happen to the object. In our button example, most client code wont want to respond to a repaint request, but it will want to be involved in a clicking event. A little thought determines that we need three things: a way to specify what events will originate with a class, a way to say how code will respond, and a way to hook up the event with the response code at runtime. C# provides the delegate to solve the problem. To hook up events to responses, you first declare a delegate, which serves as a declaration of what the methods that handle events should look like and what event declarations can call into those events. Its an innovative way of structuring the connection, but its easier to demonstrate than describe. Consider the following:
public delegate void BoomEvent ( int dB );

The preceding declaration says, Im going to declare events of type BoomEvent. Any handler function you want to have called in response to events of that type should take one parameteran int that tells how loud the boom. In the code that generates the event, an event must be declared with the name of its type (the same as the name of the delegate):
public class EventGenerator { public event BoomEvent GoBoom; }

Managing Flow of Control

29

To respond to the event that this delegate will handle, you must declare a method in your class with the same parameter list and return type as the delegates declaration. After you have written the handler, you can then add it to the event:
1: public class UserClass 2: { 3: private EventGenerator anEventGenerator; 4: public void MyHandler( int dB ) 5: { 6: // handler code goes here 7: }; 8: UserClass() 9: { 10: anEventGenerator = new EventGenerator(); 11: anEventGenerator.GoBoom += new BoomEvent(MyHandler); 12: } 13: }

In the preceding example, line 3 declares an instance of the class that originates the event. Lines 4 to 7 declare a method with the same parameters and return type as that of the delegate. Finally, line 10 initializes anEventGenerator with a new instance of the class, and then line 11 creates a new delegate instance, initialized with the handler function, and registers it with the event using the += operator to add it to the events notification list. To actually make the event happen, the code in the object that is the source of the event calls the event it declared as if it were a function:
public class EventGenerator { public event BoomEvent GoBoom; protected void BurnFuse() { GoBoom( 120 ); } }

In this example, the GoBoom( 120 ) call passes into the instance of the delegate GoBoom, which then calls all the handlers that have been added to it (as shown previously in UserClass). You can add multiple handlers for the same event, however your requirements dictate.

Exceptions
Exceptions, the third class of control statements, are constructs used to handle exceptional or unexpected situations. For example, an exception is generated if your code tries access through a null reference or if an arithmetic operation overflows the data type of a variable in a computation. Although they are commonly misused by programmers to return normal result values, this is unwise; exceptions should be used only

30 Chapter 1: Language Elements

to handle situations that should not happen during normal execution. An exception can end your program, so if you want to return a value or an out parameter, dont use an exception. When an exception is generated, or thrown, the code in the current context stops execution, and if not contained in an exception handling block, control returns to the next context up in the call stack. If that context does not handle the exception, it continues to propagate up the call stack until either it is handled or it reaches the top of the stack. If it reaches the top without being handled, the program is terminated by the runtime. Exceptions are full-fledged types in their own right; there are numerous types of exceptionsall ultimately deriving from the object System.Exception in the .NET runtime library. As you write your code, you have the capability to create new exception classes that are tailored to your own application. Used wisely, this capability enables you to integrate your application-specific error handling into the programming model in a consistent, sensible manner. try, catch, and finally The first step in working with exceptions is handling those that are defined by the system. This is an important consideration in your code. Because unhandled exceptions will terminate your program immediately, you need to consider what exceptions will be raised as you make calls into the CLR. The keywords you will use in handling exceptions are try, catch, and finally. Consider the somewhat naive code in Listing 1.7. Listing 1.7
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:

A Risky Way of Network Programming

// checks if a web server is up by trying to connect to it. bool retVal = false; Socket so = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp ); // resolve the address and port (80 is default for HTTP) IPAddress svrAddr = Dns.Resolve( server ).AddressList[0]; IPEndPoint ep = new IPEndPoint( svrAddr, 80 ); // connect so.Connect( ep ); if ( so.Connected ) retVal = true; so.Close(); return retVal;

Managing Flow of Control

31

This short piece of C# could be the body of a routine to see whether a Web server is available for use. However, in network programming, the only assumption you can usually make with certainty is that something will go wrong. This is no exception; for example, the Domain Name System (DNS) lookup at line 8 could fail to resolve the hostname, and the connection at line 12 could fail to complete. In C#, problems like this cause exceptions to be raised by the runtime library. Listing 1.8 shows a more robust version of the example: Listing 1.8
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27:

A Little Better: This Catches Some Problems

// checks if a web server is up by trying to connect to it. bool retVal = false; Socket so = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp ); try { // resolve the address and port IPAddress svrAddr = Dns.Resolve( server ).AddressList[0]; IPEndPoint ep = new IPEndPoint( svrAddr, 80 ); // connect so.Connect( ep ); if ( so.Connected ) retVal = true; } catch ( SocketException ) { // You could implement more complex error handling; this is // all that we need here. retVal = false; } so.Close(); return retVal;

C++ and Java programmers will be instantly at home with this code because the syntax is identical to both of those languages. Potentially risky code is marked with the try keyword, followed by a statement block enclosing the code of concern in braces. You then follow the closing brace with one or more catch statements. You can catch a specific exception, as I did in line 18, by following the catch keyword with the type name of the exception. In the example, if a SocketException occurs, the code enclosed in the catch block is executed; if any other exception occurs, it is not handled, and the program will abort if no other appropriate exception handlers are in the call chain.

32 Chapter 1: Language Elements

You can use catch in two other ways. If you supply a variable identifier after the type name, you can use that identifier to manipulate the exception or gain additional information about the error, as in the following:
catch ( SocketException se ) { System.Console.WriteLine( se.Message ); retVal = false; }

You can also omit the exception specification entirely if you simply want to capture any errors regardless of their nature, as in the following example:
catch { retVal = false; }

Because exceptions are classes, you can catch a whole family of them by catching a superclass that they share. Similarly, if you want to catch all exceptions, as you would by omitting the specification, but you still need to manipulate the exception that was thrown, you can catch System.Exception with a variable declaration:
using System; catch ( Exception e ) { System.Console.WriteLine( e.TargetSite.Name + : + retVal = false; }

e.Message );

With this code, trying to connect to a nonexistent server results in the following output on the console:
GetHostByName: No such host is known

You can have more than one catch statement on one try block. When multiple catch statements exist, they are evaluated in order from top to bottom until a catch statement is found with an exception parameter that matches the exception that has occurred. However, because catching an exception class will match to both that exception and any exception that derives from it, you must construct your catch list with the most specific exceptions first, and then more general ones following. Youll often encounter this question when youre catching exceptions you expect, but you want to handle some general exceptions so that even if you didnt foresee a particular error, your code can still handle it. For example, the following code is well intended, but cant compile:
try { // task code here }

Managing Flow of Control catch ( Exception e ) { } catch ( SocketException se ) { }

33

The intent with this code was to handle a socket exception specifically but, if something else went wrong, to still provide intelligent behavior. However, when the compiler starts at the top, the catch for the Exception class will mask the catch for the SocketException because a SocketException would be caught there. With that in mind, the compiler will generate an error for this code, saying, A previous catch
clause already catches (System.Exception). You try { // task code here } catch ( SocketException se ) { } catch ( Exception e ) { } all exceptions of this or a super type

should use the following order instead:

This will compile successfully. When youre writing applications, you constantly create and release objects that use some kind of resource or interact with other objects. When an exception occurs, your normal path code for cleaning up might not be executed. The finally statement enables you to provide code that will be executed whether or not the code in the try completes successfully. Listing 1.9 shows the final version of the network code. Listing 1.9
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:

The Final, Error-Tolerant Version of the Code

// checks if a web server is up by trying to connect to it. bool retVal = false; Socket so = null; try { so = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp ); // resolve the address and port IPAddress svrAddr = Dns.Resolve( server ).AddressList[0]; IPEndPoint ep = new IPEndPoint( svrAddr, 80 );

34 Chapter 1: Language Elements

Listing 1.9
14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35:

continued
// connect so.Connect( ep ); if ( so.Connected ) { retVal = true; }

} catch ( Exception e ) { // something went wrong, but I dont care what System.Console.WriteLine( e.TargetSite.Name + : + e.Message ); retVal = false; } finally { // if the code excepted after connecting, then close the socket if ( so != null && so.Connected ) so.Close(); } return retVal;

In this version of the code, Ive moved everything likely to cause an exception within the try block, still catching whatever exception occurs at line 22, and then doing some cleanup in the finally block in lines 28 to 33. If an exception occurs in the try block, execution first transfers to the code within the catch block. When that code completes, execution transfers to the finally block, checking to see whether the socket was created and connected, and if so, it ends the connection. Within the catch and finally blocks, you need to be careful to avoid errors that will result in throwing additional exceptions.

Throwing Exceptions
The other side of exceptions is throwing them. When you throw an exception, you are exiting your current context and returning to the parent context, where the exception can be handled. In general, when you encounter an exceptional condition in your code, you should first clean up any resources (potentially in a finally block if the exception occurs in your normal path code) and then throw the exception up the stack. You use the throw statement to throw an exception, with the exception to throw in following parentheses:
throw new ArithmeticException( Cannot divide by zero. )

Because most exceptions are not useful to the code throwing them, youll often use this form, creating the exception using new in the throw itself.

Managing Flow of Control

35

As youre writing code with exceptions, you might want to clean up your current context in a catch and then re-throw the exception to delegate up and pass the exception along. For example, in a data layer class, you could catch an exception occurring in working with the database, clean up the resources you allocated to work with it, and then throw the exception again to pass it up to your business layer class. The code in Listing 1.10 does exactly that. Listing 1.10
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28:

Re-throwing an Exception

using System.Data; using System.Data.SqlClient; . . . SqlConnection conn = null; SqlCommand cmd = null; SqlDataReader dr = null; try { // do database work here } catch { // clean up any resources if ( cmd != null ) cmd.Dispose(); if ( conn != null ) { conn.Close(); conn.Dispose(); } // pass the exception up the chain; just re-throw it throw; }

In Listing 1.10, code inside the try block that worked with the database could except for a variety of reasons, so that if an error occurred, it would be difficult to say for sure what had been initialized. In the catch, lines 14 to 28, it checks each variable that might be initialized and use significant resources, disposes of the ones that need to be, and then line 27 re-throws the exception to pass it along to the calling code. So far, Ive only demonstrated working with the exceptions defined in the runtime library. However, it will often be useful to create a custom exception that is meaningful in the context of your program. You do so by creating a class that derives from System.Exception. You can then either add to it or just use it with the default behavior. Often, the simple fact of having the exception with an appropriate name will be enough. Listing 1.11 demonstrates working with a custom exception.

36 Chapter 1: Language Elements

Listing 1.11

Building a New Exception Class

1: using System; 2: 3: // declare custom exception to throw if code calls 4: // this library 5: class BadLibraryException : Exception 6: { 7: public string AdditionalInfo; 8: 9: public BadLibraryException() 10: { 11: AdditionalInfo = Not specified; 12: } 13: 14: public BadLibraryException( string s ) 15: { 16: AdditionalInfo = s; 17: } 18: } 19: 20: [Obsolete(This library is no longer usable.)] 21: public class OldClass 22: { 23: public void SomeFunc() 24: { 25: throw new BadLibraryException(Class OldClass); 26: } 27: }

For this example, I assume that I have a library class that doesnt work anymore, so I cant have code using it. Therefore, Ive declared an exception called BadLibraryException (lines 5 to 18 in Listing 1.11). This is a class that derives from System.Exception, and in this case, I added a field to store custom information if needed, but thats not required. With this class declared, I can use it in other classes in the library, as I demonstrate in lines 20 to 27. This class, which Ive also marked Obsolete to alert programmers who use the library by mistake, replaces any code that might be in SomeFunc() with the code to throw my custom exception.

Inheritance
Youve already seen inheritance in use with interfaces. You should already be familiar with object-oriented concepts, so Ill describe only the mechanics of it in C#. An inheritance relationship is established using the colon after the class name in defining the class, as youve seen. The base class list cannot contain more than one base class, but can contain as many interfaces as you choose. The final set of members of any class is the sum of all members of its base class and interfaces and all their base types, all the way up the inheritance tree.

Inheritance

37

For type compatibility purposes, a reference to a class T can legally be assigned from an instance of T or any type derived from T. A class cannot derive from another class that is less visible; deriving a public class from a private or internal class will cause an error. C# does not provide access control over inheritance like C++ does, so items inherited from a base class have the same access level as in the parents declaration. Further, you cannot change the access of a member of a class by overriding it with a more restrictive access level. Because definitions in the current namespace exist at the same level, it is possible to declare classes with circular inheritance relationships; that is, a class A that derives from class B, which then derives from class A. However, for obvious reasons, this would cause a compile error because the compiler would not know where to stop in following the inheritance chain. Members can be declared that hide or replace members of a base class; you must supply implementations with interfaces, but you have far more control with base classes. As with other things, however, C# intends for you to be much more specific in declaring your intentions. In the simplest case, you can declare a method that through semantic order hides a member of the base class, as in the following:
class SuperClass { public int A() { // do work } } class SubClass : SuperClass { public new int A() { // do different work } }

In this case, the SubClass.A() method will be called if a user routine calls A() on an instance of SubClass. Because declaring a member with the same name as a member of a base class is a common and easy error to make, the compiler will issue a warning if you dont use the new modifier on the derived class member as I have in the example. Hiding a base class member is only a partial measure, however. In the preceding definition, the replacement is only semantic and does not hold up to polymorphic code. In the example, a reference to a SubClass would call the derived classs method only if the code invoking the call used a reference of type SubClass. However, code using a

38 Chapter 1: Language Elements

reference to a SuperClass will invoke the method provided in nothing exists to make it happen otherwise.

SuperClass

because

In determining how to resolve a method invocation when derived methods hide base class methods, you can encounter seemingly odd results. Listing 1.12 demonstrates this phenomenon. Listing 1.12
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42:

Access and Inheritance Is Not Always Simple

class SuperClass { public virtual int A() { return 5; } } class SubClass : SuperClass { private new int A() { return 6; } private int B() { return 4; } public int C() { // calls SubClass.A() return A(); } } class MainClass { public static int Main( string[] args ) { SubClass sc = new SubClass(); // calls SuperClass.A() System.Console.WriteLine( sc.A().ToString() ); // compilation error int b = sc.B(); return 0; } }

Inheritance

39

The code in the example behaves as the comments suggest. In the derived subclass, the method B() is defined as private, so the Main() functions attempt to call it at line 38 results in a compiler error. The call to A() at line 35, however, resolves to the method SuperClass.A() because SubClass.A() is invisible to the client code by virtue of its private modifier. SubClass.C() calls the method in its own type, of course, because that method is accessible. This is different from similar elements of other languages; in C++, for example, both calls would create an error. To achieve full polymorphic functionality, C# includes the keywords virtual and override modifiers. You use them as modifiers on class members containing code that you will override to extend or modify functionality in a base class. Listing 1.13 shows how to use these keywords to modify the sample classes. Listing 1.13 Behavior
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:

Use

virtual

and

override

to Fully Implement Polymorphic

class SuperClass { public virtual int A() { return 5; } } class SubClass : SuperClass { public override int A() { return 6; } }

By declaring A() in the base class using the virtual modifier, I declare the function a candidate for override; client code calling this function through a base class reference will have available to it the necessary code to correctly resolve a method override at runtime. The override modifier in the declaration at line 11 establishes the derived classs definition as an override, completing the picture. When you override an inherited method, you can use the sealed modifier in addition to override to finalize your override and prevent other classes deriving from your class from overriding your override methods. You cannot use the sealed modifier with nonoverride methods; when you dont want your method overridden, omit the virtual modifier. The last inheritance modifier is abstract. Like classes, abstract modifies its target to indicate that you are not providing an implementation; it produces a similar effect on specific items to that which declaring an interface produces on all its members. To declare an abstract property, for example, youd use code similar to the following:

40 Chapter 1: Language Elements abstract class AbstractProperty { public abstract int X { get; } // other class code here }

This code causes any class deriving from property called X.

AbstractClass

to implement a read-only

The inheritance keywords can modify properties, methods, and events. You can legally apply abstract, however, only on members of an abstract class definition.

Unsafe Code
As with other high-level languages, such as Visual Basic and Java, a key feature of C# is isolating code from the underlying machine. Although this contributes to reliability, you have a problem when you discover you need to pierce the veil and work directly with the computer. With Visual Basic or Java, this situation usually means you need to learn another language, create your low-level code in a library, and then invoke the code through some native code interface.

Calling External Functions


As you might expect, however, C# learns from the past and provides a much more elegant interface to so-called unsafe code. If you need to call into native code libraries, you can use the extern modifier on a method declaration and specify the function you want to call and the dynamic link library within which it resides. The following declaration demonstrates:
class ExtAPIs [ [DllImport(kernel32, SetLastError=true)] public static extern uint CreateFile(String lpFileName, int dwDesiredAccess,int dwShareMode, SECURITY_ATTRIBUTES lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, uint hTemplateFile); }

Although external method declarations can be used, their real impact is probably limited. You dont need these methods to access libraries and other managed components or COM objects. Unlike other safe languages, C# is closely tied to the Windows line of operating systems, so most of the platform is available through system classes. Finally, this method still has the code sitting outside your program in another file with another language.

Unsafe Code

41

Writing Unsafe Code


C# goes the rest of the way with the unsafe modifier for types and statements. With you can work directly with memory, pointers, and similar unsafe objects in your managed code. Often described as writing C inside of C#, unsafe code is very close syntactically to C. You dont have header files or macros, but the basic syntax is there.
unsafe,

You can apply the unsafe modifier to a type declaration, or selectively to its members. If all you need is a short block of code, on the other hand, you can enclose unsafe code in an unsafe block. Listing 1.14 shows all three methods. Listing 1.14
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35:

You Can Use the

unsafe

Modifier for Unsafe Code

class UnsafeMembers { unsafe char * pc; public void SafeCode() { // unsafe code needs to be in an unsafe statement here. } public void UnsafeStatement() { // code here is safe code still unsafe { // I can use unsafe code here. } } public unsafe void UnsafeMethod() { // Im unsafe here, too. } } unsafe class CoreDump { public void Crash() { // unsafe code can overrun buffers; the result // is undefined! char * pc = stackalloc char[25]; for ( int i = 0; i <= 25; i++ ) *pc = \0; } }

42 Chapter 1: Language Elements

In Listing 1.14, the first class is a normal managed class, except that it has unsafe members; a pointer to char on line 3, and an unsafe method in lines 19 to 22. This class by default is still a safe class, so in method SafeCode(), you cant manipulate unsafe members. Although safe code can call unsafe methods, it cannot in any way manipulate pointers, even in just passing one to an unsafe method that was set by another unsafe method. Lines 10 to 17 demonstrate the unsafe statement; this consists of the unsafe keyword followed by a statement block in which you can write unsafe code. You have a few restrictions in using unsafe code. To start with, when you compile your code, you have to use the /unsafe option on the csc C# compiler, or it will raise an error as soon as it encounters an unsafe item. Second, although you have a lot of the C syntax, some things just dont work. For example, the following code is probably a pretty familiar format to C and C++ programmers coming to C#:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: public unsafe void memcpy( byte * from, byte * to, int len ) { if ( from == null || to == null || len == 0 ) throw new System.ArgumentException(); do { *to++ = *from++; } while ( (len--) > 0 ); }

The copy loop in lines 6 to 10 is pretty standard fare for C; each time through the do loop, line 8 dereferences the pointers, the byte pointed to by from is copied to the byte to which to points, and then the postincrement operators (++) increment the pointers to the next location. Something is different, though; in C, the fact that Boolean values come from integers, and pointers are implicitly integers, means that I could have written the check at line 3 using the following syntax:
if ( !from || !to || len == 0 )

I could also have written the comparison in line 10 using the following form:
while ( len-- );

Alas, even in unsafe code, you have to have a pointer certainly wont!

bool

value; an

int

wont do, and a

This short introduction to unsafe code is good enough for an overview. Ill demonstrate much more of it in Chapter 8, Unsafe Code.

Preprocessor Directives
Preprocessor directives enable you to control dynamically how and if code is compiled. The term comes from early computer language compilers, which compiled code in two

Preprocessor Directives

43

steps: The first step was to preprocess the code you had written, often modifying it in some way; in the second step, the actual compiler ran, creating the p-code or machine code that was executed. C# has no preprocessor, it only pretends. Preprocessor directives and code are evaluated in one step, but conceptually, you can think of it as two steps to make the process clearer. The directives in C#, listed in Table 1.6, are oriented mainly to support conditional compilation, allowing you to include or exclude sections of code based on symbols you define. Table 1.6 Symbol
#define #undef #if #else #elif

C# Preprocessor Directives Use


Defines a symbol Undefines a previously defined symbol Introduces a conditional compilation Introduces an alternate block if an #if evaluates false Introduces a second question; equivalent to else if Ends an #if block Causes the compiler to emit msg as a warning Causes the compile to abort and emit msg as the error Sets the compilers current line and, optionally, filename for error reporting In Visual Studio, introduces a region you can collapse in the code outlining function; requires a closing #endregion Closes a region

symbol symbol cond_exp

cond_exp msg msg num [ file ] name

#endif #warning #error #line

#region

#endregion

C++ programmers note: The macro-processing features of C++ are not in C#. Still, what is there is better than dispensing with them completely, as in Java. The most commonly used directives are the #define/#undef, and the #if directives. You use these to define or remove symbols during compilation, and by so doing include or exclude sections of your code. Listing 1.15 demonstrates a common usagemanaging debug and production versions of code with a DEBUG symbol. Listing 1.15 Use Preprocessor Directives to Conditionally Include Code

1: #define DEBUG 2: . 3: . 4: . 5: public void ProcessSomething() 6: { 7: int v = DoSomeWork(); 8: #if DEBUG 9: Console.WriteLine( DoSomeWork() returned + v.ToString() ); 10: #endif 11: }

44 Chapter 1: Language Elements

The #define in line 1 of Listing 1.15 defines the symbol DEBUG for the compiler in the code that follows. In lines 8 to 10, an #if directive surrounds the Console.WriteLine() statement and checks to see if DEBUG is defined. If it is, the output statement is included; otherwise, the compiler discards it and moves on. By using the preprocessor, therefore, the debug code is completely removed from the application if the symbol is undefined. You can use this approach to control debug code across your application with very little effort. You can handle situations that are more complex, as well. For example, you could manage version compatibility using multiple conditions:
#if VER1 // version one code #elif VER2 // version two code #elif VER3 // version three code #endif

In addition to conditional compilation, you can use directives to warn or even disallow compilation by using the #warning and #error directives. For example, if you wanted to allow a module to be compiled only in a debug version of your code, you could use the #error directive:
#if NODEBUG #error This code cannot be included in a production build. #endif

This code will cause the compile to abort if the symbol NODEBUG is defined. If you dont need to be so draconian, you could instead give a warning:
#if NODEBUG #warning This code should not be included in a production build. #endif

When the compiler reports an error or warning during compilation, it tags it with the filename and line number where the error occurred. You can manipulate this information by using the #line directive. This capability can be beneficial to tool developers. For example, embedded SQL tools preprocess C source code themselves, creating new source code that the compiler is run against. These tools use Cs similar directives to cause the C compiler, if it encounters an error, to report the file and line number of the original source code, not the code after it has been massaged by the embedded SQL tool.

Preprocessor Directives

45

The #line directive sets the line and filename for the compiler at the place the directive is in your code:
#line 123 myData.cs Console.WriteLine(Pick a card, any card.;

In this code, the compiler will report an error (no closing parenthesis in the method call), but no matter what the actual file or location being compiled, the #line directive will cause it to report that the error is at line 123 in the file myData.cs. The last two directives are currently useful only to Visual Studio users: #region and #endregion. In Visual Studio .NET, the code editor constantly parses your code and gives you the capability to collapse the code displayed in the viewer, similar to the way that tree view controls work in other applications. Although Visual Studio identifies these rollup regions automatically for you, keying on namespace, class, and method boundaries, you can mark whole areas in your code using these directives. Figure 1.1 shows a region expanded to show the code within, and Figure 1.2 shows the same region, collapsed.

Figure 1.1. The region shows code when expanded

46 Chapter 1: Language Elements

Figure 1.2. but you can collapse it if you dont need to see it.

Summary
This chapter has been a whirlwind introduction to C#. Although C# omits some of C++s more powerful elements, it introduces some compelling features of its own. However, you need more information to build applications with C#: how to compile and link programs and libraries, and what goes on under the hood when your program loads and runs. Chapter 2, Working with Applications, fills in the blanks by describing how to use the tools in the .NET Framework SDK to turn C# code into working applications.

Working with Applications

CHAPTER 2
Working with Applications
Chapter 1, Language Elements, described the C# language, but language elements are only part of the question. No modern language lives by itself, independently of the environment in which programs written in that language will execute. This chapter describes the environmentthe way you build and run applications and commonly used tools in the Framework SDK.

Intermediate Language and the Common Language Runtime


The programs you create with C# are different from traditional Windows programs. The code you write, after it is compiled, runs in a managed environment that verifies security, ensures safe execution, and manages resources for your programall with the intent of ensuring system reliability and minimizing the possibility of programs interfering with each other.

Intermediate Language
To enable this managed execution, programs for the .NET framework are not compiled to machine code, but instead to a Microsoft-defined intermediate language (MSIL). MSIL is similar to machine instructions, but it is independent of any particular processor architecture and contains additional features to support object-oriented programs. If youre interested, you can read the specification for MSIL (and the rest of the execution environment) in the directory <sdk_install_dir>\ Tool Developers Guide (open StartDocs.htm for a table of contents).

48 Chapter 2: Working with Applications

Some have looked at the managed environment and concluded that C# is Microsofts Java. However, .NET is language-agnostic. Where the Java environment requires that you use the Java language, the only requirement of programmers in .NET is that the development tool must produce MSIL code. Beyond that, the .NET environment doesnt care what language you use. More than 20 languages are already available for the platform, so you are able to select the language that is appropriate to the task or continue using a language with which youre comfortable. Another result of using MSIL is full cross-language compatibility from the infrastructure layer up. On the .NET platform, you have complete, transparent interoperability between modules, no matter what language the code was written in originally. You can even implement a class in one language that extends (derives from) another class written in another language.

NOTE
You can get a feel for what IL looks like by using the ildasm.exe tool from the SDK; the intermediate language disassembly program can open IL-compiled dynamic link libraries or executables and display the code in a mnemonic form similar to assembly language. I discuss ildasm at the end of the chapter, and additional information is available in the ILDasmAdvancedOptions.doc in the Tool Developers Guide directory.

The Common Language Runtime


The Common Language Runtime (CLR) supplies the underlying platform for your programs. As the implementation of the Common Language Infrastructure (the specification describing the .NET execution environment), the CLR handles loading, compiling, linking, and managing the execution of your program. In addition to what is traditionally included in a runtime, it also provides compilation, debugging, and profiling services. The .NET libraries for user interface, data access, and operating system APIs also sit on top of and interact with the CLR. For data, the CLR implements the Common Type System (CTS), an important part of the .NET architecture. The types defined by the CTS and implemented by the CLR are listed in Table 2.1. This list is very similar to the native C# value types, with some exceptions: the C# value types struct, enum, and decimal do not map directly to CTS types, and they are implemented by the C# compiler. Table 2.1 CTS Base Types Type Keyword Values
int8 unsigned int8 int16 unsigned int16 int32

signed 8-bit int unsigned 8-bit int 16-bit signed int 16-bit unsigned int 32-bit signed int

Executables, Assemblies, and Components 49

Table 2.1 continued Type Keyword


unsigned int32 int64 unsigned int64 float32 float64 natural int natural unsigned int bool char string object typedref

Values
32-bit unsigned int 64-bit signed int 64-bit unsigned int 32-bit float 64-bit float natural sized int natural size unsigned int Boolean value wide char string of characters reference type typed pointer

Most development environments provide facilities with which you create a description of services your code provides, and you attach that description to your code or distribute it in a package. Windows, for example, uses type libraries to describe COM components, and Java uses manifests and reflection. When you program in C#, your programs and libraries will always contain these descriptions, called metadata, and you dont need to make any extra effort to create them. However, you can influence or even add to the metadata by using attributes, discussed later in this chapter. The metadata compiled into your product contain a complete description of the types and code youve created in your program that are visible to other programs or tools. Combining the metadata and the intermediate language code makes compiled .NET components self-describing, platform-neutral distribution units. With this information at hand, the CLR can be much more flexible in laying out code in memory, resolving library references, and ensuring that linkages between components and types are resolved correctly at runtime.

Executables, Assemblies, and Components


The physical unit of code for the .NET platform is still the Portable Executable (PE) format of yore; when you compile your programs and libraries, you are creating EXE and DLL files as always, but under the .NET framework, any executable item will link to the CLR execution environment, which handles compiling and executing your code.

Assemblies
On the other hand, the logical unit of deployment in .NET is the assembly. Assemblies include a manifest, a set of metadata that identifies the files and types that are exposed by the assembly to other applications. The manifest can also contain a strong name a combination of the assembly name, version information, and optional culture information. In an assembly with a strong name, the metadata contain a public key signature the CLR uses to verify that the assembly has not been changed since it was compiled.

50 Chapter 2: Working with Applications

The assembly could also include a digital signature using an Authenticode certificate to verify the origin of the code and enable the CLR to ensure the veracity of the assembly signature. Assemblies can be either private or shared. Private assemblies are used only by the application that installed them, but shared assemblies are installed into the Global Assembly Cache (GAC), a repository maintained by the framework. The GAC uses reference counting and versioning of assemblies to manage libraries (including the frameworks Base Class Library, or BCL), and works with the CLR to keep competing versions of libraries from developing into a DLL Hell situation. The decision whether to install an assembly as a private or public assembly is an important one. By installing an assembly into the GAC, you gain the capability to use one copy of it from multiple applications. However, the application must then be installed onto the computer using an installation program, such as the Windows Installer. If you maintain the assembly as private, however, you gain what Microsoft calls XCOPY deploymentthe capability to install the application by just copying it onto the destination computer. Microsofts assertion on the matter is that disk space is cheaper than installation and support issues. In general, I agree, but you should make the decision based on the requirements of your particular situation.

Fusion
When your program loads, the CLR reads the metadata in the file to learn the types that your code requires to run. The framework then locates each of the libraries your program references, in a process called fusion. This process is far more complex than the path-based search resolution in traditional operating system environments, and it is completely configurable to support varying security and architecture scenarios. Reference resolution includes three main tasks. First, if the reference uses a strong name to refer to the type it needs to access, the runtime will determine what version of the referenced item your program needs from the reference. It then tries to locate the assembly using hints from application, publisher, and machine configuration files. If these checks do not succeed, it probes for the file either in the applications installation directory, in a codebase location specified in the applications configuration or invocation context, or in specially named subdirectories under the applications installation. Your program might include static references, which are those established by using a type in your code, or dynamic references, through the use of the reflection feature of .NET. However, both classes of reference are resolved in the same fashion.

NOTE
Details of configuration in .NET are beyond my purposes here. For more information on assembly location, look in the .NET Framework SDK documentation under Deploying .NET Framework Applications, and How the Runtime Locates Assemblies.

Executables, Assemblies, and Components 51

The load process ensures only that the first level of assemblies is available; references to additional assemblies are resolved on-the-fly as the code executes. This benefits you by making it so that code the user does not need is never touched. An obvious optimization on the local computer, it is even more important to applications running across the World Wide Web, because it minimizes the amount of code shipped across the network.

Components
Components are a valuable concept in programming. The implication is of a discrete piece of software that can be distributed as a unit and reused by multiple applications to achieve a well-defined purpose. In .NET, the term component encompasses nonvisual items such as data connections, and Web services, and controlscomponents that implement a user-interface element, such as check boxes or buttons. Enabling component-based programming is a major objective of the .NET framework and of C#. Strictly speaking, a component is a class (not an assembly) that implements IComponent. This implements relationship requires instigating IDispose, a base interface of IComponent, and a read/write ISite property called Site. In use, a component is contained by a site, and communication between the component and its site is achieved through events raised on the Site property. Components can also include other basic services to enable applications, including support for development tool designers and for remote application service invocation. You are not required to implement the IComponent interface directly. For GUI or remoted objects, the framework provides the following base classes:
System.ComponentModel.MarshalByValueComponent System.ComponentModel.Component System.Windows.Forms.Control and UserControl

The first class, MarshalByValueComponent, is useful for components that are passed between client and server contexts but that do not require a persistent reference between contexts. A good example is a query from a databaseas soon as the information is passed to the client, the server can free the resources allocated to satisfy the request. On the other hand, Component helps to implement long-lived components, when the object should remain allocated, and the client needs a persistent reference across multiple operations. The distinction is drawn because, as in COM+, stateful objects that maintain hard references consume far more resources than those that are passed by value, and they will always be less flexible than stateless objects. is a base class for user-interface objects. You can derive from this class to create custom controls for your application. If you prefer, UserControl subclasses Control and can provide a clear distinction in the inheritance tree for custom controls you develop.
Control

52 Chapter 2: Working with Applications

NOTE
By strict definition, a .NET component implements IComponent. In practice, components are more loosely defined as the reusable objects I alluded to in the beginning. You need to be aware of the distinction as you work with .NET, because even in Microsoft communications, the word component is used with both meanings. Similarly, controls implement user-interface functionality; however, you can create server controls that you can use as components (general meaning) in ASP.NET applications.

This very basic description of components does not do justice to their contribution in .NET. Many core features build on .NET components, from Web services to database and user interfaces. For this reason, you will be seeing components, either directly or indirectly, throughout the examples in this book.

Component and Assembly Attributes


Youve seen some usage of attributes in adding information to classes. When youre building assemblies and components, you have a number of built-in attributes at your disposal to control how the binaries are built, deployed, and used. A good example of assembly attributes is the set defined in System.Reflection. Table 2.2 lists these attributes. Table 2.2 Assembly Attributes Attribute Effect
AssemblyAlgorithmIdAttribute AssemblyCompanyAttribute AssemblyConfigurationAttribute AssemblyCopyrightAttribute AssemblyCultureAttribute AssemblyDefaultAliasAttribute AssemblyDelaySignAttribute AssemblyDescriptionAttribute AssemblyFileVersionAttribute

Controls hash algorithm used to create the assembly manifest. A string indicating the assemblys source company name. A string indicating the build configuration of the assembly. A string to indicate copyright restrictions on the assembly. Indicates culture code for type compatibility; see RFC 1766 for valid culture codes. Defines a friendly name for the assembly, for use instead of the assembly name. True/false value indicating whether the assembly utilizes delayed signing. String value used to include a short description of the assembly. String indicating the file version if different from the assembly version.

Component and Assembly Attributes 53

Table 2.2 continued Attribute


AssemblyFlagsAttribute

Effect
Flag value that can control usage of the assembly; currently controls only side-by-side executionmultiple versions of the assembly can run: 0x0000 No restriction 0x0010 Not in the same app domain 0x0020 Not in the same process 0x0030 Not on the same computer Defines an informational version that is meaningful to users but is ignored by the runtime. Names the file containing the key pair to use to sign the assemblys strong name. Names the Cryptographic Service Provider key container holding the key pair used to sign a strong name. Names the product with which this assembly is associated. Provides a friendly title for the assembly. Provides trademark nformation. Sets version for type compatibility.

AssemblyInformational VersionAtttribute AssemblyKeyFileAttribute AssemblyKeyNameAttribute

AssemblyProductAttribute AssemblyTitleAttribute AssemblyTrademarkAttribute AssemblyVersionAttribute

The attributes in Table 2.2 all end in the word Attribute by convention. This convention is reflected in the way that you use the attributes. The compiler will fill in the blanks for you if you leave off the Attribute postfix, as in the following:
using System.Reflection; [assembly: [assembly: [assembly: [assembly: AssemblyTitle(MyAssembly)] AssemblyDescription(An example assembly)] AssemblyConfiguration(debug)] AssemblyVersion(1.0.*)]

Attributes for an assembly are specified using the normal square-bracket syntax, with the addition of the assembly target specification. You have to use the using statement to import the namespace containing the attributes youre going to use. You then use the attribute name, followed by the appropriate parameters in parentheses. Although attributes are by and large to be supplied as you see fit, you should supply the AssemblyVersion, AssemblyName, and AssemblyCulture when you sign an assembly with a strong name. Components have specific attributes you can set, as well. Table 2.3 lists some of them, from the System.ComponentModel namespace.

54 Chapter 2: Working with Applications

Table 2.3 Component Attributes Attribute Effect


AmbientValueAttribute BindableAttribute BrowsableAttribute CategoryAttribute DefaultEventAttribute DefaultPropertyAttribute DefaultValueAttribute DescriptionAttribute DesignerAttribute DesignerCategoryAttribute DesignerSerialization VisibilityAttribute DesignOnlyAttribute EditorAttribute EditorBrowsableAttribute ImmutableObjectAttribute InheritanceAttribute InstallerTypeAttribute LicenseProviderAttribute ListBindableAttribute LocalizableAttribute MergablePropertyAttribute

NotifyParentPropertyAttribute ParenthesizeProperty NameAttribute PropertyTabAttribute

Identifies a control property, which is obtained from the parent object Marks a control as safe for binding Prevents or allows display of a property at design time Sets the category in which a property or event is displayed at design time Identifies the default event of a component Sets the default property for a component Sets the default value for a property Provides a description of a component Identifies the library and type of a components designer Specifies the category of a component designer Controls whether and how a property should be saved by a designer Marks a property that can be set only at design time Sets the editor to use to modify a property at design time Controls whether a property should be displayed for editing at design time Marks a component whose properties are not modifiable at design time Holds inheritance level of a component Identifies the type of an installer to use to install the target component Identifies the type that provides licensing support on the target Specifies whether a list can be bound Marks properties that should have localization resources created during code generation Controls whether a property can be displayed with other properties in a property window at design time Marks a property whose parent should be notified of changes to the property at design time Controls displaying the property name in parentheses in the Properties tab of a development tool Indicates the Property tab and optionally the scope of a component for a development tool

D e v e l o p m e n t To o l s 5 5

Table 2.3 continued Attribute


ProvidePropertyAttribute ReadOnlyAttribute RecommendedAsConfigurable Attribute RefreshPropertiesAttribute

Effect
Marks a property extender provider Marks a property that is read-only in a designer Marks a property that should be made available as a configuration setting for the user of an application Controls what degree of repainting is done to refresh a properties view when a property value changes in a designer Controls whether an installer is run when a component is installed Associates a type converter with an attribute

RunInstallerAttribute TypeConverterAttribute

Most of the attributes in Table 2.3 are intended to support using your components in development tools such as Visual Studio .NET. By implementing appropriate types and connecting them to your component using the appropriate attributes, your component can integrate completely into Visual Studio and other development tools that work with .NET components.

Development Tools
As a new environment for software, the .NET framework requires new development tools. Although many programmers will use Visual Studio .NET to develop applications, the .NET Framework SDK download includes, for free, all the tools necessary to build .NET applications in C#.

Compiling C# with csc


Of course, the main piece of software you need to know about is csc, the C# compiler. Table 2.4 lists the options available from this program. Table 2.4 Switch Commonly Used C# Compiler Switches Abbreviation Purpose
none none
/d:symbol(s)

/addmodule:modfile_list /debug[+|-] /define:symbol(s) /doc:file

/lib:path_list /incremental[+|-] /incr[+|-]

Add modfile(s) to an assembly Turn debugging info on or off Define preprocessor symbols Output XML documentation to filename Search path_list for libraries Enable or disable optimizations

56 Chapter 2: Working with Applications

Table 2.4 Switch

continued Abbreviation
/linkeres:file_list /o[+|-]

Purpose
Store reference to files in output file Turn optimizations on or off Create filename as the output file Use the assembly files named to resolve references Include file_list in the output file Create a console application Create a windowed application Create a dynamic link library Create a module for an assembly

/linkresource:file_list /optimize[+|-] /out:file /reference:file_list

none
/r:file_list

/resource:file_list /target:exe /target:winexe /target:library /target:module

/res:file_list /t:exe /t:winexe /t:library /t:module

The lists (path_list, file_list) in Table 2.4 are comma separated, containing one or more of a path or file specification as appropriate. You invoke the compiler by typing csc followed by appropriate options, ending with the names of the files you want to compile:
csc /out:program.exe file1.cs file2.cs

This example will produce the file program.exe by compiling the code in file1.cs and file2.cs. Note that no separate link step is required when you use the compiler this way; the output is immediately executable. Applications are rarely built in one source file and one executable, however. Those who remember the difficulty of building and managing DLLs in previous versions of Windows will be amazed at the difference in .NET. Because every PE file always carries a manifest listing its contents, all you have to do is tell the compiler how to put your program together as you compile each piece. Listing 2.1 shows a simple class that contains a single property and a constructor that Ill put in a DLL that I can then use from other applications. Listing 2.1
UsedClass.cs:

A Simple Class for the Library

public class UsedClass { private string myName;

D e v e l o p m e n t To o l s 5 7

Listing 2.1

continued

public string Name { get { return myName; } set { myName = value; } } public UsedClass() { myName = This is a UsedClass object.; } }

The UsedClass type is declared public so that anyone can use it. To compile this class into a DLL requires only the following command:
csc /out:UsedClass.dll /t:library UsedClass.cs

The

out option UsedClass.dll.

on the compiler tells it the name of the file to create, in this case I used the abbreviated target command to tell the compiler to create a library, and then the name of the file to compile (UsedClass.cs). To use the type in the DLL from another project requires only that you reference it when you compile the code. For example, the code in Listing 2.2 uses the UsedClass type. Listing 2.2
MainFile.cs:

Making Use of

UsedClass

using System; public class SimpleMain { public static void Main() { UsedClass uc = new UsedClass(); Console.WriteLine( uc.Name ); } }

After storing this code in the file MainFile.cs, the following command compiles it for execution:
csc /out:a.exe /t:exe /r:usedClass.dll MainFile.cs

58 Chapter 2: Working with Applications

In this example, the /t:exe option tells the compiler to produce an executable; the /out:a.exe specifies the name of the file that is produced from MainFile.cs. Because the code uses UsedClass, the compilation would fail for an unresolved reference to a type, but the option /r:UsedClass.dll tells the compiler that it should include types from the referenced DLL when it resolves references in the code. This will create an executable, which makes the appropriate calls into the UsedClass.dll at runtime.

Managing Compilation with nmake


Making use of csc to compile a small number of files is okay, but it quickly becomes unmanageable in any nontrivial project. The SDK supplies the nmake utility to manage larger developments. This utility is a rules-based dependency-management tool that evaluates dependencies among files, recompiling the affected modules when you make changes to your code by comparing timestamps on the files. The program does not automatically recognize dependencies. You must create a makefile containing rules and commands that control its execution. A makefile can contain comments, declarations, and rules. Comments are plain text beginning with a # character. Declarations are simple variable declarations, like those you would find in a batch file or other script:
# this is a simple declaration SRC=SourceFile.cs

This code declares the variable SRC to be equal to SourceFile.cs so that you can use it in other areas of the makefile. Rules are the meat of a makefile. A rule generally consists of a target filefiles on which the target dependsfollowed on separate lines by one or more commands to execute if the target becomes out of date. In the DLL example, the UsedClass DLL depends on the file UsedClass.cs. This dependency relation can be expressed to nmake by using the following rule:
UsedClass.dll: UsedClass.cs csc /out:UsedClass.dll /t:library usedClass.cs

The first line names the dependent file, UsedClass.dll. This is followed by a colon and then by a list of the files on which the DLL dependsin this case just the source file, UsedClass.cs. This tells nmake that it should check the timestamp on UsedClass.cs, and if it is newer than the DLL, the DLL needs to be re-created. The second line, indented using a tab, contains the compiler command necessary to get the job done. You can combine multiple rules into one file to manage all the compilation and other tasks necessary to build your project. Listing 2.3 shows a simple makefile that would accomplish building the sample application.

D e v e l o p m e n t To o l s 5 9

Listing 2.3

A Simple Makefile for the Library Example

a.exe: MainFile.cs UsedClass.dll csc /out:a.exe /t:exe /r:UsedClass.dll MainFile.cs UsedClass.dll: UsedClass.cs csc /out:UsedClass.dll /t:library UsedClass.cs

When nmake reads the file, it assumes the first rule is the main file that you want to build, and it evaluates other rules based on that assumption. In this case, the first rule is the one for building the executable; the executable depends on MainFile.cs and the DLL. Knowing that, nmake looks to the second rule, which describes how to create the DLL if necessary. Together, these two rules give nmake all the information it needs to build the executable and to build the DLL if youve changed the source files that go into creating it. If you store these rules in a text file called makefile (no file extension) in the same directory as the two source files, typing nmake at the command line will cause nmake to read the makefile and then build the targets as necessary.
nmake reads the file named makefile by default. You can also use an option switch, /f, to provide an alternative filename. Table 2.5 lists additional useful nmake options.

Table 2.5 Switch


/a /i /n /s /t /?

Commonly Used Purpose

nmake

Switches

Rebuild all targets (ignore timestamps) Ignore errors (normally nmake stops if an error occurs) Only display commands without executing (useful for debugging makefiles) Suppress displaying commands as they are run Change timestamps on files without building Display help information

Although the first example will handle building the sample application, as with many things technological, the simple is too simplistic. Youll want to make use of additional nmake features in your makefiles to stay flexible as your project grows. Listing 2.4 shows a makefile for the DLL example that is far more robust. Listing 2.4
1: 2: 3: 4: 5: 6: 7: 8: 9:

A Better Makefile for the Library Example

# makefile: 7/20/2001 wmr # # This file manages compiling the test dll and exe for # chapter 2 of Pure C#. # # 2001 Pearson Education # declare project file groups LIB=UsedClass.dll

60 Chapter 2: Working with Applications

Listing 2.4
10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:

continued

LIBSRC=UsedClass.cs LIBDEP=$(LIBSRC) makefile EXE=a.exe EXESRC=MainFile.cs EXEDEP=$(LIB) $(EXESRC) makefile # declare build rules $(EXE): $(EXEDEP) csc /out:$(EXE) /t:exe /r:$(LIB) $(EXESRC) $(LIB): $(LIBDEP) csc /out:$(LIB) /t:library $(LIBSRC) clean: del $(EXE) $(LIB)

Lines 1 to 6 of Listing 2.4 are comments. Like any other type of code, you should include information about the purpose and function of the artifact. Lines 8 to 15 declare several variables, using a number of them at the same time. Line 9 declares a variable called LIB, which stores the name of the DLL; LIBSRC (line 10) holds the filename of the source file, and LIBDEP holds the names of the files on which the library depends, including LIBSRC and the makefile itself. When the makefile changes, the library should be rebuilt in case the build steps have changed. Note the syntax for using the LIBSRC variable in line 11: you surround the variable name with parentheses, prepending a dollar symbol. Line 11 declares LIBDEP with UsedClass.cs makefile as its value. Lines 17 to 22 are essentially the same code as in the first makefile, but are rewritten to use the variables Ive defined. If you run nmake without specifying a target, it assumes the first rule in the file is the file that you want to build, so Ive placed the executable file as the first rule, with the rule to build the DLL following it. When you execute nmake, you can supply the name of a target as a command-line parameter. Lines 24 and 25 show a target that uses this feature. They define a target named clean that will never be touched in a normal build of the application, because no other target depends on it. The clean target is often defined in a makefile as a means to remove all the intermediate and target files produced by the compilation process. Assuming the makefile is named makefile, you can execute the following command to clean out all the compiled files, leaving just the source:
nmake clean

This command tells nmake to build the clean target; because the target has no dependencies, the commands associated with it will always execute, resulting in the following command being issued:
del a.exe UsedClass.dll

D e v e l o p m e n t To o l s 6 1

It might not be immediately apparent why the second makefile is an improvement over the first but when your project starts to grow, it becomes eminently clear. For example, the first example would require more effort than necessary to maintain as you add files to your project. However, by coding the makefile as in the second example, the only thing necessary to include a new code module (in this case, named Another.cs) is to add the name of the new file, as in line 10 of Listing 2.5. Listing 2.5
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:

A Better Makefile for the Library Example

# makefile: 7/20/2001 wmr # # This file manages compiling the test dll and exe for # chapter 2 of Pure C#. # # 2001 Pearson Education # declare project file groups LIB=UsedClass.dll LIBSRC=UsedClass.cs Another.cs LIBDEP=$(LIBSRC) makefile EXE=a.exe EXESRC=MainFile.cs EXEDEP=$(LIB) $(EXESRC) makefile # declare build rules $(EXE): $(EXEDEP) csc /out:$(EXE) /t:exe /r:$(LIB) $(EXESRC) $(LIB): $(LIBDEP) csc /out:$(LIB) /t:library $(LIBSRC) clean: del $(EXE) $(LIB)

After adding the filename, nmake will incorporate the file into new builds without any additional work. This is only an introduction to what you can do with nmake. Although Windows programmers have become used to integrated programming environments, most development on Unix-style platforms is driven by some type of makefile. Although straightforward references to nmake are few, the .NET SDK samples demonstrate many other techniques you can use, and youll see additional techniques throughout this book.

Assembly Building with sn and al


Building a DLL for use by your own program is useful, and it can help avoid library version conflicts. However, building bare assemblies without any additional

62 Chapter 2: Working with Applications

information precludes the CLR from performing version checks at runtime, so you have to manage your libraries completely on your own. If you sign your assembly with a strong name before distributing it, however, you gain both the assembly versioning features of the platform and the capability to share the assembly among applications via the global assembly cache. Although this is referred to as signing the assembly, remember that it does not verify the originator of the code, only the version. Creating an assembly with a strong name requires two steps: 1. Obtain a digital key pair. 2. Sign the assembly at compile time using the key. The sn utility is included in the Framework SDK to manage keys for strong names. The simplest use is to generate a key file using the /k switch (note that sn treats switches as case sensitive):
sn /k:key.dat

This command creates a file called key.dat in the current directory and places a cryptographic key pair in the file that you can then use to sign your assemblies. If its appropriate, you can store key pairs into a cryptographic storage provider (CSP) container by using the /i option:
sn /i keyfile container_name

Other options you can use with sn are listed in Table 2.6. Table 2.6 Switch Common Switches Purpose
sn

/D assy1 assy2 /k keyfile /i keyfile cont /d cont /R assy keyfile /Rc assy cont /v[f] assy /Vr assy [user_list] /Vu assy

Compare assy1 and assy2 to verify that only their signatures differ. Generate a key file. Install key into CSP container called cont. Delete CSP container cont. Sign (or re-sign) assembly assy with the key pair in keyfile. Sign (or re-sign) assy from CSP container cont. Verify signature on assy; if the f is included, forces verification even if the assembly is registered to skip. Register assy temporarily to skip signature verification. Unregister assy from skipping signature verification.

WARNING
The sn tool enables you to bypass signature verification so that you can use convenient signature keys during development. There is a risk, however; when you later disable skipping verification, the framework will not go back and verify signatures of the assemblies you have been working with. This can leave unverified assemblies in the GAC.

D e v e l o p m e n t To o l s 6 3

Signing the assembly is no more difficult than creating the key; you can either apply the key using sn or provide the key as an option to the assembly linker, al. When you sign an assembly using sn, you should include at least the following assembly attributes in your code:
[assembly: AssemblyVersion(1.0.0.0)] [assembly: AssemblyCulture()]

These attributes contribute the metadata that make up the assemblys strong name. You then compile your assembly as you would normally. After the assembly is compiled, you use the following command to sign the assembly:
sn /R assembly keyfile

In this example, assembly represents the name of the library file, and keyfile represents the name of the file containing the key pair to use to sign the assembly. To sign the UsedClass library using the keyfile key.dat, you would use the following command:
sn /R UsedClass.dll key.dat

For larger assemblies constituting a collection of many files, you can compile your code to intermediate modules and then use the al assembly linker to construct a DLL containing references to the module files. Although this increases the number of files that must be distributed for the assembly, it benefits performance in that the runtime will load only those modules that are actually invoked during program execution. Until the program makes use of a type in a module file, the runtime will not even check whether the file exists. Continuing with the UsedClass example, modifying the makefile to produce a signed assembly produces the code in Listing 2.6. Listing 2.6
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:

Creating an Assembly: The Makefile

# makefile: 7/20/2001 wmr # # This file manages compiling the test dll and exe for # chapter 2 of Pure C#. # # 2001 Pearson Education # declare project file groups LIB=UsedClass.dll LIBSRC=UsedClass.cs Another.cs LIBMOD=UsedClass.mod Another.mod LIBDEP=$(LIBMOD) makefile LIBKEY=/keyf:key.dat EXE=a.exe EXESRC=MainFile.cs

64 Chapter 2: Working with Applications

Listing 2.6
17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33:

continued

EXEDEP=$(LIB) $(EXESRC) makefile # declare build rules $(EXE): $(EXEDEP) csc /out:$(EXE) /t:exe /r:$(LIB) $(EXESRC) $(LIBMOD): $(LIBSRC) csc /out:$@ /t:module $*.cs $(LIB): $(LIBDEP) al $(LIBKEY) /out:$(LIB) /t:library $(LIBMOD) newkey: sn -k $(LIBKEY) clean: for %i in ( $(EXE) $(LIB) $(LIBMOD) ) do del %i

To the original makefile, I start by adding a LIBMOD variable holding the names of the modules Im going to create (line 11) and a LIBKEY variable with the al option to name the keyfile to use (line 13). This includes the option switch so it can easily be changed to a different type of signature repository by changing the option switch along with the value. The rule for building the executable is unchanged; it is in the rules for the assembly that the differences appear. Lines 23 and 24 make use of nmakes capability to apply one rule to several files at a time. Line 23 declares that all the files named in the LIBMOD variable depend on the files named in LIBSRC. nmake therefore takes each file in LIBSRC and applies the command in line 24. Line 24 uses two nmake special macros; for each file in LIBSRC, nmake replaces the $@ macro with the name of the target file being generated and replaces the $* macro with the base name of the target (no file extension). Thus, for the UsedClass.mod target, nmake creates the following command and executes it:
csc /out:UsedClass.mod /t:module UsedClass.cs

This produces the individual .mod files from each source file. Lines 26 and 27 of the makefile create the assembly DLL itself; al executes with the signature switch declared in line 13, the name of the DLL to create from line 9, and the name of the modules to link to the assembly. The resulting DLL contains no code, only a manifest containing references to the modules in the assembly. Table 2.7 lists additional switches you can use with al.

D e v e l o p m e n t To o l s 6 5

Table 2.7 Switch

Common

al

Switches Abbreviation

Purpose
Sets assemblys base address Sets company attribute (see AssemblyCompanyAttribute) Sets configuration string (see
AssemblyConfigurationAttribute)

/baseaddress:address /company:cy_info /configuration:info /copyright:info /culture:info /delaysign[+|-] /description:info /evidence:file /embedresource:file

/base /comp /config /copy /c /delay /descr /e /embed

Sets copyright string (see


AssemblyCopyrightAttribute)

Sets culture string (see


AssemblyCultureAttribute) Specifies whether the assembly is delay signed Sets description string (see AssemblyDescriptionAttribute) Embeds file in the target as a resource named Security.Evidence Embeds file as a resource in [, name[, private]] target; optionally specify the name for the resource, and optionally mark resource as private to the assembly Sets informational version (see AssemblyFileVersionAttribute) Sets assembly flags (see AssemblyFlagsAttribute) Instructs al to report full paths in any error messages Displays usage information Names the file containing the keys to sign the assembly Names the CSP container holding the key(s) to sign the assembly Links file as a resource to the [, name[, assembly; optionally name the target[, private]]] resource, optionally name a target to which the file is copied, and optionally mark the resource as private Specifies the entry point for the assembly Suppresses al information printout Required; specifies name of output file

/fileversion:version /flags:flags /fullpaths /help /keyfile:filename /keyname:info /linkresource:file /keyf /keyn /link

/main:method_name /nologo /out:file

66 Chapter 2: Working with Applications

Table 2.7 Switch

continued Abbreviation
/prod /productv

Purpose
Sets product string (see
AssemblyProductAttribute)

/product:info /productversion:info

Sets product version string (see


AssemblyInformational VersionAttribute)

/target:lib /target:win /target:exe /template:file

/t:lib /t:win /t:exe

/title:file /trademark:file /version:version /win32icon:file /win32res:file @file /trade /v

Creates a DLL Creates a GUI application Creates a console application Names an assembly from which to inherit assembly metadata (used for a satellite assembly) Sets friendly title (see AssemblyTitleAttribute) Sets trademark string (see AssemblyTrademarkAttribute) Sets assembly version (see AssemblyVersionAttribute) Names a .ico file for inclusion in the output for display in Explorer Names a .res resource file for inclusion in the output file. Specifies a response file for al to read options from

You use the following syntax with al:


al source_spec option_spec

The source_spec is a sequence of module filenames, /embedresource, and/or /linkresource options, and option_spec includes any other appropriate options.

Managing Assemblies with gacutil


When youve signed your DLL with a strong name, you can use the resulting library the same as you did the original DLL, but now you have the additional capability to install it to the global assembly cache to share with other applications. You can add or remove assemblies in the global assembly cache by using Windows Explorer, but gacutil is available for use by installation scripts and makefiles. You use gacutil with the following syntax:
gacutil [ options ] [ assembly_file ]

Table 2.8 describes the options available for gacutil.

Debugging in .NET 67

Table 2.8 Switch


/cdl /help /i assembly /nologo /silent

gacutil

Switches Abbreviation

Purpose
Deletes all components from download cache Displays usage information Installs an assembly into the GAC Suppresses gacutil information printout Suppresses all output Uninstalls an assembly from the GAC. Using the long version instead of the abbreviation will also remove it from the native assembly cache, if present.

/h

or

/?

/ungen nspace

/u

Note that although you can clear out the download cache by using the /cdl option, you cannot add components to it. When you install an assembly using gacutil, you provide the utility with the name of the file containing the assembly; when you uninstall it, you supply the name of the assembly. To install the UsedClass assembly into the GAC, you can either drag and drop it into %systemroot%\assembly or use the following command in a command prompt:
gacutil /i UsedClass.dll

Symmetrically, to uninstall the class, you use this command:


gacutil /u UsedClass

Debugging in .NET
To debug an application requires two things: analyzing runtime execution (whats usually implied by debugging) and inspecting the output of the build process. The .NET Framework SDK includes two debuggers: the command-line cordbg and the GUIbased DbgCLR, also known as the Microsoft CLR Debugger. Which debugger you use depends on your needs. cordbg gives you a large set of commands to see into the plumbing of the CLR and your application, but it requires devotion to learn to use effectively. DbgCLR, on the other hand, contains nearly all the same capability but is much more accessible (and thus arguably more useful). The SDK also contains assembly/disassembly tools, ilasm and ildasm, which can help you with insight into your compiled application or system components.

TIP
You might have to adjust your environment path to enable you to easily run the GUI debugger. Although the cordbg program file is located in the ...\Microsoft. NET\bin directory with the rest of the SDK tools, the CLR debugger is in its own directory, called GuiDebug.

68 Chapter 2: Working with Applications

Debugging with DbgCLR


will be immediately familiar to anyone used to the Microsoft family of development tools. The tool provides much the same debugging capability as that which exists in the Visual Studio .NET IDE, but without the development and solution management tools. The program also gives you the capability to save debugging sessions as .dln files so that you neednt re-create your environment each time you start debugging.
DbgCLR

You can start the program from the command line, by double-clicking it in Explorer, or by creating a shortcut on the Start menu or desktop (my preferred method). Whichever method you use, the program displays initially as shown in Figure 2.1.

Figure 2.1 The Microsoft CLR Debuggers interface is a familiar one.

When you first begin debugging, the debugger needs to know with which program you want to work. Remember, you must compile the program using the /debug+ option on any module you are going to be debugging. For most situations, youll use the Program to Debug option on the Debug menu to tell the debugger to load an executable. This command produces a dialog box that you can use to set the program file, startup arguments, and working directory for the program. When youve set these parameters, you can use the VCR-like play controls to start, stop, and pause the program. You can also attach to an already running process by using the Debug Processes option on the Tools menu. This option produces the dialog box shown in Figure 2.2. The top control group in this dialog box shows the processes currently running on the computer and the bottom shows those to which you are currently attached (processes you are

Debugging in .NET 69

debugging). Unlike previous development environments from Microsoft, which required terminating a debugged process when you left the debugger, in .NET you are free to attach to a managed process, work with it in the debugger, and then detach the debugger and allow the process to continue running. You can debug multiple processes simultaneously, which enables you to work with multiprocessing programs; however, missing from the debugger in the Framework SDK is the capability to attach to remote processes as you can in the full Visual Studio .NET package.

Figure 2.2 Attaching to a process in DbgCLR.

After you have loaded or attached a program, you have a good number of tools with which to see into your code and its execution. Figure 2.3 shows the debug menus expanded. This should give you some idea of the completeness of the toolset available. Table 2.9 describes the elements that youll find on the Debug menu. Table 2.9 Option Debugging Options in DbgCLR Purpose
Controls execution of the program Attach/Detach processes Controls effect of exceptions on program execution Single step execution Evaluate expressions, view variables Use breakpoints to control execution

Continue, Break, Stop, Restart Processes Exceptions Step Into, Over, Out QuickWatch New, Clear, Disable Breakpoints

70 Chapter 2: Working with Applications

Figure 2.3 Debugging tools available in DbgCLR.

For different views of your programs execution, you can use the windows listed in Table 2.10 on the Debug/Windows menu. Table 2.10 Window Debugging Windows in DbgCLR Purpose
Displays the documents running in the application youre debugging; enables you to debug scripts and controls in those documents Displays variables and expressions you specify Displays local variables and other items pertinent to the current execution context Displays variables declared in the current context, including the this object Window specifically dedicated to viewing this Command window where you can type expressions and statements in the current debugging language; useful for ad hoc modification to variables, invoking methods, and so forth When stopped at a breakpoint, shows the stack of calls above the current frame and enables you to navigate up and down the list so you can inspect program state in parent frames

Running Documents

Watch Autos Locals This Immediate

Call Stack

Debugging in .NET 71

Table 2.10 Window


Threads

continued Purpose
Displays all threads in current program so you can change debugging context and pause or resume threads Displays information on modules loaded by the program, with version and other information Displays raw memory in use by your application Dissassembles and displays the native code for the application; does not display IL Shows the value available in arithmetic and segment registers and the CPU status flags

Modules Memory Disassembly Registers

The GUI debugger provides an extensive set of tools for analyzing your programs execution. The principal difference between it and the full debugger in Visual Studio .NET are the enterprise-oriented features in the latter. Although the Framework SDK includes minimal management capability, combining a good programmers editor with the SDK tools will suffice completely to achieve many programming tasks.

A View Inside Compiled Assemblies


An often neglected expertise, but one thats necessary to be truly effective at debugging, is understanding the underlying structure and execution of the compiled code. .NET introduces an additional layer of complexity with intermediate language, but it also includes tools to help you work with it. The debugger can show you the native assembly code; to access the IL code, you use ildasm, the Intermediate Language Disassembler. The ildasm utility can be run in either GUI mode or text mode. Text mode, which sends output either to the console or to a file, produces a listing of the disassembled metadata and code in an assembly; you can then inspect this code to see in detail how your compiled program will operate. In GUI mode, on the other hand, you can browse your assemblys manifest and code interactively, as shown in Figure 2.4.

Figure 2.4 Browsing disassembled code in ildasm.

72 Chapter 2: Working with Applications

The contents of the information that is displayed in the GUI or output in text mode is governed by switches you supply on the command line. Table 2.11 describes these switches. Table 2.11 Switch
/all /bytes /header /item=itemspec /linenum /nobar /noil /out=file /pubonly /quoteallnames /raweh /source /text /unicode /tokens /visibility=tla [+tla...] /UTF8

Disassembly Switches for ildasm Purpose


Shorthand for /bytes /header /tokens. Include byte values from code in output. Include PE header information. Only disassemble itemspec, where itemspec is a fully qualified member or type name; for example, UsedClass::get_Name. Include line numbers from original source. Dont display status bar pop-up during execution. Dont include IL code. Identifies a file to receive text output. Include only public items. Enclose names in single quotes. Show exception handler code in output. Include original source code as comments in the IL listing. Invokes ildasm in text mode for console or file output. Output Unicode text. Include metadata tokens in output. Only output items with specified visibility(ies) where tla is the three-letter abbreviation indicating the visibility for which to filter. Output UTF-8 encoded text.

TIP
A reference to MSIL is outside the scope of this work, but a complete reference to IL and the execution environment is included with the Framework SDK in the directory <sdk_install_dir>\FrameworkSDK\Tool Developers Guide\docs.

For those really adventurous programmers, there is a corresponding ilasm utility, which assembles MSIL assembly code from ildasm output, from other development tools, or from your own authoring effort. Most C# programmers will never write their own MSIL assembly code, however; I mention it here for completeness.

Summary
The .NET framework is a powerful platform on which to develop applications. The tools in the .NET Framework SDK include compilers, debuggers, and a raft of additional tools for building applications and managing configuration and deployment. This chapter described some of these tools. The following chapter completes the description of the .NET Frameworks C# programming environment by touring some of the Base Class Library, the .NET runtimes library classes.

The Base Class Library

CHAPTER 3
The Base Class Library
Just as any other language does, C# needs a runtime library it can depend on. C#s runtime is the .NET frameworks Base Class Library (BCL). The BCL is the basic runtime library required in all CLI implementations and includes all the classes in the System namespace, except for the following:
System.Data System.Net System.Reflection System.Web System.Windows.Forms System.Xml

The BCL helps enable your programming work by extending the CLRs execution and compilation support with an extensive library of classes, collections, and system APIs that are used by all programming languages and compilers on .NET. Programmers using multiple languages no longer need to learn multiple programming models and runtime libraries, and code and data can be shared among applications. The BCL is also extensible. In .NET, you can inherit from and extend classes even if you dont have the original source code, as long as the class is not marked sealed in its manifest. At runtime, your derived class is the same as it would be if you had developed the original class yourself.

Architecture and Profiles


The .NET platform is built using a layered architecture, as shown in Figure 3.1. The CLR provides the basic environment and then the BCL builds on that; additional libraries, applications, and development tools build out to meet particular needs.

74 Chapter 3: The Base Class Library

Figure 3.1. The .NET framework libraries are built in layers.

The .NET platform includes the concept of profiles, predefined configurations of the BCL, the CLR, and certain additional libraries that enable specific functionality. A profile is tailored to the needs of a particular hardware environment or application, such as a mobile phone or home appliance. The only currently defined profiles are the Kernel and Compact profiles. The Kernel profile is the very minimum required of a conforming implementation of the CLI. The Kernel profile doesnt require support for floating-point math, complex arrays, reflection, or remoting. The Kernel profile contains only the BCL and compiler and execution facilities. The Compact profile adds XML, Networking and Reflection to the services available in the Kernel profile. The Compact profile is intended to enable small-footprint .NET applicationsfor mobile devices, appliances, and similar equipment that might suffer under resource limitations.

Strings and Regular Expressions


C# gains extensive support for string manipulation by exposing the string capabilities in the CLR. Strings have flexible formatting options, as well as comparison and search functions using the classes in System.Text.RegularExpressions. Strings can be used in many ways. Listing 3.1 shows several of them.

Strings and Regular Expressions 75

Listing 3.1

String Manipulation Examples

using System.Text; . . . string s1, s2; StringBuilder sb; // // s1 s2 sb simple initialization; the strings are assigned references to the literal objects. = This is a test.; = this is a test.; = new StringBuilder();

// case sensitive/insensitive compare if ( string.Compare( s1, s2, true ) == 0 ) sb.Append( The strings are equivalent. ); else sb.Append( The strings are not equivalent. ); char c = s1[7]; // gets the char at position 7 char [] ar = new char[20]; s1.CopyTo( 0, ar, 0, s1.Length ); // copies range of chars // enumeration

CharEnumerator ce = s1.GetEnumerator(); while ( ce.MoveNext() ) { sb.Append( ce.Current ); }

if ( s1.EndsWith( test. ) ) // checks end of string sb.Append( The string is a test. ); s2 = (string)s1.Clone(); // copies a reference so that // s2 now points to s1s string

int i = s1.IndexOf(is); // returns 2 i = s1.LastIndexOf(is); // returns 5 string s3 = s1.Insert(5, value); sb.Append( + s3); s3 = s1.PadLeft(20, ); // right aligns s1 in a 20-char // wide space. s1 = s3.Trim(); // removes padding again; can also // TrimLeft(), TrimRight() s3 = s1.ToLower(); // convert to lower case // has to make a new string // strings are immutable

76 Chapter 3: The Base Class Library

The CLR also provides the System.Text.RegularExpressions namespace, which contains flexible pattern-matching classes. Many of the searching methods in strings (such as EndsWith()) provide functionality that is easily implemented using a regular expression. Listing 3.2 shows some uses of Regex functionality. Listing 3.2
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43:

Using Regular Expressions

// illustrates using custom capture processing protected string MatchEval( Match m ) { // append a newline and return return m.ToString() + \n; } private void DemoRegexp() { StringBuilder sb = new StringBuilder(); string s1, s2; bool b; // // s1 s2 Set up to strings; these are used throughout the following code. = This is a test?; = this is a test.;

Regex r = new Regex(test); int i = r.Match( s1 ).Index; r = new Regex(test.$); b = r.IsMatch(s1, 0); // equiv to s1.IndexOf( literal )

// same result as // s1.EndsWith( literal )

s2 = This is a really long, aimless sentence that + has no real purpose but to illustrate using regular expressions.; // separate a sentence into individual words r = new Regex(@\S*[ \.]); MatchCollection mc = r.Matches(s2, 0); foreach ( Match m in mc ) { // Process each word here; I just show it on the screen System.Windows.Forms.MessageBox.Show( m.Captures[0].ToString(), Capture ); } // place all the words in the sentence on their own line s2 = r.Replace( s2, new MatchEvaluator( MatchEval ) ); System.Windows.Forms.MessageBox.Show(s2, Capture); }

Strings and Regular Expressions 77

Regular expressions have long been a part of computerized text processing. I first encountered regular expressions while working on Unix in 1990, and they were nothing new then. The examples in Listing 3.2 only scratch the surface of their power, but they illustrate the key concepts. A regular expression is a pattern, which can contain both literal text and special characters that are interpreted as any number of character classes. You pass the pattern to the Regex constructor, as in line 19 of Listing 3.2. This pattern is a simple literal stringthe word test. I then use the Regex objects Match() method to look for that pattern in the string s1 at line 21. Line 23 uses a different pattern, making use of two character classes: the period and the dollar symbol. Regex interprets the period as a wildcard indicating any single character; the dollar symbol is likewise interpreted as the end of a line in a multiline input string, and as the end of the string. Therefore, the pattern test.$ will match the word test if it is separated by one character from the end of a line or the end of the string; the Boolean value stored to b in line 24 will therefore be true because the word test is the last in the string s1, with one character (the question mark) separating it from the end of the string. Lines 31 to 38 demonstrate a more complicated expression and the Regex librarys capability to find more than one match at a time. The pattern in line 31 looks for sequences of characters ending with either a space or the end of a line or the string. The @ before the pattern string makes it easier to write the string itself by turning off C#s escape character processing (otherwise, each time I use a \, I would have to use two of them). The first item in the string is \S*. \S is an escape sequence that Regex interprets as matching any non-whitespace character. The asterisk tells it to match any number of them. The next item is a character list in brackets, [ \.]. Regex will look at the position of the character list for any one of the characters within the bracketsin this case, a space and a period (the latter escaped so it is interpreted as a literal character instead of a wildcard). The result of the pattern is to tell Regex to look for any sequence of nonwhitespace characters ending either in a space or a period. The result is that it will match each individual word in the sentence. Having established the search pattern, line 32 uses the pattern to search the string for matches, but instead of just the first match, the Regex.Matches() method returns all matches in the string as a MatchCollection object. Each Match object contains a collection called Captures, which contains the text that matched the pattern. For a simple pattern like this one, there will be only one capture, but you can code more complex expressions that can match the text in multiple ways. In the end, the message box (lines 36 and 37) shows each word in the sentence in turn. Regular expressions dont have to be just for finding text; they also provide a way to transform it by using the Replace() method on the Regex instance. You can provide a string to use to replace text matching the pattern or take further control over the process by providing a delegate method that processes each match, or both. The code at lines

78 Chapter 3: The Base Class Library

41 and 42 use the delegate method to hook the MatchEval() function into the replacement process. Line 41 invokes r.Replace() on the string s2, passing a MatchEvaluator delegate that is initialized with a reference to the MatchEval() function. The Regex instance r invokes the delegate with each match in the source string; whatever the MatchEval() function returns replaces the matched text in the text that Replace() returns. In this case, the search matches each word, and MatchEval() replaces each match with the match and a newline character (line 5). The resulting string is displayed in a dialog box, as shown in Figure 3.2.

Figure 3.2 The transformed string is displayed by the sample code.

This has just been an introduction to regular expressions in .NET. Entire books have been written on the subject of regular expressions, but there are more topics to cover in this one. You can easily find more information on the Internet or learn about them with the book Sams Teach Yourself Regular Expressions in 24 Hours by Alexia Prendergrast, ISBN 0-672-31936-5.

Collections
Collections and containers are important tools for programmers, and .NET comes with its share of library classes, including ordered and unordered lists, stack, queue, and dictionary types. Table 3.1 lists the basic collections provided by the .NET framework. Table 3.1 NET Container Classes Container Usage
ArrayList BitArray Hashtable Queue SortedList Stack

Dynamically sized list with array-style access Array of bit (Boolean) values Dictionary arranged by hash of keys FIFO (first in, first out) container Dictionary sorted by key LIFO (last in, first out) container

Collections 79

Listing 3.3 shows using these classes. Listing 3.3


1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45:

Using Basic Collections

public class KeyValue { string key; string val; public KeyValue( string newKey, string newValue ) { key = newKey; val = newValue; } public override string ToString() { return Class KeyValue: Key: + key + Value: + val; } public string GetKey() { return key; } public string GetValue() { return val; } } class CollectionDemo { static void Main(string[] args) { KeyValue [] kv = new KeyValue[] { new KeyValue( 1, value new KeyValue( 2, value new KeyValue( 3, value new KeyValue( 4, value new KeyValue( 5, value }; bool[]

1 2 3 4 5

), ), ), ), )

b = { true, false, false, true, true, false };

ArrayList al = new ArrayList(); foreach ( KeyValue k in kv ) { // add to array al.Add( k ); } Hashtable Queue Stack ht = new Hashtable(); q = new Queue(); s = new Stack();

80 Chapter 3: The Base Class Library

Listing 3.3
46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92:

continued
Console.WriteLine( \nArrayList: ); foreach ( KeyValue k in al ) { Console.WriteLine( k ); // add to hashtable ht.Add( k.GetKey(), k.GetValue() ); // add to queue q.Enqueue( k ); // add to stack s.Push( k ); } // lookup by key Console.WriteLine( \nHashtable: ); Console.WriteLine( ht[4] ); // objects return in order they were added Console.WriteLine( \nQueue: ); while ( q.Count > 0 ) { Console.WriteLine( q.Dequeue() ); } // objects returned in opposite order Console.WriteLine( \nStack: ); while ( s.Count > 0 ) { Console.WriteLine( s.Pop() ); } Console.WriteLine( \nBitArray: ); BitArray ba = new BitArray(b); foreach ( bool bv in ba ) { Console.WriteLine( bv ); } // add to sorted list in random order SortedList sl = new SortedList(); int [] order = { 3, 4, 1, 2, 0 }; foreach ( int i in order ) sl.Add( kv[i].GetKey(), kv[i].GetValue() );

Collections 81

Listing 3.3
93: 94: 95: 96: 97: 98: }

continued
// the objects come back out sorted by key Console.WriteLine( \nSortedList: ); foreach ( DictionaryEntry de in sl ) Console.WriteLine( de.Key + , + de.Value );

To demonstrate using collections requires a set of objects to store in them, so Listing 3.3 begins by declaring a KeyValue type to store string pairs. The type overrides the Object.ToString() to facilitate writing the objects out to the screen. Lines 25 to 32 create and populate an array of five of these objects. To test the BitArray, line 34 declares an additional array of bool values. The first collection in the example is the ArrayList declared at line 36. The ArrayList combines the behaviors of arrays and lists, providing an unordered bag of objects that can be accessed by using array syntax, as in al[3], or by using an enumerator as in the example. Lines 37 to 41 add each of the KeyValue objects into the array, and then the foreach loop starting on line 48 writes each of them out to the console. values into a and uses the key. You can override this function to provide a custom hash calculation or, as Listing 3.3 does, depend on the implementation provided by Object. You can retrieve objects from the Hashtable using array notation and a key value, as demonstrated in line 64. For efficiency, Hashtable bins objects based on their hash code and then searches only in the appropriate bin for the requested keys value. The Queue is a collection to store objects by order of insertion and retrieve them in the same order. You use the Enqueue() method to add objects to the collection, as at line 56, and then Dequeue() to retrieve the next object from the collection, as at line 70. The Stack object functions in the opposite manner. Objects come out of the stack in the opposite order from which they went in. You use the Stacks Push() method to add an object onto the stack and Pop() to take it off. Lines 74 to 78 demonstrate retrieving the objects off the stack. The BitArray collection stores bit values that you access as bool elements. In Listing 3.3, the array of bool declared at line 34 initializes the BitArray ba on line 81. You can also declare the size of the array in the objects constructor. You access individual elements of the array using array notation with an integer index. BitArray objects have the additional capability to perform binary AND, NOT, OR and XOR operations. For example, the following code masks off the first two bits of the first example by using an AND operation:
bool[] BitArray b2 = { false, false, true, true, true, true }; ba2 = ba.And( new BitArray( b2 ) );

The foreach that iterates the values in the ArrayList puts those same Hashtable, a Queue, and a Stack. The Hashtable contains any object GetHashCode() method to obtain an integer hash value for each items

82 Chapter 3: The Base Class Library

The last collection to discuss, the SortedList, orders its contents based on the value of the items keys. In the example, lines 88 through 91 create a SortedList instance and then add the test elements into the list in random order. The output from the code retrieving the items, however, displays them in order as follows:
SortedList: 1, value 1 2, value 2 3, value 3 4, value 4 5, value 5

As with other collections, you can access the items in a indexer and the objects key value:
string str = (string)sl[1];

SortedList

by using its

These are the basic collections provided by the framework. However, other collections are implemented throughout the framework for specific items. These other collection types implement the ICollection interface and are listed in the documentation for that interface in the .NET Framework Reference.

Serialization
Serialization is the process of assembling a representation of an objects state in a form that can be persisted to a disk file or sent across the network and from which the object can be re-created in another context. .NET supports serialization by separating the destination (file, network connection, and so on), the rendering (translating the object to and from a stream of bytes), and the object itself. Translating from an object to a stream and back again is handled by objects called formatters. These are contained in namespaces that are children of the System.Runtime.Serialization.Formatters namespace. .NET comes with two formatters out of the box: one that records a binary copy of objects (BinaryFormatter) and one that stores the object into a SOAP envelope (SoapFormatter). The binary formatter renders the object into an efficient binary representation that can be read quickly; this is a good solution for saving to disk or for transmitting objects across the network between similar platforms. The SOAP representation, on the other hand, can be read and used on any platform, so it might be useful in heterogeneous systems but not without a cost; the difference in size between the binary and SOAP versions of an object are significant. Listing 3.4 demonstrates using the serialization classes to write objects out to disk and read them back in. Listing 3.4 Serializing with Formatters

1: using System; 2: using System.IO;

Serialization 83

Listing 3.4
3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49:

continued

using System.Collections; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Serialization.Formatters.Soap; . . . [Serializable] class StreetAddress { public int id; public string name, street1, street2, city, state, zip; public StreetAddress() { name = street1 = street2 = city = state = zip = ; id = 0; } public StreetAddress(int inId, string inName, string inStreet1, string inStreet2, string inCity, string inState, string inZip) { id = inId; name = inName; street1 = inStreet1; street2 = inStreet2; city = inCity; state = inState; zip = inZip; } } class Serializer { static void Main(string[] args) { ArrayList addresses = new ArrayList(10); // create a list of addresses for ( int id = 0; id < 10; id++ ) { addresses.Add( new StreetAddress(id, AName, 123 Main St., Ste. 800, Anywhere, AK, 12345) ); }

84 Chapter 3: The Base Class Library

Listing 3.4
50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: }

continued
// write the information out as XML IFormatter soapFmt = new SoapFormatter(); Stream s = File.Open( outfile.xml, FileMode.Create ); soapFmt.Serialize( s, addresses ); s.Close(); // write the information out in binary form IFormatter binFmt = new BinaryFormatter(); s = File.Open(outfile.bin, FileMode.Create); binFmt.Serialize( s, addresses ); s.Close(); // reopen and read the data back in s = File.Open( outfile.bin, FileMode.Open ); addresses = binFmt.Deserialize( s ) as ArrayList; for ( int i = 0; i < addresses.Count; i++ ) Console.WriteLine( ((StreetAddress)addresses[i]).id.ToString() + + ((StreetAddress)addresses[i]).name);

The first class declared in Listing 3.4, StreetAddress, holds typical address information. StreetAddress is marked with the Serializable attribute so it can be serialized. The Serializer class creates a collection of these objects in lines 40 through 48. It then uses a SoapFormatter to write a SOAP version of the address list and a BinaryFormatter to write a binary version. All that is necessary is to create the formatter (lines 51 and 57), create a stream for it to write the information to (lines 52 and 58), and then invoke the formatters Serialize() method. Most objects contain more than just the values that make up their state; they usually also contain references to other types, which must also be re-created when the object is deserialized. The formatter takes care of inspecting the object and identifying the references it holds. It walks through the reference graph and serializes each object onto the stream. With the necessary information written into the stream, re-creating the object out of the stream only requires using the Deserialize() method on the same kind of formatter that was originally used to write the stream. The Serializer class reads the array of addresses back in from the binary file by first opening it as a stream at line 64 and then calling the BinaryFormatter.Deserialize() method on line 65. Because the Deserialize() method returns Object, I added the as ArrayList to type the result back to an ArrayList reference.

Input and Output 85

Input and Output


Even with a freshly minted application platform, some of the same old needs remain. One of these is the need to move information between your program and other entities. The .NET platform provides stream- and random-access I/O classes and the System.Console class, the members of which are used by console applications to access the standard input and output streams. In addition to the expected file and console I/O, .NET provides classes for memory, string, and network streams. Listing 3.5 shows the most basic of file operations in C#. Listing 3.5
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:

Using Basic I/O

// Create a file with some data in it. string str = This is some text.; FileStream fs = File.Create( testfile.txt ); byte [] buff; // get the bytes for each character in str // and write to the file. buff = Encoding.Unicode.GetBytes( str ); fs.Write( buff, 0, buff.Length ); fs.Close();

Listing 3.5 uses a FileStream object to create a text file and then write a string value to it. The static File.Create() method creates a file on disk and returns a FileStream attached to it. Lines 8 gets the underlying bytes from the string, and line 9 writes those bytes to the stream. Finally, line 11 closes the FileStream. Working directly with byte arrays is okay when you need that level of control, but often it is more work than its worth. Therefore, the BCL has higher-level classes to facilitate more streamlined I/O. Listing 3.6 demonstrates accomplishing the same result as Listing 3.5, but uses the StreamWriter class to simplify the code. Listing 3.6 Using Simpler I/O Using StreamWriter

string str = This is some text.; StreamWriter sw = new StreamWriter(testfile.txt, false); sw.Write( str ); sw.Close();

The StreamWriter is intended to aid you in reading and writing string values on a stream. Using the StreamWriter also has the advantage of writing the Unicode preamble (0xFEFF) to the text file automatically, enabling the OS and other programs to automatically recognize the files encoding and byte order. If you need instead to write ASCII text, you can specify the encoding to the StreamWriter:
StreamWriter sw = new StreamWriter(testfile.txt, false, System.Text.Encoding.ASCII);

86 Chapter 3: The Base Class Library

.NET supports stream composition to further facilitate I/O operations. For example, the BinaryWriter class enables you to read and write basic value types without going through the intervening steps necessary to obtain their raw data representation. BinaryWriter defines Write() methods for the base value types and for byte and char arrays. The following code writes a byte array to a sample file:
byte [] ar = new byte[10]; // initialize with values 0 to 9 for ( byte i = 0; i < 10; ar[i] = i, i++ ); FileStream fs = File.Create( testfile.dat ); BinaryWriter bw = new BinaryWriter( fs ); bw.Write( ar ); bw.Close();

To write a character array is no more difficult:


string str = This is some text.; char [] cha = str.ToCharArray(); FileStream fs = File.Create( testfile.dat ); BinaryWriter bw = new BinaryWriter( fs ); bw.Write( cha ); bw.Close();

Reading from a stream involves using the symmetrically defined reader classes. Reading a string, for example, looks like the following:
string str; StreamReader sr = File.OpenText( testfile.txt ); str = sr.ReadToEnd(); Console.WriteLine(str); sr.Close();

All the foregoing examples have been synchronous, meaning that control does not return to your code until the operation completes; however, you will often want to use an asynchronous model, particularly with console or network operations. To enable asynchronous operation, you construct a FileStream object with the appropriate parameters and then use the BeginRead() method to start the actual I/O operation. BeginRead() accepts as one of its parameters a delegate that you initialize with the address of a callback method. The method is called when the I/O is completed. You can also pass a state variable in to BeginRead(), which will then be passed to the callback method. Listing 3.7 demonstrates these features.

Input and Output 87

Listing 3.7
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47:

Using Basic I/O

struct ReadInfo { public FileStream fs; public byte [] ba; public long bufSz; public ManualResetEvent ev; } static void ReadCallback( IAsyncResult res ) { ReadInfo ri = (ReadInfo)res.AsyncState; for ( int i = 0; i < ri.bufSz; i++ ) Console.Write( ri.ba[i] ); ri.fs.Close(); ri.ev.Set(); } static void Main(string[] args) { // Create the callback delegate AsyncCallback ac = new AsyncCallback( ReadCallback ); ReadInfo ri = new ReadInfo(); // open the file ri.fs = new FileStream( testfile.dat, FileMode.Open, FileAccess.Read, FileShare.None, 256, true );

// // // // // //

path open mode access requested sharing? buffer size isAsync?

// set up other variables in the state struct ri.bufSz = ri.fs.Length; ri.ba = new byte [ri.bufSz]; ri.ev = new ManualResetEvent( false ); // Start the read operation ri.fs.BeginRead( ri.ba, 0, (int)ri.fs.Length, ac, ri );

// // // // //

buffer offset to start n bytes to read callback state object

88 Chapter 3: The Base Class Library

Listing 3.7
48: 49: 50: 51: }

continued

// Wait for the child thread. ri.ev.WaitOne();

To hold the various pieces of information related to the I/O operationthe stream, buffer, buffer size, and the synchronization objectthe code first declares the ReadInfo structure with appropriate methods. The next step is to write the method that will receive the data when the read is complete, the function ReadCallback(). Finally, the code in Main() ties it all together. Line 22 creates an AsyncCallback with ReadCallback() as its target, and line 23 instantiates the state object for the operation. Lines 26 to 33 create the new FileStream with appropriate parameters, the most important for the demonstration being the last; this bool value, set to true, tells the FileStream to set up for asynchronous operation. With the FileStream opened, I use its Length property to size the buffer in the ri structure in lines 36 and 37 and then create a synchronization event to signal when the operation is complete. With all that preamble, the actual read operation starts with the BeginRead() call at line 41. This passes the read buffer, an offset into the buffer at which to start reading (which is zero in this case), the number of bytes to read, the callback delegate, and the state structure. All the main thread does after that is wait for the synchronization event. The call to BeginRead() returns immediately, but of course, the read itself does not happen instantaneously. A separate thread of execution completes the operation, which is the reason for the ManualResetEvent. While the main thread is waiting on the event, the system completes the read and then calls into ReadCallback() with the result. This function writes out the data from the file (lines 11 to 13), closes the file (line 15), and then signals the event. The main thread then resumes execution at line 51, from which it exits normally. Although it requires more setup to work, in many instances, particularly in server or distributed applications, asynchronous I/O is necessary. It does not make sense for small data blocks, but for larger or unpredictably sized items, using asynchronous operations can keep you from tying up your application for extended periods while waiting for I/O to complete.

Network Communication
Network communication in C# follows the same model as traditional languages do, but with fewer details for the user to manage. Socket programming can still be difficult, but wrapper classes facilitate common tasks. The System.Net* namespaces are not technically part of the Base Class Libraries, but I include them in this discussion because communication is so important today. The framework and Windows support a number of protocols, but I focus on Internet Protocol (IP) and sockets programming because most readers needing to communicate across the network will use IP-based technology.

Network Communication 89

The two protocols in common use are the Transmission Control Protocol (TCP) and the User Datagram Protocol (UDP). With each of these protocols, a datagram is sent from a client identified by its IP address and a port number, a logical identifier that identifies a program or service on a computer. A Web server, for example, will usually listen to requests on port 80. UDP provides connectionless, unreliable delivery of packets between computers. UDP is called unreliable because it does not include any mechanism for maintaining a connection between computers or assuring delivery of data to the recipient. UDP doesnt even guarantee that the receiving program will receive information in the same order it was sent. The program using UDP to communicate has to provide any error checking or receipt mechanism it needs. TCP, on the other hand, is a connection-oriented, assured-delivery protocol that ensures that the data sent by the program reaches the intended recipient in order, or that the application is notified if not. As packets are received on the destination computer, they are organized into the same order as they were sent, and acknowledgements are transmitted back to the sender as each sequential packet is recognized. If the sender doesnt receive an acknowledgement, it retransmits the packet that was lost. Eventually, if too many errors occur, TCP reports an error back to the application that is trying to send the data. This method of communication is often easier to work with than UDP, but it also uses more network resources to exchange the same amount of information.

Sockets
A socket is an endpoint of communication between two programs that can carry information in either direction between the programs using it. To establish communication by using sockets, you must specify five things: the IP addresses of the sender and the receiver, the port number each is using, and the protocol used to communicate (normally TCP or UDP). The first program wanting to communicate creates a socket and binds it to a port number and protocol on its host. To establish communication, the second program connects to the first using a socket it has created itself. When the connection is made (with TCP) or a packet is sent (with UDP), the five parts of the channels identity have been specified: the host and port on each end of the channel and the protocol used between them. When using the UDP protocol, no persistent connection exists between the communicating applications; a packet is sent with the hope it will be received. With TCP, on the other hand, a virtual circuit is established between the two computers that persists until the participating applications shut it down. That virtual circuit is serviced by the TCP providers on each end exchanging status messages to manage traffic flowing between the two computers.

Communicating with Sockets


In most communications using sockets, one application starts and begins listening on a socket. Another application later starts and sends information to the first, alreadyrunning application. For convenience, Ill refer to the first application as the server and the second as the client.

90 Chapter 3: The Base Class Library

The first step in establishing the communication for both the client and the server is to create a Socket and bind it to the address family, address, and port with which the socket will be associated. What happens next depends on the kind of connection you plan to use. For connection-oriented TCP sockets (stream sockets), the server next places the socket into a listening state and waits for connections. On the client end, the communicating program issues a connection request identifying the server and port to which it wants to connect. After the connection is made, the programs can communicate using the socket. Connectionless UDP sockets require no further setup. Because no persistent connection exists between computers, the server enters its listening state, and the client sends the message. No acknowledgement is returned, therefore the operation returns immediately. Listing 3.8 shows how to send and receive datagram messages. Listing 3.8
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:

Communicating with a Datagram Socket

using System; using System.Net; using System.Net.Sockets; 5: namespace Comm { class Communication { const int sendPort = 20000; const int bufSize = 256; void Start( string ipremote ) { Socket soc; int bytesMoved = 0; // number of bytes transferred byte [] buf = new byte[bufSize]; // data buffer // Set up the local socket address IPEndPoint localEp = new IPEndPoint( IPAddress.Any, sendPort ); // create a datagram socket soc = new Socket( AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp ); // bind the endpoint to the socket soc.Bind( localEp ); if ( ipremote == ) { // server mode - listen

Network Communication 91

Listing 3.8
27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: }

continued
bytesMoved = soc.Receive( buf ); } else { // client side - transmit IPEndPoint remote = new IPEndPoint( IPAddress.Parse(ipremote), sendPort); for ( int i = 0; i < bufSize; buf[i] = (byte)i, i++ ); bytesMoved = soc.SendTo( buf, remote ); } Console.WriteLine( Transferred {0} bytes., bytesMoved ); } static void Main(string[] args) { Communication comm = new Communication(); // If IP to connect to is specified on the command line, // connect as client; otherwise listen as server if ( args.Length == 1 ) { comm.Start( args[0] ); } else comm.Start( ); }

Listing 3.8 imports the System, System.Net, and System.Net.Sockets namespaces. Although most of the classes needed to work with sockets are declared in System.Net.Sockets, several classes in System.Net are also necessary. The programs entry point is the Main() function, beginning at line 40. The program is invoked in either a client or server mode, depending on whether you specify an IP address on the command line. Main() invokes the Start() function using the IP address or an empty string, and then Start() does the appropriate work. The Start() function sends a block of data to another computer if it receives an IP address in its remote parameter or waits to receive data if not. The first step, at line 16, is to create an IPEndPoint containing the local computers IP address and the port the program will use to communicate. Lines 18 to 21 create a new IP datagram socket that will use UDP to send information. The sockets setup is completed by binding the endpoint to it using Socket.Bind() at line 23.

92 Chapter 3: The Base Class Library

TIP
SocketType.Dgram SocketType.Stream,

and ProtocolType.Udp are almost always used together; similarly, and ProtocolType.Tcp are used to specify a TCP stream socket.

Line 24 selects the programs behavior based on whether an IP was supplied. If the program is in server mode, line 27 calls Socket.Receive() to listen for incoming information. When data arrive, the library writes them into the buf buffer and returns the number of bytes that were received. On the other hand, if the program is in client mode, it creates a new endpoint with the address and port to send the data to (line 32), fills in the buffer with some data to send (line 34), and then sends the information (line 35). Thats all you need to send UDP datagrams. Life would be simpler if that was all you needed for network programming. However, UDP is simple because it has no control mechanism to manage delivery. The next example, Listing 3.9, uses TCP to manage the connection and ensure that the data sent from the client are received by the server. Listing 3.9
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30:

Communicating with a Stream Socket

using System; using System.Net; using System.Net.Sockets; 5: namespace Comm { class Communication { Socket soc; const int sendPort = 20000; const int bufSize = 256; void Start( string ipremote ) { int bytesMoved = 0; byte [] buf = new byte[bufSize]; IPEndPoint localEp = new IPEndPoint( IPAddress.Any, sendPort ); soc = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp ); // bind the send socket soc.Bind( localEp ); if ( ipremote == ) { // server mode; start listening soc.Listen(1); // begin accepting connections

Network Communication 93

Listing 3.9
31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: }

continued
Socket readSock = soc.Accept(); // on return from Accept(), readSock is a new, // connected socket bytesMoved = readSock.Receive( buf ); // were done readSock.Shutdown(SocketShutdown.Receive); readSock.Close(); // soc isnt connected so go on and close soc.Close(); } else { // client side IPEndPoint remote = new IPEndPoint( IPAddress.Parse(ipremote), sendPort); for ( int i = 0; i < bufSize; buf[i] = (byte)i, i++ ); // connect to the server soc.Connect(remote); bytesMoved = soc.Send( buf ); // close down the connection soc.Shutdown(SocketShutdown.Send); soc.Close(); } Console.WriteLine( Transferred {0} bytes., bytesMoved ); } static void Main(string[] args) { Communication comm = new Communication(); // Act as client or server if ( args.Length == 1 ) { comm.Start( args[0] ); } else comm.Start( ); }

94 Chapter 3: The Base Class Library

The program in Listing 3.9 achieves the same result on the surface as the datagram sample does, but by using a TCP stream socket connection. The first change is at lines 19 and 20, where the type and protocol for the socket are changed to SocketType.Stream and ProtocolType.Tcp, respectively. The local endpoint is bound to the socket as usual on line 23. On the server end, the program sets the socket to listen for incoming connections at line 28 by calling the Socket.Listen() function, and then begins accepting connections by calling Socket.Accept(). Accept() returns a new Socket connected to a client. Although I dont in this example, you could then go back to listening for more connections on the original socket. Between the time the call to Accept() returns a Socket and the time you go back to listening by calling Accept() again, there is a period when your software is not listening for connections, but incoming requests could arrive nevertheless. The backlog value passed into Listen() specifies the number of these requests that the system will queue up waiting for your program to call Accept(). With the socket connected, line 35 calls Receive() on the socket to wait for data to arrive, and the library fills in the buffer with received data. That concludes the receive, but additional steps are needed with a TCP socket to close it out in an orderly manner. Because the TCP socket maintains a stateful connection, the program should tell the program on the other end that it is ending the connection; the Socket.Shutdown() call on line 38 initiates this process. The TCP protocol implementations exchange control messages to disconnect, and then the Socket.Close() function frees up the socket. The client code works the other end of the connection. To request a connection to the server, it calls Socket.Connect() with an IP endpoint containing the remote IP and port to connect to. Because a TCP socket maintains a connection, the call to Send() does not require you to include an endpoint as a parameter like the UDP SendTo() function does. When the buffer is sent, the client closes its end of the connection using Shutdown() and Close().

Network Helper Classes


Programming with sockets is much less arcane in C# than in C and C++, but it can still be difficult to write and debug code. The .NET framework includes the UdpClient, TcpClient, and TcpListener classes to make communicating easier. The UdpClient handles the details for UDP communications. It also implements methods such as Connect() that enable you to work with the object as if it were connection oriented (although the actual connection still is not). Listing 3.10 shows the Start() function from the socket examples modified to use the UdpClient class. Listing 3.10 Communicating with UdpClient

1: void Start( string ipremote ) 2: { 3: byte [] buf; 4: 5: UdpClient uc = new UdpClient(sendPort);

Network Communication 95

Listing 3.10
6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: }

continued

if ( ipremote == ) { // the parameters used to initialize the endpoint are not // used, but the ep cannot be constructed empty IPEndPoint remote = new IPEndPoint( IPAddress.Any, sendPort ); // read data buf = uc.Receive(ref remote); } else { // client side buf = new byte[bufSize]; for ( int i = 0; i < bufSize; buf[i] = (byte)i, i++ ); uc.Send( buf, buf.Length, ipremote, sendPort ); } Console.WriteLine( Transferred {0} bytes., buf.Length );

UDP communication isnt terribly complex in the first place, but UdpClient simplifies it more. In Listing 3.10, line 5 declares an instance with the port to use on the local computer. For the receive end, line 11 creates an IPEndPoint to receive the IP and port of the program sending to this program, and then line 14 receives the information. On the sending end, line 22 is all thats needed to transmit the data; the Send() method handles parsing the string IP address, hooking up to the port, and transmitting the data. Using the TCP helper classes is a little more complicated, but it still is less effort than programming sockets directly. Listing 3.11 demonstrates. Listing 3.11 Communicating with TcpListener and
TcpClient

1: void Start( string ipremote ) 2: { 3: int bytesMoved = 0; 4: byte [] buf = new byte[bufSize]; 5: 6: if ( ipremote == ) 7: { 8: // set up the listener and accept 9: TcpListener tl = new TcpListener( sendPort ); 10: tl.Start(); 11: TcpClient tc = tl.AcceptTcpClient(); 12: tl.Stop(); 13: 14: // read data 15: NetworkStream stm = tc.GetStream();

96 Chapter 3: The Base Class Library

Listing 3.11
16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: }

continued

bytesMoved = stm.Read(buf, 0, buf.Length); tc.Close(); } else { // client side TcpClient tc = new TcpClient(ipremote, sendPort); NetworkStream stm = tc.GetStream(); stm.Write(buf, 0, buf.Length); bytesMoved = buf.Length; tc.Close(); } Console.WriteLine( Transferred {0} bytes., bytesMoved );

The server code in lines 9 to 16 is conceptually similar to the socket code youve seen already. A listener is created on a port at line 9, and then line 10 tells it to start listening on the port. Line 11 blocks on the listener to wait for a connection. TcpListener.AcceptTcpClient() returns an instance of a TcpClient that is connected to the remote client. Calling Stop() on the listener ends receiving connections on the TcpListener. Reading information from the connection is accomplished by using the NetworkStream returned from the TcpClient.GetStream() method. This type implements a standard stream interface on the connection. Line 16 of Listing 3.11 uses it to read the transmitted data from the connection, and then line 17 closes the connection. On the client end, line 22 instantiates a TcpClient with the target server IP and port to connect with. Line 25 writes the data to the socket, and then line 27 closes the port. Note that no explicit connection or shutdown is necessary in either block of code.

Summary
The Base Class Library contains the .NET Framework platforms essential set of application services. These range from simple collections to high-level classes that support communication across the network. The BCL makes using advanced features like asynchronous I/O easier than ever. By describing the BCL, this chapter finishes my description of the general environment. In Part II, Technique Reference, Ill get into the details of writing programs with C#.

Variables and Types

CHAPTER 4
Variables and Types
The types available in C# include the simple types, classes, interfaces, structures, and enumeration. This chapter demonstrates using each of these. It also shows how type conversions are accomplished, including converting between strings and other types, with detailed examples of formatting with the StringBuilder class. Other examples demonstrate reading performance information and managing a state code by using an enumerated type. As you write code using C#, youll use the techniques in this chapter to create types that achieve the objectives of your application.

Simple Data
Simple data types are the granular data defined by the Common Type System (CTS). These types require only declaration and initialization to use. They do not require using the new operator to instantiate them, because they are allocated on the stack when the variable definition comes in scope. They are similarly deallocated automatically when execution leaves their enclosing context.

Instantiation and Use


Using simple data types is, wellsimple. You instantiate a variable by using the name of the type and an identifier to name the instance:
int c; // instantiates a variable; must still be initialized int I = 1; // instantiate and initialize at the same time

9 8 C h a p t e r 4 : V a r i a b l e s a n d Ty p e s

Simple data types can be initialized by using literals or expressions. These expressions can contain references or literal values, depending on the needs of the application, as in the following:
int a = 3; int b = a * 2; // initialize with a literal // initialize from an expression decimal

Floating-point values are similar, but float and typed when you are assigning literal values.
1: 2: 3: float a = 3.0F; double b = 1.5; decimal c = (decimal)a * 2.0M;

values must be explicitly

In this example, lines 1 and 3 demonstrate using postfix-type letters to specify the type of the literal; a numeric literal containing a decimal fraction is by default a double in C#. Therefore, to assign a literal to a single-precision float or 128-bit decimal, you must either cast the value or use a type specifier. Line 3 also demonstrates using a type cast to convert the float variable to decimal. The type specifiers, which are not case sensitive, are listed in Table 4.1. Table 4.1 C# Type Specifiers Type Specifier Resulting Type
F D M

or or or

f d m

float double decimal

(The decimal type is intended for currency values, thus M for money.)

C# integral types can be manipulated as integer values or as binary patterns, as in C++. The following example demonstrates this:
int i = 0xFFE7; i <<= 4; i &= 0xFF; // initial value in hex // equiv to i *= (int)Math.Pow( 2, 4 ) // mask lower two bytes

Although it removes many language elements found in C++, C# continues its economic expression.

Strings and String Conversions


Strings in C# are proper class types and can be manipulated using operators, as in Visual Basic. For example, it is legal to add two strings together, like the following:
String s; s = new String( One string.ToCharArray() ); s += another string;

Simple Data 99

This is legal, but not optimal. C# is different from other languages in that strings are immutableafter you create a string, its value cannot change. The runtime is implemented in this manner primarily to optimize performance; if strings were changeable, manipulating them would result in the character buffer associated with the string being constantly moved, and the runtime would have to update every reference to the string every time it was changed. To facilitate the common task of constructing strings, without incurring the additional overhead of mutable strings, the runtime provides the StringBuilder class. You use StringBuilder to assemble the pieces and then call its ToString() method to create the final String value. The following demonstrates this by displaying the values created by the earlier integer code:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: int i = 0xFFE7; // describe a hex value StringBuilder sb = new StringBuilder(); sb.AppendFormat( {0:X} is the initial value; , i ); i <<= 4; // equiv to i *= (int)Math.Pow( 2, 4 ) i &= 0xFF; // mask upper two bytes sb.AppendFormat( {0:X} is the final value., i ); Console.WriteLine( sb.ToString() );

In this example, line 2 instantiates a StringBuilder variable; this variable then formats two string values at line 4 and line 8, and line 10 prints out the following:
FFE7 is the initial value; 70 is the final value.

TIP
I used a StringBuilder because I was constructing the string piecemeal; if I were intending to construct the string in one step, I would have used the String.Format() method instead.

The format string you use for StringBuilder.AppendFormat() and String.Format() is unusual. You have two choices: standard or picture formatting. Standard formatting is similar to C++ printf-style formatting, by using format specifiers in a formatting string to control how the runtime converts the value. The formatting methods take a formatting string as their first parameter, which contains embedded tokens showing where to insert strings obtained from the remaining parameters. Each token is formatted using the following format:
{n[:fmt]}

where n is the zero-based index of the parameter following the format string to insert, and fmt is the relevant formatting specifier. The value supplied for fmt controls how the library formats the indicated parameter object, and the resulting string replaces the

1 0 0 C h a p t e r 4 : V a r i a b l e s a n d Ty p e s

token in the output string. If fmt is left out, the library uses the objects method to obtain a string representing the value. Table 4.2 lists the numeric format specifiers and their effects. Table 4.2 Specifier
Cn

ToString()

.NET Format Specifiers Format


Currency

Output
$nnn.nn (positive) ($nnn.nn) (negative) Produces n decimal places if n is given; otherwise uses two decimal places. nnn.nnn (positive) -nnn.nnn (negative) Produces at least n digits, padding with zeroes if necessary. nnnE+nnn Does not normalize to nearest multiple of three for the exponent. Same as decimal, but with max prec number of decimal places. Returns the shortest string with prec decimal places. Same as decimal format, but with culture-specific thousands separators; if prec is set, provides that many decimal places, otherwise gives two. Produces a percentage-formatted string from a fractional value where one is 100%; supplies prec decimal places if specified. Creates a value that can be converted by the runtime back to the type it was originally by using System.Convert; floating point numbers might lose precision. Produces a hex formatted value; if n is supplied, outputs at least n digits.

Dn

Decimal

Eprec

Exponential

Fprec Gprec Nprec

Fixed point General Numeric

Pprec

Percentage

Round-trip

Xn

Hexadecimal

Format specifiers in Table 4.2 are not case sensitive, but their case influences the case of letters in the output, where applicable. The following code illustrates this effect:

Simple Data 101 Console.WriteLine( Console.WriteLine( Console.WriteLine( Console.WriteLine( exp: EXP: hex: HEX: + + + + (0.000123456).ToString( e5 )); (0.000123456).ToString( E5 )); (0xABCD).ToString( x )); (0xABCD).ToString( X ));

Running this code produces the following output:


exp:1.23456e-004 EXP:1.23456E-004 hex:abcd HEX:ABCD

Dates and times have their own formatting options, shown in Table 4.3. Unlike the numeric specifiers, those in Table 4.3 are case sensitive. Table 4.3 Specifier
d D f F g G M R

.NET Date and Time Format Specifiers Format Output


Short date Long date Full (long date and short time) Full (long date and long time) General General (long time) Month and day RFC1123 compliant m/d/y w, m d, y w, m d, y h:m ap w, m d, y h:m:s ap m/d/y h:m ap m/d/y h:m:s ap md w, d m y h:m:s GMT (The weekday and month are abbreviated.) y-m-d h:m:s h:m ap h:m:s ap y-m-d h:m:sZ (appends Z for Zulu time zone) Same as F, but converts to GMT m, y

or or

m r

s t T u

U Y

or

Sortable Short time Long time Similar to s, but converts to GMT or Universal time Long universal Month and year

Other objects in the library define string conversions as appropriate to their types. Picture formatting gives you much finer control over numeric string formats, in a manner similar to the PRINT USING statements used in old BASIC dialects. A picture format is a string that serves as a template for how to format the number. You lay out the format using placeholders and literal text. Table 4.4 contains the tokens you can use and their significance.

1 0 2 C h a p t e r 4 : V a r i a b l e s a n d Ty p e s

Table 4.4 Token


0 # . , % E+fmt E-fmt \pfchar litstr {

.NET Picture Format Characters Usage


Zero-filled position; emits a digit from the value or a non-significant zero. Digit position. Decimal point. Thousands separator; can be used to round numbers, as well. Percentage; formats a value as a percentage, where 1.0 = 100%. Enables exponential display; fmt is a numeric format using number tokens. Using + always displays the sign of the exponent. C printf style escape, for example, \n for newline. Delimits a literal string of text. In a format string passed to one of the library Format functions, you must use double braces to include either character in the output string; braces are ignored by type.ToString(). Delimits sections to apply to positive, negative, or zero values.

or

and

The following code illustrates using picture formats with the individual types ToString() methods and with the String.Format() function:
decimal m = 12387679876.20M; float f = 0.9999F; Console.WriteLine( Console.WriteLine( Console.WriteLine( Console.WriteLine( Normal: Exponential: Round to Mil: Percentage: + + + + m.ToString(###,###,###.00) ); m.ToString(000.###E-00) ); m.ToString(###,###,, Million.) ); f.ToString(00.000%) );

String s = String.Format( {0:00.000%} is the percentage., f ); Console.WriteLine( s );

Executing this code produces the following output:


Normal: 12,387,679,876.20 Exponential: 123.877E08 Round to Mil: 12,388 Million Percentage: 99.990% 99.990% is the percentage.

Notice how using the trailing commas in the third example rounds the number automatically, and using the percentage formatter automatically multiplies the value by 100%.

Simple Data 103

Conversions and Casts


Conversion across types in C# is accomplished both through implicit and explicit means. Implicit conversions occur when a well-defined, unambiguous conversion from the source type to the target type is defined. For numeric types, implicit conversion is acceptable if both are similar types (both integral or real) and the target of the conversion is the same or greater width than the source. This means that a float can be assigned to a double implicitly, but going the other direction wont do. The simplest conversion involves creating a new object of the target type and constructing it from the source object, where the target type has a constructor that can initialize an object from the source type. This is no different from any other constructor invocation. C# supports defining overloaded operators to and from your classes, however, and gives you control over how they are used. Listing 4.1 demonstrates all three methods of conversion. Listing 4.1
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31:

Coding Type Conversions

public class IntString { int i = 0; public IntString( int ival ) { i = ival; } public IntString( String s ) { i = Convert.ToInt32(s); } public static implicit operator IntString( int iVal ) { return new IntString( iVal ); } public static implicit operator IntString( String sVal ) { return new IntString( sVal ); } public static implicit operator int( IntString i ) { return i.i; } public static implicit operator String( IntString i ) { return i.i.ToString(); } }

1 0 4 C h a p t e r 4 : V a r i a b l e s a n d Ty p e s

Listing 4.1
32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51:

continued

. . . String int IntString

s = 111; i = 999; istr;

// basic initialization from the String and int values istr = new IntString(s); istr = new IntString(i); // use // and istr = istr = implicit IntString operators to convert from String int values s; i;

// use implicit String and int operators to convert from // the IntString i = istr; s = istr;

Class IntString declares a type that is freely convertible between integers and strings. Instantiation-style conversion is accomplished in lines 5 through 13 by declaring constructors that accept either a String or int parameter. Lines 40 and 41 demonstrate using them. Conversion is a directional process; as youve seen, stipulating that conversion from type A to type B is possible does not imply that conversion from type B to type A is also allowed. Therefore, IntString defines operators for conversions in both directions for both int and String types. Lines 15 to 22 define conversion operators that take int or String values and return a new IntString instantiated with the source value. These enable you to write the code in lines 45 and 46, which directly assign an int and a String to an IntString variable. Finally, lines 23 through 30 enable conversion in the other direction, demonstrated in lines 50 and 51. C# also supports explicit typecasting, as in C++ and Java. For example, the following explicitly rounds a float and stores the result into an int:
float f = 3.1415926536F; i = (int) f; // i = 3

In Listing 4.1, all the operators were declared as implicit to require a minimum amount of typing to convert to and from an IntString. This is sufficient for demonstration, but you should really only define implicit conversions for conversions that will not produce unintended side effects or unclear code. For example, conversion among similar numeric types is a great use of implicit conversion because the types have such

Classes 105

similar behavior. For the IntString class, which has essentially no intrinsic semantics except for conversion, implicit conversions are not really appropriatethere is so much difference that the conversion should be made explicit. The only thing required is to change the character of the operators from implicit to explicit. Listing 4.2 shows the changes to the code. Listing 4.2
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22:

Coding Type Conversions

// Changes to the prototypes for the operators; now they are // explicit instead of implicit public static explicit operator int( IntString i ) ... public static explicit operator String( IntString i ) ... public static explicit operator IntString( int iVal ) ... public static explicit operator IntString( String sVal ) ... // use // and istr = istr = explicit IntString operators to convert from String int values (IntString)s; (IntString)i;

// use explicit String and int operators to convert from // the IntString i = (int)istr; s = (String)istr;

WARNING
Its hard to over-emphasize the importance of ensuring semantic appropriateness of operators. Every operator carries a specific meaning in the minds of people using your code, so you must make sure that the operators you create support that meaning. In particular, implicit operations, which dont perform as expected can create insidious, hard to find bugs.

Classes
The basic building blocks of programs in C# are classes. A class refers to a discrete type definition that defines the state information operations available from objects of that class. In object-oriented languages like C#, you can define classes as inheriting from a base class or implementing an interface. Inheriting from a parent class incorporates the members of the parent class into the derived class, which can then extend the parent with additional state or behavior. C# enables you to indicate if it is legal

1 0 6 C h a p t e r 4 : V a r i a b l e s a n d Ty p e s

to instantiate a class in object variables, or if it is an abstract class that can only be inherited from. The ability to define new classes makes it possible to extend the language and class libraries to achieve the objectives of your program. To demonstrate building classes, this section shows a class that reads performance information from NT and Windows 2000 servers. Listing 4.3 shows the code for the initial class. Listing 4.3
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39:

Building a Server Class

using System.Diagnostics; using System.Threading; namespace C4Servers { public class Server { // stores the name of the server we are monitoring protected string Host; // Property to read the servers CPU Usage public virtual double CpuUsage { get { PerformanceCounter pc; CounterSample cs1, cs2; // read the processor utilization pc = new System.Diagnostics.PerformanceCounter( Processor, % Processor Time, _Total, Host ); // get two samples so the Calculate function can compute // the average; have to be further apart than minimum // interval of the timer cs1 = pc.NextSample(); Thread.Sleep( 1 + (int)(1.0 / cs1.SystemFrequency) ); cs2 = pc.NextSample(); // Calculate() figures out the value from the two samples return CounterSample.Calculate( cs1, cs2 ); } } // property that reads the servers memory utilization (note

Classes 107

Listing 4.3
40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: }

continued
// this is just one of many counters that describe the current // memory situation on the machine) public virtual long MemUsage { get { PerformanceCounter pc; // Read the counter pc = new System.Diagnostics.PerformanceCounter( Memory, Committed Bytes, , Host ); // since the committed bytes value is absolute, we only // need one sample. return pc.NextSample().RawValue; } }

public Server(string Hostname) { Host = Hostname; } }// END CLASS DEFINITION Server

In Listing 4.3, the Server class is enclosed in a namespace called chapter four servers. The class declaration itself begins at line 7.

C4Servers,

for

TIP
The classes I show in examples often start life in Microsofts Visio 2002, which in its Professional version contains software and database-diagramming tools and the capability to generate database schema and code in a number of languages, including C#. Although I wish it could do more in some places, it is, in general, a good, economical solution for building moderately sized projects, and it integrates with Visual Studio .NET.

The Server class has a single field, Host, which stores the hostname on which a Server instance will report. This field is initialized by the constructor at line 60. Because the object has no heavyweight resources it maintains, the class has no destructor. The class declares two properties, both of which are read-only. The CpuUsage and MemUsage values cannot be set in reality, so the properties beginning at lines 13 and 42 each declare a get but no set.

1 0 8 C h a p t e r 4 : V a r i a b l e s a n d Ty p e s

The code within the accessors reads the appropriate performance counters and returns the value to the caller. You could use the Server object, as in the following code:
double i; Server s = new Server( SNAME ); i = s.CpuUsage; // MemUsage returns a long, but implicit conversion from long to // double is OK. i = s.MemUsage;

This code would read the CPU and memory load on the server named SNAME.

Interfaces
Creating a class for a Server is okay if youre going to be working only with Microsoft hosts. However, if you wanted to extend the class to report on database instances, you would encounter a problem; although its not unusual to standardize on one operating system platform, many times businesses will need to support more than one database product, so your programs design would need to easily accommodate more than one database type. Interfaces are a perfect solution to this problem. The first task is to define an interface that stereotypes the way to interact with a database server. Listing 4.4 shows such an interface. Listing 4.4 A Database Server Monitor Interface

1: namespace C4Servers 2: { 3: 4: public interface IDatabaseServer 5: { 6: 7: // Current number of users logged in. 8: long UserCount 9: { 10: get; 11: } 12: }// END INTERFACE DEFINITION IDatabaseServer 13: 14: }

In the example, a class implementing the IDatabaseServer interface will expose a property called UserCount that provides the number of users who are currently connected to the database instance. The property declaration in lines 8 to 11 requires any class implementing this interface to provide a read-only property called UserCount. The IDatabaseServer interface, by definition, cannot contain any implementation for the members it declares. Listing 4.5, on the other hand, shows a class that implements the interface to interact with a Microsoft SQL Server database instance.

Interfaces 109

Listing 4.5
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: using using using using using

Implementing the

IDatabaseServer

Interface

System; System.Data; System.Data.SqlClient; System.Text; System.Diagnostics;

namespace C4Servers { public class MSDbServer: Server, IDatabaseServer { protected string Instance; // Current number of users logged in. public long UserCount { get { PerformanceCounter pc; StringBuilder sb = new StringBuilder(); // create the counter name sb.Append(MSSQL); if ( Instance.Length > 0 ) sb.Append($ + Instance); sb.Append(:General Statistics); // Read the counter pc = new System.Diagnostics.PerformanceCounter( sb.ToString(), User Connections, , Host ); // since the committed bytes value is absolute, we need // only one sample. return pc.NextSample().RawValue; } } public MSDbServer(string Hostname, string InstanceName) : base(Hostname) { Instance = InstanceName; }

1 1 0 C h a p t e r 4 : V a r i a b l e s a n d Ty p e s

Listing 4.5
48: 49: 50: 51: }

continued

}// END CLASS DEFINITION MSDbServer

Beginning at line 10, Listing 4.5 defines the MSDbServer class, which derives from Server, so all that classs functionality is available and implements the IDatabaseServer interface to provide the database features. This class contains a protected field, Instance, which stores the SQL Server instance name from which you want to gain information. The constructor for the class (lines 43 to 47) is parameterized with the host and instance names; the instance name is stored in the Instance field, and the hostname passes to the base Server classs constructor on line 43. Implementing the propertys accessor is essentially the same as for those in the Server class, with the additional need to construct the counter-category name. The code in lines 24 to 27 accomplish this task, and then reading the counter is the same as for any other simple counter.

Structures
Structures are useful value types that organize related items when class semantics and features (such as Remoting) arent necessary. The classic examples are those of graphics points, colors, and so forth. Another useful application is managing database connection information, as shown in Listing 4.6. Listing 4.6 Organizing Database Connection Information Using a struct

// defines SQL connection structure using System.Data.SqlClient; // carries information used to establish a data connection, along // with common objects we use to interact with the connection. public struct SqlConnectionInfo { public bool trusted; // using trusted connections? public string uid, pwd; // user id and password public string host, instance; // the host and instance public SqlConnection conn; public SqlCommand cmd; public SqlDataReader dr; } // // // // the connection to the server command object to work with the server reader to get data from cmd

The SqlConnectionInfo struct type in Listing 4.6 groups the basic information used to establish a connection to a database together with the framework objects used to

E n u m e r a t e d Ty p e s 1 1 1

work with the connection and data. This set of information can be populated at one point and then passed among different sections of code, without having to pass the individual variables again and again.

Enumerated Types
Enumerated types implement sets of values to scope the domain of a value type. Enumerations are usually implemented under the hood as int values but can be treated either as integers with explicit casts or as native value types. Listing 4.7 demonstrates using an enum to reflect the status of a document during its lifetime. Listing 4.7 Using an
enum

to Reflect the Status of a Document

class Document { enum DocStatus { emptyDoc, loadedDoc, newDoc, dirtyDoc }; // emptyDoc empty object; save not needed // loadedDoc loaded a document from a file; not modified // newDoc created a new document; should be saved // dirtyDoc modified a loaded document; should be saved DocStatus status;

bool LoadDoc() { // load data from a file // then set the status status = DocStatus.loadedDoc; // return success return true; } bool ModifyContent( /* include appropriate parameters */ ) { // do whatever processing, then // set status if ( status == DocStatus.loadedDoc ) status = DocStatus.dirtyDoc; else status = DocStatus.newDoc; return true; } public Document() { // the document doesnt have anything in it yet

1 1 2 C h a p t e r 4 : V a r i a b l e s a n d Ty p e s

Listing 4.7
}

continued
status = DocStatus.emptyDoc;

public void CheckAndSave() { if (status == DocStatus.dirtyDoc || status == DocStatus.newDoc) { // if needed, save the data // then set the state status = DocStatus.loadedDoc; } } }

As you can see, enum types can make the code you write much clearer when you have a value with a small domain of possible values. The .NET Framework class library uses enumerated types extensively for such items as file status flags, days of the week, and window border styles. By using enumerations, the meaning and reliability of named constants like these is greatly improved.

Summary
The set of types you can work with out of the box is very robust in the .NET environment, but much of your work in creating applications for the platform will involve creating or extending new types of your own. This chapter has discussed the mechanics involved in working with types, and in manipulating and converting between instances of types. Next, Chapter 5, Classes and Components, will drill in with detailed information on the composition of classes, the most common type you will define.

PA R T I I
TECHNIQUE REFERENCE
Knowing how to code in C# is useful, but its just the first step. In Part I, you learn about C# and the tools and libraries you can use to program with it. By presenting techniques youll need to apply, Part II provides a more thorough reference for you to use as you create applications. Part II is organized so that you can use it as a source of answers while youre programming. Each chapter describes a specific topic in C# programming, such as configuration or memory management, and contains an explanation of the topic and code that you can use directly in your work. Although certainly worth your while, it isnt necessary for you to read Part II sequentiallyyou can also use it piecemeal as you need the information and code each chapter contains. 5 Classes and Components 6 Memory Management and C# 7 Advanced Program Control 8 Unsafe Code 9 Using Metadata and Reflection 10 Configuring Components and Applications 11 Using the SDK

Classes and Components

CHAPTER 5
Classes and Components
The class in C# is the fundamental construct used to support object-oriented programming. C# qualifies as an objectoriented language by virtue of its support for the concepts of encapsulation, inheritance, and polymorphism. As youll see, C# carries forward and improves many of the features of C++ and adds some great ones of its own, but it also adds restrictions on how you manage inheritance and visibility in your programs. In the end, C# is a powerful, modern tool for objectoriented programming.

Defining Entities and Classes


To demonstrate classes in C#, Ive chosen the Author and Title entities from the pubs sample database shipped with Microsoft SQL Server. For the examples, the Author entity will contain the following attributes: First and last name Authors contract status Titles the author has written The static structure diagram in Figure 5.1 shows some key classes of a C# implementation of the Author. The attributes and behaviors included in the design are those that the examples in the rest of the chapter require. Although the ultimate implementation will use other classes the System.Data.Common.DataSet object, for examplefor simplicity, the diagram includes only the classes suggested by the problem. These classes are suggested by the entity and the underlying table structure in the database, illustrated in Figure 5.2.

116 Chapter 5: Classes and Components

Figure 5.1
AuthorsListBuilder

The Author implementation requires Authors, a class to load the list.

SortedList

to hold them, and an

Figure 5.2 The physical data model in the pubs database stores the information retrieved by the Authors classes.

Methods 117

The elements in the class diagram show classes and their operations and attributes. In C#, you will use methods to implement operations, and elements such as fields, properties, or indexers to implement attributes. This chapters examples use methods and properties.

Methods
A method is the main vehicle for coding the behavior of your objects. A method declaration contains a combination of the following elements: Attributes, framed by square brackets Visibility modifier (such as public) Instance modifier (static) Code modifier (such as unsafe or extern) Inheritance modifier (such as sealed, virtual, or override) (Mandatory) Return type (Mandatory) Method name, which must be a well-formed identifier (Mandatory) Opening and closing parentheses, which contain zero or more comma-separated parameters

A method can accept both fixed parameters and parameter arrays. A fixed method parameter declaration consists of the following elements: Attributes, framed by square brackets Calling protocol modifiers: ref or out Type identifier Parameter identifier

For a variable parameters list, you can use a parameter array marked with the params modifier, as in the following:
void VarParms( params int [] ary ) { foreach ( int i in ary ) { // process an element } } void AMethod() { VarParms(); VarParms( 1, 2, 3, 4, 5 ); }

// this is OK // so is this

You can use both fixed and variable parameters in the same method declaration, but the parameter array must be the last declaration in the list:
static void VarParms(double f, string s, params int [] ary) {

118 Chapter 5: Classes and Components . . . }

Although I have demonstrated using an array parameter of type int, you can completely generalize the array by using System.Object, as in the following notional declaration for Strings Format() method:
public sealed class String { public static sealed string Format( string format, params object[] args ) { . . . } }

In this case, the first parameter to the method must be a string, but it may be followed by zero or more objects of any type whatsoever. To implement the Authors example, its AuthorsListBuilder class, shown in Listing 5.1, tiate a list of Author objects from the database. Listing 5.1 The
GetAuthors()

worthwhile to begin with the which uses a class method to instan-

Method Creates the List of

Author

Objects

1: public class AuthorsListBuilder 2: { 3: /// <summary> 4: /// Creates collection of authors from the database and 5: /// tenders them to user code in a SortedList. 6: /// </summary> 7: 8: // database connection info 9: protected string Host; 10: protected string Instance; 11: 12: // construct the object with host and instance of SQL Server 13: // to which we will connect 14: public AuthorsListBuilder(string DbHost, string DbInstance ) 15: { 16: Host = DbHost; 17: Instance = DbInstance; 18: } 19: 20: public SortedList GetAuthors() 21: {

Methods 119

Listing 5.1
22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68:

continued
SqlConnection Conn = null; SqlCommand Cmd = null; SqlDataReader Dr = null; SortedList Sl = new SortedList(); // connect to the database, retrieve author ids, then instantiate // an Author for each id, adding it to the list return value try { // Get the database Conn = GetConnection(); // Load from the database Cmd = Conn.CreateCommand(); Cmd.CommandText = SELECT au_id FROM authors; Dr = Cmd.ExecuteReader( CommandBehavior.SequentialAccess ); // Read each author and insert into the list while ( Dr.Read() ) { Author Au = new Author(Host, Instance); Au.Id = Dr.GetString(0); Sl.Add(Au.Id, Au); } // return the final list return Sl; } // make sure that the connection resources are freed even if // there is a problem. finally { if ( Cmd != null ) Cmd.Dispose(); if ( Conn != null ) Conn.Dispose(); }

} // Utility function to connect to SQL Server HOST\INSTANCE protected SqlConnection GetConnection() { SqlConnection Conn = null; StringBuilder Sb = new StringBuilder(); try

120 Chapter 5: Classes and Components

Listing 5.1
69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: } {

continued
// build the connection string if ( Instance.Length > 0 ) Sb.AppendFormat(SERVER={0}\\{1};UID=sa, Host, Instance); else Sb.AppendFormat(SERVER={0};Trusted_Connection=Yes, Host ); // connect to the database Conn = new SqlConnection( Sb.ToString() ); Conn.Open(); Conn.ChangeDatabase(pubs); return Conn; } catch { if ( Conn != null ) { Conn.Close(); Conn.Dispose(); } // pass the exception up the chain; just re-throw it throw; }

User code constructs an AuthorsListBuilder class by using the constructor (lines 14 to 18) to designate the hostname and instance name of the database from which to retrieve data. You could also implement a username and password, but for simplicitys sake the protected GetConnection() method (lines 65 to 97) works with the default installation configuration, which does not set a password for the sa (administrator) login. GetConnection() implements the connection and error-handling code necessary for connecting to the database and returns a connected System.Data.SqlClient. SqlConnection instance. The method is protected because it is not part of the public interface. Aside from the constructor, the only public interface method is the GetAuthors() method in lines 20 to 60. Line 32 uses GetConnection() to retrieve a connection to the database. Lines 35 and 36 create a SqlCommand to SELECT the author ids from the database, and then line 37 executes the command, receiving a DataReader connected to the resulting list of ids. With this data set, lines 40 to 45 can iterate on the DataReader, creating Author objects and inserting them into the SortedList instance that returns to

Properties 121

the caller. When the code returns the list, or if an exception is thrown, the finally block on lines 52 to 59 clean up the SqlCommand and SqlConnection objects by calling their Dispose() methods. Listing 5.2 demonstrates using an AuthorsListBuilder object. Listing 5.2 User Code Can Easily Retrieve the Authors from SQL Server

AuthorsListBuilder AL = new AuthorsListBuilder((local), ); SortedList SL = AL.GetAuthors(); foreach ( Author a in SL.Values ) { Console.WriteLine(\n\nAuthor id {0}., a.Id ); Console.WriteLine( SSAN: {0}, Name: {1} {2}, a.Id, a.FirstName, a.LastName ); }

Object operations from your OO design are most typically implemented by using methods in C#. In Listing 5.2, Ive used a builder class to factor out the metaoperation of creating a list of Authors. I could also have chosen to implement a class operation (static method) in an Author class, but didnt. Your choice will depend on your own method and standards.

Properties
A property in C# implements a construct that is partway between a field and a method. To client code, it appears to be a read-only or read/write field; however, within the class, it is a named identifier containing a get method for read-only properties or a get and set method for read/write properties. To demonstrate, Ill use properties to implement the public data fields of the Author class. When implementing database-backed classes, you often find that when client code is making use of the class, it refers only to part of any particular instance. For example, when using a list of Author objects, you might display a list of author names from which the user could select an author and see the titles that author worked on. For the majority of Author instances, which the user didnt select, loading the titles associated with the author would be a complete waste of time. In C#, a property is an elegant mechanism for handling this situation. The remaining listings in this chapter illustrate using properties in an Author class that incorporates a delay-load mechanism, not loading information from the database unless client code actually asks for it. Listing 5.3 shows the first part of the Author object, its state declarations, and constructor. Listing 5.3 The Author Class Begins with Internal State Data and a Constructor to Initialize It
1: public class Author 2: { 3: // database connection information 4: protected string Host;

122 Chapter 5: Classes and Components

Listing 5.3
5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28:

continued

protected string Instance; // author protected protected protected protected base information string Au_id; string FName; string LName; bool Contract;

// // // //

the authors SSAN author first name author last name true = on contract

protected DataSet TitlesSet;

// the titles the author wrote

// states protected bool BaseInfoLoaded; // has base info been loaded protected bool BaseInfoDirty; // has base info been changed protected bool TitlesSetLoaded; // have titles been loaded public Author(string DbHost, string DbInstance ) { Au_id = ; TitlesSetLoaded = false; BaseInfoLoaded = false; Host = DbHost; Instance = DbInstance; }

Lines 4 and 5 declare a host and instance name, as in the AuthorsListBuilder class; lines 8 to 11 declare the core state variables for the Author; and lines 16 to 18 declare variables to record when the class actually populates the state or modifies it. The constructor initializes the variables that are important to the code.

TIP
Listings 5.3 and 5.4 demonstrate using direct persistence code in the class to persist its information. Passing in a DataSet instead is another option. This is a more complicated approach so I didnt use it here, but it opens up other options, such as reading and writing an XML stream instead of to a database.

The next set of code, Listing 5.4, implements loading the state and title data from the database by providing the LoadBaseInfo() and LoadTitles() methods with the GetConnection() required to support them. Listing 5.4
1: 2: 3: 4: Author

Loads State Information in Protected Members

protected void LoadBaseInfo() { if ( Au_id != ) {

Properties 123

Listing 5.4
5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51:

continued
SqlConnection Conn = null; SqlCommand Cmd = null; SqlDataReader Dr = null; try { Conn = GetConnection(); // retrieve basic information Cmd = Conn.CreateCommand(); Cmd.CommandText = SELECT au_fname, au_lname, contract + FROM authors + WHERE au_id = + Au_id + ; Dr = Cmd.ExecuteReader( CommandBehavior.SequentialAccess ); // get the information out if ( Dr.Read() ) { FName = Dr.GetString(0); LName = Dr.GetString(1); IsContract = Dr.GetBoolean(2); BaseInfoLoaded = true; } } catch { // if an error, were not loaded BaseInfoLoaded = false; throw; } finally { if ( Cmd != null ) { Cmd.Dispose(); } if ( Conn != null ) { Conn.Dispose(); } } } else throw new ArgumentException(

124 Chapter 5: Classes and Components

Listing 5.4
52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98:

continued
Property queried without setting identity., Id );

} protected void LoadTitles() { SqlConnection Conn = null; SqlDataAdapter SqlDa = null; String Select; if ( Au_id != ) { try { // connect to the database Conn = GetConnection(); Select = String.Format( SELECT t.title_id, title, type, pub_id, + price, advance, royalty, ytd_sales + FROM pubs..titles t + JOIN pubs..titleauthor ta + ON ta.title_id = t.title_id + WHERE ta.au_id = {0}, Au_id ); SqlDa = new SqlDataAdapter( Select, Conn ); TitlesSet = new DataSet(Titles); SqlDa.Fill(TitlesSet); Conn.Close(); Conn.Dispose(); TitlesSetLoaded = true; } catch { // if theres an exception, then were not loaded TitlesSetLoaded = false; // let go of the connection if ( Conn != null ) { Conn.Dispose(); } // pass the exception up the chain; just re-throw it throw;

Properties 125

Listing 5.4
99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141:

continued
} } else throw new ArgumentException( Property queried without setting identity., Id );

} protected SqlConnection GetConnection() { SqlConnection Conn = null; StringBuilder Sb = new StringBuilder(); try { // build the connection string if ( Instance.Length > 0 ) Sb.AppendFormat(SERVER={0}\\{1};UID=sa, Host, Instance); else Sb.AppendFormat(SERVER={0};Trusted_Connection=Yes, Host ); // connect to the database Conn = new SqlConnection( Sb.ToString() ); Conn.Open(); Conn.ChangeDatabase(pubs); return Conn; } catch { if ( Conn != null ) { Conn.Close(); Conn.Dispose(); } // pass the exception up the chain; just re-throw it throw; } }

The LoadBaseInfo() method (lines 1 to 54) loads the state info into the Author object. The LoadTitles() method, on the other hand, uses a SqlDataAdapter to bind a DataSet to the titles table entries that correspond to publications the author has

126 Chapter 5: Classes and Components

written. By using the DataSet, the Author class enables client objects to access any column in the titles entries; this contrasts with the Author object itself, in which clients have available only those columns to which the class provides access. With the code in Listings 5.3 and 5.4, the Author class can load both internal state and related titles. What remains is to provide access to the information to client code. Listing 5.5 implements properties that satisfy this need, invoking the appropriate loaders to retrieve information when its requested. Listing 5.5 Clients Access Author Information Through Properties, Loading Information from the Database as Necessary
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: public string FirstName { get { // if the base isnt loaded, load it. If the method // fails, it will throw out of this property. if ( !BaseInfoLoaded ) LoadBaseInfo(); // otherwise, we have information to return. return FName; } set { BaseInfoDirty = true; FName = value; } } public String LastName { get { // if the base isnt loaded, load it. If the method // fails, it will throw out of this property. if ( !BaseInfoLoaded ) LoadBaseInfo(); // otherwise, we have information to return. return LName; } set { BaseInfoDirty = true; LName = value; } }

Properties 127

Listing 5.5
39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: }

continued

public bool IsContract { get { // if the base isnt loaded, load it. If the method // fails, it will throw out of this property. if ( !BaseInfoLoaded ) LoadBaseInfo(); // otherwise, we have information to return. return Contract; } set { BaseInfoDirty = true; Contract = value; } } public string Id { get { return Au_id; } set { if ( Au_id != value ) { Au_id = value; TitlesSet = null; } } } public DataSet Titles { // only provide a getter for this property; set // would not be sensible. get { if ( !TitlesSetLoaded ) LoadTitles(); return TitlesSet; } }

128 Chapter 5: Classes and Components

Listing 5.5 implements a series of properties: FirstName, LastName, IsContract, and IsContract. Each is of an appropriate type for the underlying state data and checks the BaseInfoLoaded field. If the data hasnt been loaded, the property invokes the loader function. It then returns the value that the client requested. The Titles property does the same thing for the DataSet instance TitlesSet. By using properties, you can provide access to member data in your classes, but retain complete control over the method in which they are used. This has implications beyond maintaining consistency in the classs state, and it further enables you to elegantly implement such features as the delayed loading structure demonstrated in the Author class.

Namespaces
In C#, symbols such as method and variable declarations are confined to their enclosing scope, whether it is a class, method, property, or statement block of some other kind. In your programs, however, and particularly in libraries such as the .NET framework itself, a mechanism with a broader reach is necessary to group entire collections of related types. The mechanism delivered by C# is the namespace. A namespace is a conceptual grouping of type declarations that is imported into your code via the using statement. System, System.Data, and System.Xml are all namespaces included in the .NET framework. You can create your own namespaces by using the namespace keyword. Listing 5.6 creates an Authors namespace to hold the Author and AuthorsListBuilder classes. Listing 5.6 The namespace Keyword Establishes Units of Code You Can Then Import with the using Keyword
1: namespace Authors 2: { 3: public class Author 4: { 5: . 6: . Implementation code goes here 7: . 8: } 9: 10: public class AuthorsListBuilder 11: { 12: . 13: . Implementation code goes here 14: . 15: } 16: }

The namespace implemented in Listing 5.6 establishes a scope for the Authors library classes. The classes can then be used as shown in Listing 5.7.

Namespaces 129

Listing 5.7 Client Code Imports the Authors Namespace to Use Its Declarations Easily
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: using using using using System; Authors; System.Data; System.Collections;

class AuthorUserMain { static void Main(string[] args) { double f = 0.0; string s = this is a string value.; try { AuthorsListBuilder AL = new AuthorsListBuilder((local), ); SortedList SL = AL.GetAuthors(); foreach ( Author a in SL.Values ) { Console.WriteLine(\n\nRetrieving author id {0}., a.Id ); Console.WriteLine( SSAN: {0}, Name: {1} {2}, a.Id, a.FirstName, a.LastName ); foreach ( DataTable Dt in a.Titles.Tables ) { foreach( DataRow Dr in Dt.Rows ) { Console.WriteLine(Title: + Dr[title]); } } } } catch ( System.Data.SqlClient.SqlException se ) { Console.WriteLine( se.ToString() ); } } }

After the code is compiled into an assembly, the assembly must either be explicitly referenced by the metadata in your program or installed into the global assembly cache. The code in Listing 5.7 is compiled into an executable that references Authors.dll, the assembly that contains the Authors code. With the using statement at line 2, the client code imports the types in the Authors namespace for use in the AuthorUserMain class. Its then free to use the AnchorsListBuilder at line 15 and the Author line 18 and in the enclosed code.

130 Chapter 5: Classes and Components

TIP
Namespaces are not restricted to a single source file; you can create multiple classes in multiple files and then compile them into an assembly, as your code management needs dictate.

Summary
The class, the basis of object-oriented programming, is well supported by C#. In this chapter youve seen the extensive set of features in C# for developing classes, and how to organize them by using namespaces to group related declarations. Along the way, the examples have also demonstrated optimizing a database-aware type by delaying loading information from the database until its needed. In Chapter 6, Memory Management and C#, youre going to take a look into the details of how your classes are manipulated, stored, and recovered in the .NET Framework

Memory Management and C#

CHAPTER 6
Memory Management and C#
A major objective of the .NET platform is to make programming easier, more productive, and the resulting products more reliable. Although traditionalists have forcefully asserted that memory management can be made reliable through safe programming and standards, the cost is high. In most development efforts I have led or participated in, memory management has been a major challenge in debugging and reliability efforts. In one C++ project, roughly 40% of the coding effort involved reliability, principally eliminating memory leaks. To help battle these costs, the .NET execution engine supplies a managed memory facility, including an asynchronous garbage collector. Although unmanaged (unsafe) code still requires you to clean up after yourself, managed code can safely ignore the majority of resource issues. Only in the case of objects that utilize resources that are limited, costly (such as file handles or data connections), or unmanaged, do you need to worry about explicitly freeing resources. The managed garbage collector (GC) can generally provide a much greater degree of reliability with dramatically less effort, but it is by no means a panacea. As you will see, in specific cases, understanding how it works is necessary, and you must take specific steps to ensure correct program operation.

Memory Management in the .NET Framework


Objects, instances of types, are created on the managed heap by using the new statement. The memory manager keeps the memory used by all current objects in a contiguous block at the

132 Chapter 6: Memory Management and C#

base of the heap, so satisfying a request for a new object requires only allocating the space immediately following the last allocated block of memory. Simply allocating objects in the next available location in memory is easy and very fast, but an end existssooner or later you run out of space. In addition, as your program executes, objects that were previously allocated go out of scope and are no longer needed, leaving holes of unused memory. When the program hits the end of memory, therefore, the GC comes into play, compacting active objects back down to contiguous lower memory. The code in Listing 6.1 describes how memory allocation works. Listing 6.1
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:

Memory Management Gets Complicated Quickly

class MyObj { public void MyMethod() { String [] AnArray = new String[50]; // do some meaningful work here } } . . MyObj m = new MyObj(); m.MyMethod(); MyObj [] MyArray = new MyObj[10]; for ( int i = 0; i < 10; i++ ) MyArray[i] = new MyObj(); m = MyArray[5];

The code at line 11 allocates an instance of a MyObj type at the current bottom of free space on the heap. When line 12 calls into MyMethod(), line 5 allocates an array and 50 String objects immediately after the MyObj in memory. During MyMethod()s execution, the heap is arranged as shown in Figure 6.1. When MyMethod() returns, the array and the strings it contains are orphaned; the reference to it, allocated in the stack frame for the method call, simply goes away, leaving the array no longer reachable from any code. Line 18 causes the same thing to occur to the initial instance of MyObj, by overwriting the reference to it that was stored in the variable m, creating the situation in Figure 6.2. After line 18 executes, the root references remaining are MyArray and m. No references exist to the original MyObj instance, the string array, or any of the strings it contained. Its completely impossible to use these objects any more, so they can be destroyed to free up space.

Memory Management in the .NET Framework 133

Figure 6.1 Objects grow up on the heap as they are allocated.

Figure 6.2 Holes in the heap develop over time.

A garbage collection at this point will begin by building a reference graph starting with each root; because every type carries metadata with it, the collector can add the root object to the list and then add each object the root references, recursing to add all objects that could still be reached. When this operation is complete, the collector knows every object that can still be used by the application; all others will be destroyed in the next step, when the collector walks the heap, compacting objects down in memory. Unreachable objects low in the heap are overwritten by objects from higher in memory that are still in play, so that all objects still alive are again contiguous in memory at that bottom of the heap (see Figure 6.3).

134 Chapter 6: Memory Management and C#

Figure 6.3 Holes in the heap develop over time.

For simple objects, the process Ive described is sufficient. Unfortunately, its never that simple. Objects can allocate references to resources that are scarce, as Ive mentioned before, and code can manipulate data that the GC doesnt know about through various means. To handle the first case, the runtime provides the IDisposable interface, and for the latter, a pattern called a finalizer. Together, these two features help solve the problems of scarce and unmanaged resources.

IDisposable
Objects implement IDisposable to enable freeing complex or scarce resources in a deterministic fashion, instead of waiting for that unknown time in the future when the GC finally gets around to deallocating it. Implementing IDisposable requires you to provide a single public method with the following signature:
public void Dispose();

This method should free any resources, including simple state data, if applicable. Listing 6.2 demonstrates implementing IDisposable. Listing 6.2
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: using using using using

Use

IDisposable

to Enable Freeing Scarce Resources

System; System.Data; System.Data.SqlClient; System.Text;

namespace GCDisposeFinalize { class MyAuthor : IDisposable { String NameVal = ; SqlConnection Conn = null; String ConnStr = ; bool Disposed = false;

Memory Management in the .NET Framework 135

Listing 6.2
14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60:

continued
public void Dispose() { if ( !Disposed && Conn != null ) { // Conn.Close() calls Conn.Dispose() Conn.Close(); Conn = null; ConnStr = ; } else throw new ObjectDisposedException(MyAuthor, Disposed more than once. ); } public MyAuthor( String MyCStr ) { ConnStr = MyCStr; } private void GetData(int au_id) { // use to interact with the database SqlCommand cmd = null; SqlDataReader dr = null; // connect to the database if ( Conn == null ) { Conn = new SqlConnection(ConnStr); Conn.Open(); } // retrieve the author information StringBuilder sb = new StringBuilder(); sb.Append(SELECT au_fname + + au_lname); sb.AppendFormat( FROM authors where au_id like {0}%, au_id); cmd = Conn.CreateCommand(); cmd.CommandText = sb.ToString(); dr = cmd.ExecuteReader( CommandBehavior.SequentialAccess ); // Get the name and keep it. if ( dr.Read() ) {

136 Chapter 6: Memory Management and C#

Listing 6.2
61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107:

continued
NameVal = dr.GetString(0); } dr.Close(); cmd.Dispose(); } public void Find( int au_id ) { if ( !Disposed ) { GetData(au_id); } else throw new ObjectDisposedException( MyAuthor, Disposed before Find() ); } public string Name { get { if ( !Disposed ) return NameVal; else throw new ObjectDisposedException( MyAuthor, Disposed before Name Access ); } }

} // primary thread class MainClass { const string ConnStr = SERVER=(local)\\NETSDK;DATABASE=pubs;UID=sa; static void Main(string[] args) { MyAuthor ma = new MyAuthor(ConnStr); for ( int i = 1; i < 10; i++ ) { ma.Find(i); // got one, write the name Console.WriteLine({0}s id starts with {1}.,ma.Name,i);

Memory Management in the .NET Framework 137

Listing 6.2
108: 109: 110: 111: 112: 113: }

continued
} ma.Dispose(); }

The class MyAuthor provides very simple access to the authors in the pubs databases authors table. It uses a very common optimization technique, maintaining a continuous connection to the database over multiple calls, in the member variable Conn. Clients of the type can use the Find() method to search for an author (line 68) and the Name property to get the name of the author found (line 79). The resource issue arises in the GetData() method, lines 34 to 66. Until the user requests a record by using the Find() method, nothing expensive happens. But when Find() calls GetData() to retrieve information from the database, the method allocates a SqlConnection (lines 43 and 44). To avoid connecting repeatedly to the database, this connection is not closed during the lifetime of the object. For the example, which allocates one object and reuses it, this produces much faster execution than the alternative. However, it also means that the database connection might persist long after the object passes out of scope, because it wont be reclaimed until the next garbage collection. To resolve this problem, lines 15 to 27 implement the IDisposable.Dispose() method to enable clients to signal explicitly when the object will no longer be necessary. This method, invoked by the user code at line 110, releases the connection immediately by calling Conn.Close(). The MyAuthor instance now occupies only the storage required by it and its constituent members; although it and the database connection object remain in memory until collected, the expensive resource (the actual connection to the database) no longer exists.

NOTE
is a commonly implemented method in types like network connections and files; by convention, Close() has the same semantic implication as Dispose(). As the comment (line 19) notes, the Close() call behaves as if it also called the connections Dispose(), although it is not required to do so internally.
Close()

After an object has been disposed, it is an error to attempt to use that object again. Therefore, MyAuthor maintains a Disposed field that the Dispose() method sets to true. All public methods check this variable and throw an exception if the object is manipulated after being disposed.

Finalizers
For types that manipulate unmanaged data, .NET includes the concept of a finalizer, a method that is called when the object is about to be garbage collected. In other

138 Chapter 6: Memory Management and C#

languages, such as Visual Basic .NET, programmers have to implement a special method called Finalize to create a finalizer for an object. With C# on .NET, you implement a finalizer using C#s destructor syntax, as in Listing 6.3. This program allocates operating system resources using unsafe code and uses a destructor to clean up if the objects Dispose() method isnt called. Listing 6.3 Use a Combination of Dispose() and Finalization for Safe and Unsafe Resources
1: class BinaryFileReader : IDisposable 2: { 3: // Requires file to exist on open 4: const uint OpenExisting = 3; 5: 6: // Requests read access 7: const uint GenericRead = 0x80000000; 8: 9: // returned on failure to open a resource 10: const uint InvalidHandleValue = 0xFFFFFFFF; 11: 12: // Win32 Kernel functions 13: [DllImport(kernel32, SetLastError=false)] 14: static extern unsafe uint CreateFile 15: ( 16: string lpFileName, 17: uint dwDesiredAccess, 18: uint dwShareMode, 19: uint lpSecurityAttributes, 20: uint dwCreationDisposition, 21: uint dwFlagsAndAttributes, 22: uint hTemplateFile 23: ); 24: 25: [DllImport(kernel32, SetLastError=false)] 26: static extern unsafe bool CloseHandle 27: ( 28: uint hFile 29: ); 30: 31: [DllImport(kernel32, SetLastError=false)] 32: static extern unsafe bool GetFileSizeEx 33: ( 34: uint hFile, 35: ulong* lpFileSizeHigh 36: ); 37: 38: [DllImport(kernel32, SetLastError=false)] 39: static extern unsafe bool ReadFile 40: (

Memory Management in the .NET Framework 139

Listing 6.3
41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87:

continued
uint hFile, void* lpBuffer, uint nBytesToRead, uint* nBytesRead, uint overlapped );

unsafe byte [] Buffer = null; ulong BufferSize = 0; uint CurrentFileHandle = 0; // public functions for using data // Get size of data read public ulong Length { get { return BufferSize; } } // Indexer for safe access to buffer public int this[ulong index] { get { if ( index >= 0 && index < Length ) return Buffer[index]; else throw new IndexOutOfRangeException( Accessing BinaryFileReader[]); } } // Loads data into memory unsafe public void ReadData( string path ) { uint FileHandle = CreateFile( path, GenericRead, 0, 0, OpenExisting, 0, 0 ); if ( FileHandle != InvalidHandleValue ) { ulong NewBufferSize = 0; uint BytesRead = 0; // hold on to the handle so I can use it in other // operations CurrentFileHandle = FileHandle;

140 Chapter 6: Memory Management and C#

Listing 6.3
88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134:

continued
if ( GetFileSizeEx(FileHandle, &NewBufferSize) ) { if ( BufferSize < NewBufferSize ) { BufferSize = NewBufferSize; Buffer = new byte[BufferSize]; } fixed ( void * BufferPtr = Buffer ) if ( !ReadFile(FileHandle, BufferPtr, (uint)BufferSize, &BytesRead, 0) || BytesRead != BufferSize) { throw new IOException( Error reading file.); } } else throw new IOException( Access error reading file size.); } else throw new FileNotFoundException( In BinaryFileReader.ReadData(), path);

} // am I disposed? bool Disposed = false; // Releases current file unsafe private void CloseFile() { if ( CurrentFileHandle != 0 && CurrentFileHandle != InvalidHandleValue ) { CloseHandle( CurrentFileHandle ); CurrentFileHandle = 0; } } // Managed release public void Dispose()

Memory Management in the .NET Framework 141

Listing 6.3
135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: } {

continued
if ( !Disposed ) { // release the file handle CloseFile(); // release reference Buffer = null; // keep me from being finalized GC.SuppressFinalize(this); } else throw new ObjectDisposedException(BinaryFileReader, Disposed more than once. );

} // Finalize ~BinaryFileReader() { if ( CurrentFileHandle != 0 ) CloseFile(); }

Although the C# language itself imposes no restriction on the content or behavior of a finalizer/destructor, .NET itself does. You must remember that a finalizer is intended only to dispose of unmanaged resources. Managed objects, such as the SqlConnection used to demonstrate the IDisposable interface, must not be disposed of in a destructor. The GC makes absolutely no promises about the order in which objects will be collectedan objects destructor is called just before its own object is collected, but other managed instances referenced by the object might have already been collected. Therefore, calling any method, including Dispose(), can crash your program with a null reference exception. The program in Listing 6.3 uses the DllImport attribute with static methods to access Win32 functions in KERNEL32.DLL. Lines 4 through 10 declare constants the program uses in calling the file and handle functions; the values for these constants come from the declarations in WINNT.H. Lines 12 through 46 declare prototypes for the functions the program will use to interact with the native API. The rest of the code is straightforward for anyone familiar with Win32 programming; most of the action happens in the ReadData() function, which reads the contents of a file into a memory buffer, retaining the open file handle. When the object is no longer needed, one of two things can happen: either the client using the object calls the Dispose() method to release resources, or the

142 Chapter 6: Memory Management and C# ~BinaryFileReader()

finalizer is invoked at garbage collection. The preferable method is for the client to call Dispose() and free the resource when it isnt needed any longer. This method calls CloseFile() to close the file handle at line 139 and then releases the reference to the buffer on line 142.

Of particular interest is the call to GC.SuppressFinalize() on line 145. Within the garbage collection process, objects with finalizers arent immediately destroyed in the garbage collection process; the objects are placed into a finalization queue, and a separate thread in the CLR runs the finalizers on the objects in this queue. The objects are subsequently collected after finalization. After the Dispose() method has released the unmanaged resources, though, it is duplicative to call the finalizer. To avoid the additional overhead, a Dispose() method can call GC.SuppressFinalize() to prohibit finalization. The pattern demonstrated in Listing 6.3 is generally the one to follow. Declare a method to free any unmanaged resources (the CloseFile() method in Listing 6.3), and then declare a Dispose() that frees all resources and a finalizer to free only unmanaged items.

TIP
A practice you should avoid in your programs is that of resurrection. When an object is finalized, the code in the finalizer can cause a new reference to the object to be created if any action it takes results in a reference to the object being stored elsewhere; in this situation, the GC will not collect the object in subsequent garbage collections (now the object is reachable). However, the objects finalizers have run, so it might not be in a predictable state. Because resurrection has the potential to create unexpected results, its generally better to avoid it.

Using Weak References


Often a dilemma arises when you want to keep an object around thats a memory hog, but a possibility exists that youll need that memory for something else before you use the object again. For whatever reasonsuppose that the object is costly to construct you dont want to go through reconstructing it when you come back to it, but you still need space in the meantime. The .NET framework provides the concept of weak references to deal with this problem. A weak reference enables you to keep hold of an object, but it allows the GC to collect the memory referred to if necessary. You create a weak reference to the object and then destroy all other references to it. If a garbage collection occurs, the object will be collected; if not, you can retrieve a strong reference to the object by using the Target property of the WeakReference class. Listing 6.4 shows how to use WeakReference. Listing 6.4 Weak References Enable the Greatest Flexibility for Memory Management
1: BinaryFileReader Reader = new BinaryFileReader(); 2: .

Using Memory in C# 143

Listing 6.4
3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21:

continued

// work with the object . WeakReference w = new WeakReference( Reader ); Reader = null; . // other unrelated processing; the object may be collected here . if ( w.IsAlive ) { Reader = (BinaryFileReader)w.Target; } else { // reconstruct the object Reader = new BinaryFileReader(); Reader.ReadData( FileName ); } . // use the object again

Line 1 of Listing 6.4 allocates a BinaryFileReader that the code could then use to work with a file. Line 5 creates a weak reference to the object, and then line 6 destroys the strong reference in the Reader variable by assigning a null to it. The program can then go on to do other things, as indicated by the comment on line 8. A garbage collection occurring at this point would cause the object to be finalized and its memory collected. At line 10, the object might or might not still exist; the code therefore queries the WeakReferences IsAlive property to discover if the object is still around. If it is, line 12 retrieves the object, reestablishing the strong reference to it; otherwise, lines 17 and 18 re-create the object. The program can then go on to use the object again. By using the weak reference, the program is able to reclaim the memory if it needs it, but if collection isnt necessary, the program can return to using the object without the cost of re-creating it. As Ive demonstrated it, the WeakReference class accomplishes nothing more; it is whats known as a short weak reference. You can also instruct the WeakReference class to track resurrections by passing a second parameter, a Boolean true, to the WeakReference constructor. This establishes a long weak reference; if the objects finalizer resurrects the object, the WeakReference object is informed and will know of the resurrected object. However, because of the drawbacks of resurrection in general, using long weak references is deprecated.

Using Memory in C#
Even in the managed environment, considerations are involved in using memory. The .NET framework supports both managed and unmanaged references, leading to complex operation. Although simple code can get by with blind faith in the GC, more advanced applications will have to be built taking it into account.

144 Chapter 6: Memory Management and C#

The fixed and using Statements


Two statements in C# help manage how you use resources. Managed references are variables that you declare in your code and associate to objects by using the new keyword. The runtime manages these objects and the underlying references to them, updating pointers as necessary during garbage collection. As you saw in Listing 6.3, however, you can use the fixed statement to temporarily pin an object in memory to keep the GC from moving it:
fixed ( void * BufferPtr = Buffer ) if ( !ReadFile(FileHandle, BufferPtr, (uint)BufferSize, &BytesRead, 0) || BytesRead != BufferSize) { throw new IOException( Error reading file.); }

The fixed statement declares a pointer that points to the object in question, but only during the statements execution. This statement is intended to enable you to use memory objects while accessing the platform API, or to implement algorithms best coded using pointers. The fixed statement keeps the pinning isolated in scope because enabling globally fixed objects would circumvent the GC and impact performance. Although you can get around this, you should minimize your use of this feature. Another statement useful in managing resources is the using statement. Objects that acquire unmanaged resources should implement the IDisposable interface to release those resources; in C#, the using statement automates calling the Dispose() method when your code is finished with the object. For example, the following code makes use of the BinaryFileReader to read a file:
using ( BinaryFileReader Reader = new BinaryFileReader() ) { try { // FileName is a string var holding the path to the file to read Reader.ReadData( FileName ); // do something useful with the data } catch (Exception e) { // handle any errors... } }

Summary 145

The using statement takes in its parameter list one or more object allocation statements (multiple allocations are separated by commas). The code within the using statements code block can manipulate those objects; then, on exit from the statement, the objects Dispose() method is invoked. This technique can help you avoid resource leakage caused by forgetting to call Dispose().

Effective Memory Management


Although the memory management and garbage collection are quite efficient in the .NET platform, you can still take steps to improve the way your program works with it. The GCs performance is affected by fragmentation, so although your programs design is driven by several other competing factors, you should consider this fragmentation if performance is a concern. To start with, you should minimize your use of fixed and unmanaged resources, making use of these features only when other options wont suffice. Although Ive chosen to use the task of working with a file as an example, the .NET framework class libraries have extensive support for I/O and other platform featuresusing these classes will optimize your programs interaction with the outside world. In designing your program, its wiseboth for maintainability and for performance to use as shallow an object graph as you can. During operation, a program will begin with its main class and any static objects declared by it or libraries it uses. It will create other objects during operation, which in turn will instantiate others. Each additional reference must be maintained by the memory manager, so keeping the resulting object tree simple will always improve performance. Finally, if your program expects to be allocating and using a large number of objects, it can help to allocate them in larger blocks ahead of time. By doing so, you again minimize the number of references that the collector needs to manage, because the objects will be compacted a minimum number of times. Similarly, if you surrender a large volume of data in a particular area of your code, it can help to initiate a garbage collection by using the GC.Collect() method.

Summary
Managed execution is the source of much of the reliability and productivity Microsoft expects from programs on .NET. The memory management features of the CLR are important to achieving .NETs objectives. Although garbage-collected memory can have performance implications in extreme circumstances, performance wont usually be an issue, and your programs will be easier to create and maintain. Next, Chapter 7 delves further into the details of how your program runs, by examining flow of control and execution techniques.

Advanced Program Control

CHAPTER 7
Advanced Program Control
Few modern programs can afford to perform only one action at a time throughout the application. Being able to move your program along multiple lines of execution has become important to desktop programs and indispensable for server programs. This chapter demonstrates techniques for multithreaded programming and for using delegates and events to dispatch single notifications and multicast updates. The segment of processing in Win32 programming has traditionally been the process. Within a process, multiple execution paths could exist by executing multiple threadseach with its own register state, execution priority, and instruction pointer. Processes were prohibited from interfering directly with each other by using the processors virtual memory manager. Although processes and threads still exist in the .NET environment, the application boundary for managed code is an application domain. The CLR enforces isolation between application domains, enabling multiple, isolated applications to exist within one process. In normal program execution, the application domain containing a program is started on an initial thread, and the application can create and destroy further threads as needed during its lifetime. A pool of additional threads is maintained to allocate execution to asynchronous contexts that you invoke using timers, asynchronous I/O, and other asynchronous facilities of the runtime.

Threads
Lets start with the basics. To start a new thread in your program, youll create an instance of the ThreadStart delegate

148 Chapter 7: Advanced Program Control

that points to the method in which the runtime will start executing a new thread. You then create a Thread instance using that delegate to initialize it, and then call its Start() method to start it running. Listing 7.1 demonstrates these steps. Listing 7.1
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43:

Simple Demonstration of Threading

using System; using System.Threading; namespace cmdThreading { /// <summary> /// Command line program demonstrating simple threading /// </summary> class ThreadClass { // number of this thread int id; public ThreadClass( int i ) { id = i; } public void Start() { try { Random rnd = new Random( id ); int sleepTime = rnd.Next( 100, 200 ); Console.WriteLine( Thread {0} starting; sleep {1}., id, sleepTime ); Thread.Sleep( sleepTime ); Console.WriteLine( Thread {0} ending., id ); } catch { Console.WriteLine(Thread {0} ending in abort., id ); } } } class cmdThreading { public static void Main(string[] args) { Thread [] t = new Thread[10]; int i = 0;

Threads 149

Listing 7.1
44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: }

continued
// use separate loops since creating all the objects takes // time. for ( i = 0; i < t.Length; i++ ) { ThreadClass tc = new ThreadClass( i ); t[i] = new Thread( new ThreadStart( tc.Start ) ); t[i].IsBackground = true; } Console.WriteLine(Starting threads.); // now rapidly loop through and start the threads. for ( i = 0; i < t.Length; i++ ) t[i].Start(); Console.WriteLine(Finished starts.); t[0].Join(); Console.WriteLine(Exiting the application.); for ( i = 0; i < t.Length; i++ ) if ( t[i].IsAlive ) t[i].Abort(); }

Lines 9 to 36 of this example define a ThreadClass type that constructs with an identification number and defines a single method, Start(), that serves as the entry point to the class. Creating an independent class to encapsulate the functioning of a thread is a common pattern, and it enables you to think of the thread as a miniprogram within your application. Lines 38 to 69 contain the main class of the example, including the Main() function of the program. This function allocates an array of Thread to hold the threads it will create, on line 42. Lines 47 to 52 loop through each element in this array, creating a new instance of ThreadClass on line 49 and then the Thread instance on line 50. Line 50 creates the ThreadStart delegate and passes it to the new Thread in one step; you might notice that the code doesnt retain a reference to the delegate or the ThreadClass instances, but because the Thread refers to the ThreadStart, which in turn refers to the ThreadClass, theres no need for retention. Line 51 sets the threads IsBackground property to true. A background thread has a lower priority than a foreground threada useful attribute you can use to keep background processing from interfering with the responsiveness of your user interface.

150 Chapter 7: Advanced Program Control

Lines 57 and 58 loop through the thread array again, starting each of the threads in rapid succession by calling its Start() method. An important feature of this method is that it is an asynchronous call; it only requests that the thread be started. The CLR will usually start the thread some time after your request, but when that will be is not deterministic from an applications perspective. After the threads are started, line 62 waits for the first one to end. A thread dies when execution returns from its entry point (the method used to initialize its ThreadStart delegate). To make things interesting in the example, the ThreadClass.Start() method waits a random amount of time, based on the instances id, and then returns. The Thread.Join() method blocks until the thread exits, so line 62 stops the main threads execution until thread 0 completes. Lines 65 to 67 then loop through all the threads, calling Thread.Abort() to end any that are still alive; Abort() causes a ThreadAbortedException to throw in the target thread, so the catch statement starting on line 31 executes in the threads that are still alive, and then the thread exits. Illustrating the variability of the threads behavior in the CLR execution engine, the following output results from two runs of this program.
C:\>cmdthreading Starting threads. Finished starts. Thread 5 starting; sleep 133. Thread 7 starting; sleep 138. Thread 9 starting; sleep 142. Thread 4 starting; sleep 181. Thread 6 starting; sleep 186. Thread 8 starting; sleep 190. Thread 2 starting; sleep 177. Thread 0 starting; sleep 172. Thread 1 starting; sleep 124. Thread 3 starting; sleep 129. Thread 5 ending. Thread 7 ending. Thread 1 ending. Thread 9 ending. Exiting the application. Thread 3 ending. Thread 6 ending in abort. Thread 0 ending in abort. Thread 2 ending in abort. Thread 4 ending in abort. Thread 8 ending in abort. C:\>cmdthreading Starting threads. Finished starts. Thread 3 starting; sleep 129. Thread 2 starting; sleep 177.

Synchronization 151 Thread 1 starting; sleep 124. Thread 9 starting; sleep 142. Thread 7 starting; sleep 138. Thread 5 starting; sleep 133. Thread 4 starting; sleep 181. Thread 6 starting; sleep 186. Thread 0 starting; sleep 172. Thread 8 starting; sleep 190. Thread 3 ending. Thread 1 ending. Exiting the application. Thread 5 ending in abort. Thread 4 ending in abort. Thread 2 ending in abort. Thread 6 ending in abort. Thread 7 ending in abort. Thread 8 ending in abort. Thread 9 ending in abort. Thread 0 ending in abort.

In both runs of the program, the main thread gets through starting all the threads before any of them produce any output. In the first run, however, threads 5, 7, 1, and 9 complete their execution before the main thread starts killing them off; in the second run, only threads 3 and 1 complete. Its also interesting to note that even though the thread start requests were all issued in order, the order they actually start and run in is purely dependent on the runtime and the operating systems scheduler.

Synchronization
Usually it isnt acceptable to your program to have its execution based solely on the whim of the environment. The .NET framework delivers several methods to control synchronization. Youve already seen one such; the Thread.Join() method stops execution on one thread until another thread terminates. Table 7.1 lists the other synchronization constructs you can use. Table 7.1 Feature
lock Interlocked Monitor

.NET Synchronization Features Usage


Synchronize code section; uses Monitor. Synchronized increment, decrement, and comparison of a data item. Synchronize access to a code section; similar to Win32s critical section. Stop flag that a thread can block on; versions can be automatically or manually reset after threads waiting on the event are released.

statement type type

AutoResetEvent, ManualResetEvent

types

152 Chapter 7: Advanced Program Control

Table 7.1 Feature


Mutex

continued Usage
Similar to ManualResetEvent; can be named and used to synchronize execution across application and process boundaries (Mutex abbreviates mutually exclusive).

type

Listing 7.2 demonstrates the problem of synchronization using a modified version of the threading sample. Listing 7.2
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36:

Problem Code That Doesnt Synchronize Its Activities

using System; using System.Threading; namespace cmdThreading { /// <summary> /// Command line program demonstrating threading and synchronization /// </summary> class ThreadClass { cmdThreading Target; public ThreadClass( cmdThreading Tgt ) { Target = Tgt; } public void Start() { long Res = Target.Result; Res = Res + 1; Thread.Sleep(1); Target.Result = Res; } } class cmdThreading { public long Result = 0; public void RunThreads() { Thread [] t = new Thread[100]; int i = 0; // use separate loops since creating all the objects takes

Synchronization 153

Listing 7.2
37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: }

continued
// time. for ( i = 0; i < t.Length; i++ ) { ThreadClass tc = new ThreadClass(this); t[i] = new Thread( new ThreadStart( tc.Start ) ); } // now rapidly loop through and start them. for ( i = 0; i < t.Length; i++ ) t[i].Start(); // wait til the threads finish for ( i = 0; i < t.Length; i++ ) t[i].Join(); Console.WriteLine(The result: {0}., Result ); } public static void Main(string[] args) { for ( int i = 0; i < 10; i++ ) { cmdThreading ct = new cmdThreading(); new Thread( new ThreadStart(ct.RunThreads)).Start(); } }

To make things interesting in the example, Ive modified the cmdThreading classs Main() function to create 10 copies of the object and run each independently on its own thread. This demonstrates several runs of the object instead of just one per execution. At line 29, the code declares a long variable called Result that is updated by each of the worker threads, in the worker threads ThreadClass.Start() method. Because there are 100 threads, each of which adds 1 to the result, you would expect intuitively that the result would be 100. As written in Listing 7.2, however, the results are as follows:
C:\>cmdthreading The result: 11. The result: 19. The result: 16. The result: 12. The result: 7. The result: 13. The result: 3. The result: 9.

154 Chapter 7: Advanced Program Control The result: 12. The result: 15.

If the code in the Start() method is able to complete within the threads initial time slice, the code will execute as expected; however, if it is preempted and a switch to another context occurs, the results become unpredictable. To demonstrate this, the worker code has a call to Sleep() at line 22, which gives the runtime the capability to give time to another thread. In most cases it does, producing the unpredictable output. One approach to arbitrating access to the result is to use a method to update the value and use the lock statement to synchronize the operation:
class ThreadClass { cmdThreading Target; . . . public void Start() { Target.UpdateResult(); } } class cmdThreading { public long Result = 0; public void UpdateResult() { lock(this) { long Res = Result; Res = Res + 1; Thread.Sleep(1); Result = Res; } } . . . }

The UpdateResults() function performs the same operations as the original code, but it surrounds the code with a lock() statement; lock() uses a Monitor to prohibit access by any other thread to the object passed in while the code within its statement executes. After execution passes out of the block, the object is unlocked and other threads can use the referenced object. This method also has the advantage of relocating the code that manipulates the objects state back into the object.

Synchronization 155

In cases where the utilization of an object is not synchronized within the object, such as the .NET library classes, you must pursue a different approach. A common device used is the Mutex, an optionally named type that you acquire before taking an action, and then release after you have completed the operation. Listing 7.3 demonstrates using a Mutex to synchronize access. Listing 7.3
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41:

Using a

Mutex

to Deconflict Access

class ThreadClass { . . public void Start() { Mutex mut = new Mutex(false, cmdThreading.MutexName); // acquire the mutex and do some work if ( mut.WaitOne() ) { long Res = Target.Result; Res = Res + 1; Thread.Sleep(1); Target.Result = Res; mut.ReleaseMutex(); } else throw( new ApplicationException( Couldnt acquire the mutex!) ); } } class cmdThreading { public static string MutexName = Result.Mutex.081701; . . public void RunThreads() { . . // creates the system object at first // if it doesnt exist Mutex mut = new Mutex(false, MutexName); . . (Create and execute the threads) . } .

156 Chapter 7: Advanced Program Control

Listing 7.3
42: . 43: }

continued

The first modification to the code is to add a string with the name to use for the Mutex. The string need not be meaningful, but it should be distinctive enough to avoid being duplicated. I have appended the date to the name declared in line 27. Line 36 creates the first instance of the Mutex in the system. In the worker code, line 7 declares another Mutex using the same name. Because a Mutex is a systemwide object, this variable refers to the same OS-level object as that of the parent thread. Line 10 calls Mutex.WaitOne() to acquire a lock on the Mutex; if the method returns true, the thread has exclusive control of the Mutex, and it completes the update. Line 17 releases the Mutex, but this isnt entirely necessary in this case. When the Mutex variable is garbage collected it will release automatically. Depending on this behavior is sloppy programming, however. For the simple case of incrementing or decrementing a variable in a thread-safe manner, you can use the System.Threading.Interlocked type:
public void Start() { Interlocked.Increment(ref Target.Result); }

The Increment() and Decrement() methods of Interlocked synchronize adding and subtracting 1 from a long value. Interlocked also provides the Exchange and CompareExchange methods, which perform the classic swap operation atomically.

Delegates
C and C++ have the capability to use the address of a function and to invoke the function through the pointer by casting it to the required type. This capability was necessary for a number of applications, most notably callbacks for asynchronous operations, or similar constructs that required anonymous callbacks. The following demonstrates using C++:
// FuncPtrs.cpp : Demonstrates using function pointers. #include <stdio.h> // Declare prototype for math function. typedef int(MathFunc)(int); // Function that computes result based on passed MathFunc void DoMath( MathFunc ptr, int theNumber ) { // calls the function that ptr points to int result = (*ptr)(theNumber);

Delegates 157 // do some other stuff here using the result; the // result is determined by client code that decided // which math function to pass in. } // Function to multiply by two int TimesTwo(int in) { return in * 2; } // Function to multiply by four int TimesFour(int in) { return in * 4; } // Invoke math function using two different math functions. void main(int argc, char * argv[]) { // Call the DoMath function, passing the addresses of the two functions DoMath(&TimesTwo, 150); DoMath(&TimesFour, 150); // same code does different calculation }

Like other applications of pointers, function pointers proved to be inaccessible to many beginning programmers, and even to skilled developers, function pointers were almost as easily misapplied as other types of pointers. Because the language supported no other alternative, we put up with them. C# does a lot of things better than previous languages do, and anonymous callbacks is one of them; the language includes the delegate to enable you to pass references to functions as parameters, as in Listing 7.4: Listing 7.4 Implementing Callbacks by Using Delegates

1: class MathDemo 2: { 3: // declare delegate type 4: public delegate int MathDelegate(int i); 5: 6: // computes result based on passed MathDelegate 7: public void DoMath( MathDelegate d, int i ) 8: { 9: int result = d(i); 10: 11: // do work with the result 12: } 13: 14: // Function to multiply by two 15: public int TimesTwo(int i) { return i * 2; } 16: 17: // Function to multiply by four 18: public int TimesFour(int i) { return i * 4; } 19: }

158 Chapter 7: Advanced Program Control

Listing 7.4

continued

20: 21: class Del 22: { 23: static void Main(string[] args) 24: { 25: MathDemo md = new MathDemo(); 26: 27: md.DoMath( new MathDemo.MathDelegate(md.TimesTwo), 150 ); 28: md.DoMath( new MathDemo.MathDelegate(md.TimesFour), 150 ); 29: } 30: }

Line 4 in Listing 7.4 declares the delegate MathDelegate. The delegate declaration resembles the header for a method, but conveys different information. The delegates return type and parameter list establish the return and parameter list that a function that will be called through the delegate must exhibit. The functions I use with the delegate, TimesTwo and TimesFour, lines 15 and 18, both return an int and take a single int parameter, i, as required by the delegate declaration. The identifier declared by the delegate, MathDelegate, is used as the type of delegates declared as fields, variables, properties, or parameters in your code. The declaration of the DoMath() method at line 7 uses this identifier for the type of the first parameter. The code in lines 27 and 28 uses it as the declared type of the new instance it passes into that function; in the latter cases, the type is qualified with the name of the enclosing type. After the program is passed into the DoMath() method, it can use the instance of the delegate as if it were a method to call, as in line 9. The first call from line 27 causes DoMath() to call TimesTwo(), and the second calls TimesFour(). By thus enabling you to pass methods as parameters, you gain all the capability of a function pointer, but in a completely type-safe manner. In addition, although a function pointer can interact only within the process space where the pointer is valid, a delegate is transparently remotable, as are most features of .NET. Delegates also support multicasting through composition, as demonstrated in Listing 7.5. Listing 7.5 Multicasting Using Delegate Composition

1: delegate void ClassifyDelegate( int i ); 2: 3: class Classifier 4: { 5: public ClassifyDelegate CountFuncs; 6: 7: public void Count( int i ) 8: { 9: CountFuncs(i); 10: }

Delegates 159

Listing 7.5
11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: }

continued

class Del { static int CountPositive = 0; static int CountTotal = 0; private static void CountPositiveCallback( int i ) { if ( i >= 0 ) CountPositive++; } private static void CountAllCallback( int i ) { CountTotal++; } static void Main(string[] args) { Classifier c = new Classifier(); c.CountFuncs += new ClassifyDelegate(CountPositiveCallback); c.CountFuncs += new ClassifyDelegate(CountAllCallback); c.Count(-3); c.Count(-2); c.Count(-1); c.Count(0); c.Count(1); c.Count(2); c.Count(3); c.Count(4); // count some numbers

Console.WriteLine(Positive, All: {0}, {1}, CountPositive, CountTotal ); } }

To use composition of delegates to multicast, you again declare an instance of the delegate type that will serve as the source of callbacks, as shown at line 5 in the Classifier class. For this demonstration, the class that hosts the delegate contains only the code necessary to invoke the delegates subscribers, the function Count() in lines 7 to 10. The Del class contains two functions, CountPositiveCallback() and CountAllCallback(), which increment counters when passed a positive integer or any

160 Chapter 7: Advanced Program Control

integer, respectively. At lines 33 and 34, the Main() method adds both functions to the same delegate. Lines 36 to 43 submit a series of numbers to the Count() function, and then line 45 writes out the result to the console. Each time the delegate is invoked, both counter functions are called, producing the following output:
C:\>del Positive, All: 5, 8 C:\>

You can also remove methods from a delegate by using the -= operator. Continuing from the previous example, the following code would work to remove the counter functions from the delegate:
c.CountFuncs -= new ClassifyDelegate(CountPositiveCallback); c.CountFuncs -= new ClassifyDelegate(CountAllCallback);

You can achieve similar functionality using interfaces to that which delegates provide; however, because of their different implementation features, interfaces and delegates are useful in different ways. Interfaces are useful to pass a reference to a type that should exhibit a set of related behaviors. Delegates provide a much simpler interface, but are very flexible and easy to use, especially if you need to use their composition feature for multicasting; you can also use delegation with static methods, which is impossible with interfaces.

Events
Events in C# extend the concept of delegates specifically to support the event pattern common in user interface and COM programming. Events can represent a mouse click or key press on a control, or system operations such as a timer expiring or a particular processing step completing. Just as delegates simplify the callback pattern, events in C# are easily applied in your code. Listing 7.6 shows how to use events in C#. Listing 7.6 Events Are Specialized Delegates Used for Providing Notification
1: delegate void SourceChangedEventHandler(Object source, EventArgs e); 2: 3: class EventSource 4: { 5: String StrVal; 6: public event SourceChangedEventHandler ChangedEvent; 7: public String Val 8: { 9: get { return StrVal; } 10: set 11: { 12: StrVal = value; 13: ChangedEvent( this, EventArgs.Empty );

Events 161

Listing 7.6
14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: }

continued

} } class Ev { private static void SourceChangedNotifier(Object sender, EventArgs e) { Console.WriteLine( The new value: {0}, ((EventSource)sender).Val ); } static void Main(string[] args) { EventSource es = new EventSource(); es.ChangedEvent += new SourceChangedEventHandler(SourceChangedNotifier); es.Val = This is a value for the string.; es.Val = This is another value.; } }

Listing 7.6 declares the EventSource class with a string property (lines 3 to 16), which raises a SourceChangedEventHandler event whenever the value changes. The Ev class declares the SourceChangedNotifier method in lines 19 to 23 to receive notification of changes to the EventSource variable instantiated at line 26. This handler method is connected to the changed event by the code at lines 27 and 28this uses syntax identical to that of delegates. The two lines following the assignment assign values to the string property of the object, which results in calling the notifier function twiceonce for each value assigned. Events differ from basic delegates in that they can be invoked only from within the type that declared the event. In addition, integrating with the .NET frameworks event model requires you to follow certain conventions. For example, the name of the delegate should end with EventHandler, and it should take two parameters, as demonstrated in Listing 7.6 on line 1. It is often advantageous to declare your own class to pass as the EventArgs parameter so that you can send information relevant to the event to handlers listening for it. You should always subclass the System.EventArgs type when creating new event parameter classes, as in the following:
class SourceChangedEventArgs { // provide public fields // strings that changed public String OldValue = public String NewValue = } : EventArgs to pass out the ; ;

162 Chapter 7: Advanced Program Control

This type extends EventArgs to provide the capability to report the old and new values when the string in the EventSource class changes. With this type declared, updating the property set code produces the following:
public String Val { get { return StrVal; } set { SourceChangedEventArgs Args = new SourceChangedEventArgs(); Args.OldValue = StrVal; StrVal = value; Args.NewValue = StrVal; ChangedEvent( this, Args ); } }

Finally, with the property accessor originating the custom event arguments type, the event handler can make use of the additional information:
private static void SourceChangedNotifier(Object source, EventArgs e) { Console.WriteLine( Old, New: <{0}>, <{1}>., ((SourceChangedEventArgs) e).OldValue, ((SourceChangedEventArgs) e).NewValue ); }

An event that you define can include two special accessors named add and remove, which are syntactically similar to accessor declarations for properties. This capability enables you to provide an alternate underlying storage for the delegates composed into the event than the compiler would normally provide. If you choose this approach, you must store references to the delegates passed to the accessors as valueyou can only use the event declaration to add or remove a delegate by using the composition operators (+= and -=). The .NET framework documentation supplies two implementation examples in its description of the C# event keyword, to resolve name conflicts and to implement broadly defined but sparsely used events.

TIP
The restriction on custom event declarations only being useful for composition is easier to understand if you recognize that it is similar to the difference between declaring an instance and declaring a type. When you declare an event without accessors, you define an instance variable that you can manipulate. Declaring an event with accessors, however, doesnt produce a variableit enables you to supply code implementing your preferred method of storage.

Summary 163

Summary
C# and .NET make multithreaded and event-driven programming easier than ever, by providing a rich model tailored to the needs of programmers building real code. You must remain aware of the issues involved in synchronization and performance, but by using the language features and library classes described in this chapter, youre well equipped to deal with them. Another unique C# feature that can make real world programming easier comes next. Chapter 8, Unsafe Code, describes using pointers to access memory directly.

Unsafe Code

CHAPTER 8
Unsafe Code
C# and Java are different in many ways, but one attribute they share is managed execution. By interpreting and verifying code at runtime, both environments attempt to shield programmers from a number of problems unmanaged environments present. However, eventually you need more control than the managed environment can handle, and where C# and Java diverge the most is in how you reach beyond the managed environment. In C#, you have unsafe code, which keeps the managed heap isolated but enables you to allocate memory on the stack and manipulate it with pointers. This arrangement is a shrewd solution that accommodates your needs but localizes the impact of errors.

Pointers
As you might have heard, pointers have long been a source of frustration for programmers in traditional C-style programming languages. Although not a feature in many languages, the majority of programming activity on Windows and Unix derivatives has been accomplished using C or C++, languages in which the pointer types feature prominently.

The Problem with Pointers


To understand what challenges pointers can present requires a little background. Most computers store information in randomaccess or read-only memory (RAM or ROM). Information refers to data that programs manipulate and to the code of the programs themselves. To organize the information in memory, the memory is usually broken up into 8-bit blocks called bytes, and then the bytes are assigned numbers, called addresses. The address of the first byte is 0, the next is 1, and so forth. Most computer architectures incorporate an additional level of organization called a word, which is the base data size with which

166 Chapter 8: Unsafe Code

the CPU works. For example, although you can manipulate single bytes in memory on the Intel x86 architecture, the processor itself works with two-, four-, or eight-byte values (16, 32, or 64 bits), depending on the CPU in question. The simplest definition of a pointer is a variable that stores a memory address and that program code uses to access the memory at that address. In isolation, that doesnt seem to present a problem, but a host of issues arise. For example, some processors instructions are keyed to a particular memory alignment; an instruction targeting a 32-bit value would produce an error or lose performance if it didnt use a pointer referencing an address evenly divisible by four (a 32-bit boundary). Another program might mismanage its addresses and write data into the wrong location, causing the program to crash. Its easy to get the wrong address in a pointerand harder than you would think to make sure that you dont. The preceding examples are common, serious problems, but the most common problem with pointers is lost resources. A limit exists to the amount of memory available to your program, so compilers provide runtime libraries that manage memory. As your program runs, it allocates areas of memory to hold information while it is processing it, and when it finishes using that area of memory, it is supposed to tell the runtime library that it is done using that space. Although this process seems simple when its described, programming errors often cause the memory manager to think that memory is still in use when it isnt. This situation, called a memory leak, is a common cause of program failure, as programs run out of memory and crash.

A Solution
Even with their warts, pointers have long been considered indispensable to hard-core programming, such as systems applications, DSP work, and so forth. Languages such as Java and C# deliver functionality to programmers by including extensive class libraries so that pointers become superfluous in general programming tasks. However, there is still a large body of existing code that new programs will need to make use of; therefore, your C# applications need a way to work with older techniques. Many existing APIs and libraries use pointers to pass information between modules, which keeps alive the need to work with pointers. Sun Microsystems chose to outlaw pointer types completely in Java, but Microsoft retained the capability to work with pointers in C#. C# requires you to isolate code using pointers in blocks marked specifically as unsafe. Because pointer use cannot be verified by the runtime, unsafe code can run only in a fully trusted context; this restricts how a component relying on unsafe features can be utilized. Unlike Javas all-or-nothing approach, C#s unsafe code feature is an elegant and accessible solution to the pointer problem. In C#, you have the choice to remain within the completely managed domain or to step outside if necessary. You should minimize your use of this feature, however, because of the restrictions you incur when you include unsafe code in your application.

Pointers 167

Using Memory with Platform Invoke


A feature of .NET named Platform Invoke (often called PInvoke) enables you to call functions in DLLs written using pre-.NET tools. I used this feature in Chapter 6, Memory Management and C# for example, to import functions from KERNEL32.DLL for working with files. Ive reproduced the code in Listing 8.1 for your convenience. Listing 8.1 Unsafe Code Is Useful for Low-Level Work

1: class BinaryFileReader : IDisposable 2: { 3: // Requires file to exist on open 4: const uint OpenExisting = 3; 5: 6: // Requests read access 7: const uint GenericRead = 0x80000000; 8: 9: // returned on failure to open a resource 10: const uint InvalidHandleValue = 0xFFFFFFFF; 11: 12: // Win32 Kernel functions 13: [DllImport(kernel32, SetLastError=false)] 14: static extern unsafe uint CreateFile 15: ( 16: string lpFileName, 17: uint dwDesiredAccess, 18: uint dwShareMode, 19: uint lpSecurityAttributes, 20: uint dwCreationDisposition, 21: uint dwFlagsAndAttributes, 22: uint hTemplateFile 23: ); 24: 25: [DllImport(kernel32, SetLastError=false)] 26: static extern unsafe bool CloseHandle 27: ( 28: uint hFile 29: ); 30: 31: [DllImport(kernel32, SetLastError=false)] 32: static extern unsafe bool GetFileSizeEx 33: ( 34: uint hFile, 35: ulong* lpFileSizeHigh 36: ); 37: 38: [DllImport(kernel32, SetLastError=false)] 39: static extern unsafe bool ReadFile 40: (

168 Chapter 8: Unsafe Code

Listing 8.1
41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87:

continued
uint hFile, void* lpBuffer, uint nBytesToRead, uint* nBytesRead, uint overlapped );

unsafe byte [] Buffer = null; ulong BufferSize = 0; uint CurrentFileHandle = 0; // public functions for using data // Get size of data read public ulong Length { get { return BufferSize; } } // Indexer for safe access to buffer public int this[ulong index] { get { if ( index >= 0 && index < Length ) return Buffer[index]; else throw new IndexOutOfRangeException( Accessing BinaryFileReader[]); } } // Loads data into memory unsafe public void ReadData( string path ) { uint FileHandle = CreateFile( path, GenericRead, 0, 0, OpenExisting, 0, 0 ); if ( FileHandle != InvalidHandleValue ) { ulong NewBufferSize = 0; uint BytesRead = 0; // hold on to the handle so I can use it in other // operations CurrentFileHandle = FileHandle;

Pointers 169

Listing 8.1
88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134:

continued
if ( GetFileSizeEx(FileHandle, &NewBufferSize) ) { if ( BufferSize < NewBufferSize ) { BufferSize = NewBufferSize; Buffer = new byte[BufferSize]; } fixed ( void * BufferPtr = Buffer ) if ( !ReadFile(FileHandle, BufferPtr, (uint)BufferSize, &BytesRead, 0) || BytesRead != BufferSize) { throw new IOException( Error reading file.); } } else throw new IOException( Access error reading file size.); } else throw new FileNotFoundException( In BinaryFileReader.ReadData(), path);

} // am I disposed? bool Disposed = false; // Releases current file unsafe private void CloseFile() { if ( CurrentFileHandle != 0 && CurrentFileHandle != InvalidHandleValue ) { CloseHandle( CurrentFileHandle ); CurrentFileHandle = 0; } } // Managed release public void Dispose()

170 Chapter 8: Unsafe Code

Listing 8.1
135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: } {

continued
if ( !Disposed ) { // release the file handle CloseFile(); // release reference Buffer = null; // keep me from being finalized GC.SuppressFinalize(this); } else throw new ObjectDisposedException(BinaryFileReader, Disposed more than once. );

} // Finalize ~BinaryFileReader() { if ( CurrentFileHandle != 0 ) CloseFile(); }

Because the Win32 libraries are unmanaged code, Ive marked the API functions as unsafe. In addition, because the ReadFile() function requires client code to supply a pointer to a buffer, the client code itself must utilize unsafe features to pin the buffers location in memory (line 97 in Listing 8.1).

Unsafe Contexts
An unsafe context is lexically a code element marked with the unsafe keyword. The unsafe keyword can be applied to class, struct, interface, delegate, field, method, property, event, indexer, operator, constructor, and destructor elements. You can also mark specific blocks of code within a method as unsafe, as in the following code:
public void UseBuffer() { // normal managed code here; no unsafe allowed Buffer = new byte[BufferSize]; unsafe // surround the pointer use with an unsafe block { // now were in an unsafe context fixed ( void * BufferPtr = Buffer ) {

Unsafe Language Elements 171 // do something useful with BufferPtr } } }

The extent of the unsafe context is the scope of the element that it is used to modify. For compilation purposes, unsafe does nothing to the code that the compiler generates; all it does is enable the marked code to use unsafe elements. Access to unsafe contexts from managed contexts is restricted only by the signature of the element you want to access. Because you can manipulate unsafe elements only in unsafe code, you cannot access unsafe elements such as pointers even indirectly. Listing 8.2 demonstrates these restrictions. Listing 8.2 Passing Pointer Access Isnt Allowed in Managed Code, Even Just in

public static unsafe char * GetChars() { return null; } public static unsafe void GoodContext() { // This is OK since were in an unsafe context UnsafeFunc( GetChars(), 0 ); } public static void BadContext() { // This wont work, even though the managed code never touches the // pointer value for anything; generates a compiler error: Pointers // may only be used in unsafe contexts. UnsafeFunc( GetChars(), 0 ); }

As noted in the comments, although nothing is in the BadContext() function that manipulates the unsafe data (the character pointer returned from GetChars()), even handing it off to the UnsafeFunc() is disallowed. If you need to manage pointer types in managed code, you can use the IntPtr structure to hold the value and pass it to other methods.

Unsafe Language Elements


Within an unsafe context, you can still access managed code and data. The unsafe context provides you with additional features, however, as listed in Table 8.1.

172 Chapter 8: Unsafe Code

Table 8.1 Feature


Pointer Pin object

Unsafe Language Elements Notation


T * PtrVar; fixed ( assign ) stmt *IntPtr = 5; SPtr->member IntPtr = &IntVar i = sizeof(IntVar) IntPtr++; SPtr--;

Purpose
Contains the address of a variable of type T. Fixes the location of an object in memory. Accesses the referent instance. Accesses a member of a referent struct instance. Provides the address of an instance in memory. Provides the size in bytes of a type. Adds or subtracts sizeof(T) to or from a pointer to type T. Pointers can be compared using the same rules as those for unsigned integers. Allocates n copies of T on the stack.

Dereference pointer Select member Address-of (&) Size-of Increment/decrement

Compare

Comparison operators

Allocate stack space

TPtr = stackalloc T[n]

As Ive said, interaction with legacy library code is a principal reason for including the unsafe features in C#. For example, suppose you have the code in Listing 8.3 in a legacy C++ library. Listing 8.3 A ROT13 Library Function in C++

extern C { _declspec(dllexport) void Codec( char * buffer, int length = 0 ); } . . declspec(dllexport) void Codec( char * buffer, int length ) { char bound; if ( length == 0 ) length = strlen(buffer); // cast to a byte * to simplify syntax for ( byte * c = (byte *)buffer; c < (byte *)buffer + length; c++ ) { // only operate on alphabetic characters

Unsafe Language Elements 173

Listing 8.3

continued

if ( isalpha( *c ) ) { // handle upper/lower case if ( islower(*c) ) bound = z; else bound = Z; // translate the character if ( *c > bound - 13 ) *c -= 13; else *c += 13; } } }

The Codec() function in Listing 8.3 translates alphabetic text in ASCII into ROT13coded text (ROT13 is a tongue-in-cheek cipher that involves replacing characters with the character 13 positions away in the alphabet). Because ROT13 is symmetric, the same function can decode ROT13-coded text. This function is exported from the DLL in which it resides; therefore, you should be able to use it from your C# code by using PInvoke features. Listing 8.4 shows how. Listing 8.4
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:

Wrapping the ROT13 Utility Function

[DllImport(ROT13.DLL, EntryPoint=Codec, SetLastError=false, CallingConvention=CallingConvention.Cdecl)] static extern unsafe void DllCodec( char * buf, int length ); public static unsafe void Codec( ref string buffer ) { ASCIIEncoding ae = new ASCIIEncoding(); byte [] BufArray = ae.GetBytes(buffer); fixed ( byte * StrParam = BufArray ) { // call the function and pass the pinned buffer DllCodec( (char *)StrParam, buffer.Length ); } buffer = ae.GetString( BufArray ); }

Lines 1 to 3 use the System.Runtime.InteropServices.DllImport attribute to mark the DllCodec() method prototype as an import from the DLL and to tell it what function in the DLL to invoke (the EntryPoint parameter). The function in the DLL is not

174 Chapter 8: Unsafe Code

exposed to managed code, though; the method that is exposed is the Codec() method that follows in lines 5 to 18. The Codec() method handles marshaling the text from a Unicode string that was passed in (buffer) to ASCII text, invoking the function in the DLL and converting the resulting text back into a string. You might have noticed that in Listing 8.1, I didnt declare the LPTSTR parameters in the import statements using char *, but passed information directly as string variables. For parameters, this is OK because the framework will unobservedly convert the string to an appropriate character buffer to pass by value. Ive chosen to use an in/out buffer in this example, however, to show you how to manage the marshaling yourself. Its worth noting that although it complicates the code a bit, Ive declared the first parameter of the DllCodec() method as a char *, as it was in the C++ code. Although I could legally declare it in the C# code as a byte * to simplify interacting with the System.Text.Encoding.ASCIIEncoding instance, doing so would be sloppy, in exactly the manner that motivates language designers to dispose of pointers altogether (Pascal/Delphi programmers, please pardon the pun). Although its sometimes jokingly referred to as inline C, unsafe code is still very much C#. Of particular importance is that when youre interacting with legacy libraries, you do not have the option of including headers as you would with C or C++. Therefore, it is your responsibility to locate and redeclare any structures or canonical values, as I did in the file example in Listing 8.1.

Memory Management in Unsafe Code


For unsafe code that must allocate buffers at runtime, C# provides the stackalloc statement. Listing 8.5 demonstrates using it to allocate an array of structs: Listing 8.5 Using
stackalloc

to Create an Array of Objects

struct Tuple { public double a, b, c; } const int NTUPLES = 10; unsafe void Evaluate() { Tuple * Tuples = stackalloc Tuple[NTUPLES]; for ( Tuple * TPtr = Tuples; TPtr < Tuples + NTUPLES; TPtr++ ) { // do something useful with as, bs and cs TPtr->a = 1;

Summary 175 TPtr->a = 2; TPtr->a = 3; } }

The stackalloc statement is different from new in three key areas. First, it will not initialize the storage you allocate in any way; the variables you have will contain whatever random garbage was on the stack. Second, no explicit way exists to reclaim memory you have allocated. After you reserve memory-using stackalloc, it is reclaimed only when the function returns. Because managed types exist only on the heap, you cannot stackalloc any type that contains a managed type or a reference to one. Finally, when you stackalloc an array, the members of the array are already initialized. When new allocates an array, on the other hand, its members must be created by you. There is a compromise to be reached between unsafe code and the garbage collector (GC); the GC doesnt know what unsafe pointers refer to, so you can gain pointers only to value types or arrays of value types. Although you can still allocate instances of managed types in unsafe code, you cant declare pointers to them. The following code will not compile:
String S = a string instance; . . String * SPtr = &S; // Bad Thing: cant take the address of heap instance

Because local value types and objects allocated with stackalloc do not exist in the managed heap, they are invulnerable to being moved around by the GC. For value fields of reference types, you must use the fixed statement to expose the address of the field you need a pointer to; fixed informs the GC of managed instances that should not be moved during execution of the code within the statement, pinning them for the lifetime of the fixed statement. You must ensure that no addresses retrieved by the fixed statements code survive beyond its execution, because after execution passes beyond its scope, there is no way to predict whether those addresses will remain valid.

Summary
Managed execution is advantageous in the greater reliability and error isolation it can provide your applications. With appropriate coding techniques, you can capture and recover from errors that would mean an unavoidable crash in another environment, and the platform helps keep errors in one program from impacting the whole system. However, when you reach the limits of the managed environment, you can use the unsafe code and Platform Invoke to interact with memory and unmanaged resources. By including these two features in the language, Microsoft provides an easy, clear way to access unmanaged resources from managed components.

Using Metadata and Reflection

CHAPTER 9
Using Metadata and Reflection
Metadata is information that describes the contents of an assembly. Although certain items of information are fixed by the structure of your code (such as the name of a type or return type of a method), you can also apply attributes, metadata that you supply. The .NET framework alone defines more than 150 attribute types, and the runtime enables you to extend the metadata system by creating custom attributes of your own. The technology in .NET that you use to access metadata is called reflection. Reflection brings many classes to the table that you can use for runtime discovery of types, assemblies, attributes, and other information. With appropriate trust privileges, you can even use it to create assemblies from your program, although I wont demonstrate that here.

Using Attributes
The tool you will use most often to manipulate metadata in C# is the attribute. Attribute types are typically intended to satisfy a tightly scoped need by associating a specific piece of information or behavior to an element of your program. Programming the .NET platform effectively requires you to use attributes in many cases, and you will probably find other applications for custom attributes of your own. Attributes can be associated to any of the following elements, as appropriate: Assemblies Modules Types Events

178 Chapter 9: Using Metadata and Reflection

Properties Interfaces Fields Methods, including constructors and destructors Return values Delegates Parameters

Few attributes can apply across all these elements. Most attributes are restricted in usage to a subset of them. Attributes are class types defined like any other and can incorporate fields, methods, properties, or any other class member relevant to its intent. Attributes are declarative elements that can function essentially as tags to influence the compilers actions, or they can provide additional information at runtime to programs using Reflection. In C#, you attach attributes to an element by preceding the elements declaration with the attribute(s), contained in square brackets. Multiple attributes can be either comma separated within one set of brackets, or you can place each in its own pair. For example, the following two declarations are equivalent:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: // Stacked definition [assembly:EnvironmentPermission(SecurityAction.RequestMinimum, Read=OS;SYSTEMROOT)] [assembly:AssemblyTitle(AttributeDemo)] // combined declaration [ assembly: EnvironmentPermission(SecurityAction.RequestMinimum, Read=OS;SYSTEMROOT), AssemblyTitle(AttributeDemo) ]

These declarations both request a particular security permission for the assembly at runtime (read access to the OS and SYSTEMROOT variables) and label the assembly at build time (with the AssemblyTitle of AttributeDemo). The EnvironmentPermission attribute demonstrates positional and named parameters. Positional parameters are required and must be applied in a particular order (refer to the SecurityAction.RequestMinimum parameter at lines 2 and 9); named parameters can follow the positional parameters and neednt be in any particular order, like the Read parameters on lines 3 and 10. Unlike positional parameters, for which you simply provide the value, you furnish a named parameter in the form of <parameter_name> = <value_exp>, as in the example on lines 3 and 10. One of the intrinsic, or built-in, attributes in the BCL (Base Class Library) is the Serializable attribute. This attribute marks objects that should be enabled by the runtime to serialize by using a formatter, which you can use to transmit objects over the

Using Attributes 179

network, to persist them to disk for later retrieval or other activity that requires you to move them from one context to another. For your convenience, Listing 9.1 reintroduces the example of using this feature from Chapter 3, The Base Class Libraries. Listing 9.1 You Can Serialize to XML by Using the Serializable Attribute and an Appropriate Formatter
using System; using System.IO; using System.Collections; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Serialization.Formatters.Soap; . . . [Serializable] class StreetAddress { public int id; public string name, street1, street2, city, state, zip; public StreetAddress() { name = street1 = street2 = city = state = zip = ; id = 0; } public StreetAddress(int inId, string inName, string inStreet1, string inStreet2, string inCity, string inState, string inZip) { id = inId; name = inName; street1 = inStreet1; street2 = inStreet2; city = inCity; state = inState; zip = inZip; } } class Serializer { static void Main(string[] args) { ArrayList addresses = new ArrayList(10); // create a list of addresses for ( int id = 0; id < 10; id++ )

180 Chapter 9: Using Metadata and Reflection

Listing 9.1
{

continued
addresses.Add( new StreetAddress(id, AName, 123 Main St., Ste. 800, Anywhere, AK, 12345) );

} // write the information out as XML IFormatter soapFmt = new SoapFormatter(); Stream s = File.Open( outfile.xml, FileMode.Create ); soapFmt.Serialize( s, addresses ); s.Close(); // read the data back in s = File.Open( outfile.bin, FileMode.Open ); addresses = binFmt.Deserialize( s ) as ArrayList; for ( int i = 0; i < addresses.Count; i++ ) Console.WriteLine( ((StreetAddress)addresses[i]).id.ToString() + + ((StreetAddress)addresses[i]).name); } }

Creating Custom Attributes


The many intrinsic attributes contribute to various aspects of .NET programming, from basic serialization to important features such as security, Reflection, and COM interoperation. With everything the intrinsics enable, though, Microsoft cant preplan every case in which you would need to assign metadata to your code. Built-in attributes, in fact, have been one of the most volatile areas of the .NET framework as it has evolved. Because of the broad applicability of attributes, the attribute mechanism is extensible in code through custom attribute classes. You define a custom attribute by declaring a type derived from the System.Attribute class (in early versions of the framework this inheritance was suggested; in the final version it is required). The constructors parameters become the positional attributes you must include when you apply the attribute, and any fields or publicly writable properties are available as named parameters. You may also optionally apply the AttributeUsage attribute to your attribute class to control the way your attribute is used. Listing 9.2 demonstrates declaring a custom attribute. Listing 9.2 A Custom Attribute Must Derive from System.Attribute

1: namespace CustomAttributes 2: { 3: // declare a custom attribute that can be used to label 4: // code with the requirement it satisfies

Creating Custom Attributes 181

Listing 9.2
5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: }

continued

[AttributeUsage( AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Interface)] public class UserRequirementAttribute : Attribute { string RequirementProperty; string RevisionProperty; public UserRequirementAttribute( string ReqNr ) { RequirementProperty = ReqNr; RevisionProperty = null; } public string Requirement { get { return RequirementProperty; } } // provide for an optional revision property public string Revision { set { RevisionProperty = value; } get { return RevisionProperty; } } }

TIP
Notice the order of events in Listing 9.2. When the attribute is created, the constructor must be completely executed before any properties are set. Therefore, setting the revision string to null in the constructor does not conflict with an assignment to Revision when the attribute is used.

The class UserRequirementAttribute declares a custom attribute you can use to tag major pieces of your code with the requirement and optionally the revision ID

182 Chapter 9: Using Metadata and Reflection

requiring the item. With this type, you can write code like that in Listing 9.3, applying the requirement to other classes. Listing 9.3 LibraryClass Uses the Requirement Information
UserRequirementAttribute

to Include

[UserRequirement(1.4.6, Revision = 2.0)] public class LibraryClass { public LibraryClass() { // do constructor work } public string AProperty { get { return This string brought to you by LibraryClass.; } } }

Listing 9.3 demonstrates using the custom attribute; because the compiler will supply the Attribute suffix if you leave it off, the code refers to the attribute as UserRequirement. The constructor for the UserRequirementAttribute must receive a string parameter for the requirement number, so the LibraryClass type includes a value for it as the first (positional) parameter. UserRequirementAttribute also declares a property (Revision) that LibraryClass makes use of as a named parameter, supplying 2.0 as the value. Unfortunately, Listing 9.3 also brings to the fore a problem. Although associating requirements to code is great, suppose a version 3.0 of the program exists, and the code is modified to support another requirement? The LibraryClass still satisfies the existing requirement, but as written, the UserRequirement can be applied only once per element: assembly, class, or interface. As you probably expect, the System. AttributeUsage class incorporates a property called AllowMultiple that you set to true to enable using multiple instances of your attribute:
[AttributeUsage( AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true )] public class UserRequirementAttribute : Attribute { . . . }

Reflection and Dynamic Binding 183

Modifying the attribute with the AllowMultiple property enables attaching all the relevant requirements to LibraryClass:
[UserRequirement(1.4.6, Revision = 2.0)] [UserRequirement(3.7.3, Revision = 3.0)] public class LibraryClass { . . . }

TIP
One interesting application of a custom attribute is the TraceHook.NET attribute from Razorsoft at http://www.razorsoft.net/TraceHook.htm. This neat tool enables call logging on methods, properties, and fields in a class.

Reflection and Dynamic Binding


Attributes are OK for people reading your code, but they must be available programmatically to be truly useful. The .NET frameworks reflection libraries enable you to retrieve attributes and other metadata in code, and they also support dynamic loading and binding to assemblies and the types they contain.

Reflection on Statically Bound Elements


Before you can work with the metadata in an assembly or type, it must be loaded into your programs space. When you create programs for the .NET platform, you can choose between static and dynamic binding. Static binding, the most common approach, is the method that you are using when you reference namespaces and use types in your code. Static binding, also called early binding, is much easier than dynamic, or late, binding, but it requires that you have the assemblies to which your program will refer on hand during compilation and linking. With a statically bound reference, the compiler follows the reference and error checks your code when you compile and link your program. Any references your program makes to elements in other modules are encoded into the output file as symbolic references that describe the reference target. At runtime, the CLR uses this information to bind the references, resolving the symbolic description to the actual locations of the referent items. For assemblies that are statically linked to your program, reading metadata is not difficult. Listing 9.4 shows how to read the attributes from the LibraryClass type.

184 Chapter 9: Using Metadata and Reflection

Listing 9.4 LibraryClass Uses the Requirement Information


1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:

UserRequirementAttribute

to Include

LibraryClass lc = new LibraryClass(); . . // use the class for whatever... . . // reading the type information: first, get a reference to // the type Type t = lc.GetType(); // retrieve the attributes, specifying the type of the attribute // and whether to search the objects inheritance tree; since // were only looking at the LibraryClass, the only attributes // Ill get back are UserRequirementAttributes. Object [] Reqs = t.GetCustomAttributes( false ); foreach ( Object o in Reqs ) { if( o is UserRequirementAttribute ) // still need to check! { UserRequirementAttribute ura = (UserRequirementAttribute)o; Console.WriteLine( Class applies to requirement {0}, revision {1}, ura.Requirement, ura.Revision ); } }

In Listing 9.4, line 9 retrieves the System.Type corresponding to LibraryClass from an instance of the type. Line 15 then asks for the array of attributes assigned to that type, restricting the search to only the LibraryClass scope. The example lists the requirements and versions to the console in lines 16 to 26. Note the check at line 18 to verify that the attribute is the correct type, before the code makes the typecast at line 21. Although I know that Ill only get UserRequirementAttribute types by looking at the code that implements the LibraryClass type, relying on that knowledge would be suspect.

Dynamic Loading and Binding


Static binding is sufficient for most cases, but it is not sufficient for some applications. Some applications must be replaceable or upgradable in pieces, and that is difficult to do if you can only use static references. For more flexible situations, .NET supports dynamic binding, a process in which your program constructs a reference at runtime based on whatever decision process is appropriate to your application.

Reflection and Dynamic Binding 185

NOTE
Except for framework programmers, it might seem difficult to find a use for dynamic binding using Reflection. However, if you look more closely, it isnt hard to see ways to use dynamically assembled applications. An image-processing program, for example, could reserve a specific directory in its installation tree for installable filters; in this scenario, the application inspects assemblies in that directory for filter objects at runtime, and when the user asks to apply a filter to an image, the program displays the list of those that it found. By structuring the applications operation this way, installing a new filter DLL entails nothing more than copying it to the proper directory.

Reading Metadata from Dynamically-Bound Assemblies


For items that are dynamically bound to your code, you must use reflection to work with assemblies and code elements. This feature of .NET is extremely easy to use compared to the Win32 APIs for working with modules and functions. It is similar to the IDispatch/type library-based late binding technology in Win32, but is more approachable. To demonstrate, Listing 9.5 enumerates all the elements in an assembly. Listing 9.5
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:

Reflection Opens up Metadata to Your Program

using System; using System.Reflection; . . // read the assembly Assembly assy = Assembly.Load( args[0] ); // assembly is made up of modules: foreach ( Module mod in assy.GetLoadedModules() ) { Console.WriteLine( Module: {0}, mod.Name ); // modules contain types: foreach ( Type t in mod.GetTypes() ) { Console.WriteLine( \tType: {0}, t.Name ); // and each type has members: foreach ( MemberInfo m in t.GetMembers() ) { Console.WriteLine( \t\t{0}: {1}, m.MemberType, m.Name ); switch ( m.MemberType ) { case MemberTypes.Constructor: ConstructorInfo ci = (ConstructorInfo) m;

186 Chapter 9: Using Metadata and Reflection

Listing 9.5
26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: }

continued
foreach ( ParameterInfo pi in ci.GetParameters() ) Console.WriteLine( \t\t\tParameter: {0} {1}, pi.ParameterType, pi.Name ); break; case MemberTypes.Method: MethodInfo mi = (MethodInfo) m; foreach ( ParameterInfo pi in mi.GetParameters() ) Console.WriteLine( \t\t\tParameter: {0} {1}, pi.ParameterType, pi.Name ); Console.WriteLine(\t\t\tReturns: {0}, mi.ReturnType ); break; case MemberTypes.Field: FieldInfo fi = (FieldInfo) m; Console.WriteLine( \t\t\tField type: {0}, fi.Name ); break; case MemberTypes.Property: PropertyInfo pri = (PropertyInfo) m; Console.WriteLine(\t\t\tProperty Type: {0}, pri.PropertyType); break; } }

Using reflection usually requires both the System and System.Reflection namespaces. The fundamental types used for reflection are distributed among the two, probably because certain elements (such as the Type class) are basic to system operation. Therefore, the code in Listing 9.5 includes both namespaces, and then loads the desired assembly on line 6. The rest of the code, lines 950, is a set of nested loops that progressively drill further into the contents of selected items in the assembly and print information about them to the console. When executed against the LibraryCode DLL, Listing 9.5 produces the following output:
Module: librarycode.dll Type: LibraryClass Method: GetHashCode Returns: System.Int32 Method: Equals Parameter: System.Object obj Returns: System.Boolean Method: ToString Returns: System.String Method: get_AProperty Returns: System.String Method: Multiply

Reflection and Dynamic Binding 187 Parameter: System.Int32 a Parameter: System.Int32 b Returns: System.Int32 Method: GetType Returns: System.Type Constructor: .ctor Property: AProperty Property Type: System.String

Using Reflection to read the attributes in the LibraryClass example, you could replace line 9 of Listing 9.4 to produce the following:
Assembly Asy = Assembly.Load(LibraryCode); Type t = Asy.GetType(LibraryCode.LibraryClass); // retrieve the attribute by specifying the type of the attribute // and whether to search the objects inheritance tree Object [] Reqs = t.GetCustomAttributes( false ); foreach ( Object o in Reqs ) { if( o is UserRequirementAttribute ) { UserRequirementAttribute ura = (UserRequirementAttribute)o; Console.WriteLine( Class applies to requirement {0}, revision {1}, ura.Requirement, ura.Revision ); } }

Instead of using an existing object, this code creates an Assembly object by loading the LibraryCode assembly. This assembly is in a DLL called LibraryCode.dll, but note that you do not include the file extension when calling Assembly.Load( assy_name ). After loading the assembly, gaining a reference to the type requires only calling Assembly.GetType() with the fully qualified type name. The remainder of the code is unchanged.

TIP
Dont know the name of the item you need? You can use ildasm to inspect an assembly and find the right name to use.

Dynamic Invocation with Reflection


Reflection enables more than just reading information. You can also use it to dynamically create type instances and invoke methods. Listing 9.6 creates a LibraryClass and reads the property AProperty.

188 Chapter 9: Using Metadata and Reflection

Listing 9.6 Dynamically Invoking a Method to Read a Property with its get_XXX Method
1: 2: 3: 4: 5: 6: 7: 8: Assembly Asy = Assembly.Load(LibraryCode); Type t = Asy.GetType(LibraryCode.LibraryClass); Object instance = Asy.CreateInstance( t.FullName ); Object s = t.InvokeMember( get_AProperty, BindingFlags.InvokeMethod, Type.DefaultBinder, instance, null ); Console.WriteLine( s );

Lines 1 and 2 load the library and type as in the previous example, and then line 4 creates an instance of the class. Lines 57 invoke the method using the Type.InvokeMember() method. This method is polymorphic and can explicitly control several aspects of binding and invoking the method. In this case, Listing 9.6 specifies the method, binding flags, binder, object, and parameters. The method name, because it is for a property accessor, is formed by prepending get_ to the name of the property. The binding flags are a bitwise-ORd combination of flags from the System.Reflection.BindingFlags enumeration. InvokeMethod stipulates that this call is a simple method invocation.

NOTE
Member names in Reflection are case-sensitive, probably for performance reasons. In GetType() methods you usually have the option to have an exception thrown if the target isnt found, but other types of lookup methods will silently return a null. You should always check the return values from reflection GetXXX methods, or trap NullReferenceExceptions.

The binder parameter enables you to replace the default name resolution and type coercion behavior. You can pass in a System.Reflection.Binder-derived object to implement your own name resolution. Usually this isnt necessary, though, so you can pass in either null or explicitly use the Type.DefaultBinder object as in the example. The last two parameters to Type.InvokeMethod() are the instance of the object on which to invoke the operation and an Object [] array containing the parameters youre sending to the method. Because Listing 9.6 is calling a property get, which takes no parameters, it passes a null for the parameters list; you can also send an empty array if you want to make it obvious what is happening:
Object s = t.InvokeMember( get_AProperty, BindingFlags.InvokeMethod, Type.DefaultBinder, instance, new Object [] {} );

Although Ive demonstrated using Type.InvokeMethod(), this approach will probably be more control than you need. If you dont need to be concerned about binding and so forth, you can use the MethodInfo class returned from Type.GetMethod() to simplify

Reflection and Dynamic Binding 189

your life. To demonstrate, consider adding the following method to the LibraryClass type:
public class LibraryClass { . . . // Add a method that multiplies ints public int Multiply( int a, int b ) { return a * b; } }

To invoke the multiply method, you must supply two parameters and retrieve the return value it sends back. Listing 9.7 uses the MethodInfo created by Type.GetMethod() to achieve that objective. Listing 9.7
MethodInfo 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:

The Easy Way to Dynamically Call Methods: Use the That Describes It

Assembly Asy = Assembly.Load(LibraryCode); Type t = Asy.GetType(LibraryCode.LibraryClass); Object instance = Asy.CreateInstance( t.FullName ); // multiply two ints by using the Multiply method Object [] MethodArgs = new Object[2]; MethodArgs[0] = 2; MethodArgs[1] = 3; MethodInfo m = t.GetMethod(Multiply); s = m.Invoke( instance, MethodArgs ); Console.WriteLine( The result is {0}., s );

The LibraryClass.Multiply()method requires two integer parameters, so lines 79 in Listing 9.7 declare an Object[] array with two elements, and then place the values to pass into LibraryClass.Multiply() in the array. Line 11 retrieves the MethodInfo instance describing the method from the Type instance wrapping the type, and then line 12 invokes the method by using MethodInfo.Invoke(). This method requires only two parameters, the instance of the type and the parameter arguments array. Accessing properties through Reflection introduces two special cases. As you saw in Listing 9.6, the names of simple property accessor are modified by prepending get_ and set_ respectively to the method name. You can use BindingFlags. GetProperty in the call to InvokeMethod to hide this behavior, as Listing 9.7 shows.

190 Chapter 9: Using Metadata and Reflection

Listing 9.7 Using the Conventions


1: 2: 3: 4: 5: 6: 7: 8:

GetProperty

Flag Hides Accessor Naming

Assembly Asy = Assembly.Load(LibraryCode); Type t = Asy.GetType(LibraryCode.LibraryClass); Object instance = Asy.CreateInstance( t.FullName ); Object s = t.InvokeMember( AProperty, BindingFlags.GetProperty, Type.DefaultBinder, instance, new Object [] {} ); Console.WriteLine( s );

Listing 9.7 is identical to Listing 9.6, except for lines 5 and 6; because the GetProperty flag is set in line 6, the name in line 5 is just the name of the property, without the additional get_ prefix. You can further simplify using properties by using Types GetProperty method and the PropertyInfo type:
// using GetProperty mothod PropertyInfo pi = t.GetProperty( AProperty ); Console.WriteLine( pi.GetValue(instance, null) );

Type

In this example, the variable pi is an instance of PropertyInfo retrieved from the type. creates the PropertyInfo from the metadata describing the type, and you can then use it to obtain information about the property including its type, declaring type, and accessor functions. In the example above, I use it to retrieve the value of the property from an instance of the declaring type (instance). In addition to simple properties, there are indexed properties to work with. When you access indexed properties, you must also supply an array of Object containing elements of the correct type and in the order that you would supply them as subscripts in a code expression. The code in Listing 9.8 declares and retrieves a value from a class containing an indexed property. Listing 9.8 Accessing Indexed Properties

1: class IndexedClass 2: { 3: // some internal state values 4: protected int [] vals = { 1, 2, 3, 4, 5, 6 }; 5: 6: public int this[ int ix ] 7: { 8: get 9: { 10: if ( ix >= 0 && ix < vals.Length ) 11: return vals[ix]; 12: else 13: throw new ArgumentException( Index out of range, 14: String.Format(Index: {0}, ix )); 15: }

Summary 191

Listing 9.8
16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: }

continued

} . . . // Create an instance of IndexedClass Object [] ix = { 0 }; Asy = Assembly.GetExecutingAssembly(); t = Asy.GetType(Reflector.IndexedClass); instance = Asy.CreateInstance( t.FullName ); // Read the property pi = t.GetProperty(Item); Console.WriteLine( pi.GetValue( instance, ix ) );

Lines 117 of Listing 9.8 declare the IndexedClass type, with an internal array of ints called vals, and an indexed property that makes the array available to client code. Lines 2325 create an instance using reflection, as youve seen in the previous examples. Line 28 gets the property description and line 29 prints out the value on the console. Note that the name of an indexed property is always Item, as it was for default properties in COM+. To access the indexed property, I declare an array on line 22 containing a single int value that will be the parameter to the accessor. Line 29 passes it with the call to PropertyInfo.GetValue(), and the array is sent into the get() method at line 6. The array contains one int because that is the number and type of parameters declared for the property.

Summary
A significant feature of the .NET platform is the metadata used throughout the environment. The metadata information comes from configuration files, from development tools, and from attributes that you apply within your code. With this extensive descriptive information at its disposal, .NET is able to locate, configure, and execute applications reliably and securely. The SDK further enables you to create your own types of attributes and to access the runtime environment by using Reflection. Metadata and Reflection are only part of the application configuration question, though, so in Chapter 10, Configuring Components and Applications, youll find information on configuration and localization in the .NET environment.

Configuring Components and Applications

CHAPTER 10
Configuring Components and Applications
One of the .NET platforms distinguishing features is its extensive use of information external to application code to describe the applications structure and dependency information to the OS and management tools. For example, versioning and culture information, left to the application to manage in most environments, can be explicitly managed by the CLR in the .NET world. To enable this behavior, youll work with configuration files and resources in satellite assemblies.

Configuring Assemblies
The .NET framework applies standard protocols and information formats throughout. Its configuration files continue this trend by using XML formatting that you can edit directly and an elegant internationalization mechanism for resources. With a small amount of preplanning, you can deploy highly manageable software components. The following discussion doesnt describe .NET configuration completely, but it will give you enough information to get started managing your C# applications. For a more complete discussion of .NET configuration, please see the Configuring Applications topic in the .NET Framework SDK documentation. You can also find definitions of the configuration file structure under the Configuration File Schema heading.

Configuration Levels
.NET manages configuration information in machine, security, and application configuration files. Although you could conceivably use any text editor to modify these files, you should

194 Chapter 10: Configuring Components and Applications

use the .NET configuration console mscorcfg.msc to edit configuration settings whenever possible. It is both easier and less prone to error; manual editing can make it hard to locate and repair errors, and the messages youll receive from the runtime arent detailed enough to help if somethings wrong. The machine and application configuration files store information to configure application settings and the runtime environment. The machine configuration file is stored at <%systemroot%>\Microsoft.NET\<version>\Config\machine.config (configuration files use the .config extension) and contains information on the following and more: CLR configurationBasic defaults, remoting channels, application, and diagnostics Infrastructure configurationProxy and authentication settings Web configurationBrowser, internationalization, session, and service settings The machine and security configuration files supply systemwide settings; the application configuration file enables you to override the system defaults for particular programs or assemblies. The applications file is located in the same directory as the application and has the same name as the program plus the extension .config. Security can be specified down to the user level by manipulating user configuration files, as well.

Managing Configuration Files


Although you can edit configuration files directly, your principal tool for managing configuration information should be the .NET Admin Microsoft Management Console add-in, MSCORCFG.MSC. This add-in can add and remove assemblies in the GAC, configure .NET Remoting channels, modify runtime security code groups and permissions sets, and manage individual applications configuration files. Figure 10.1 shows the .NET Administrator add-in open in MMC. The first major feature of the .NET Admin tool is its capability to manage and configure assemblies in the GAC. The Assembly Cache node of the navigation tree displays assemblies in the GAC, as shown in Figure 10.2. The different icons displayed for the assemblies in the GAC tell the difference between platform-neutral (MSIL only) assemblies and those that have been precompiled to native code available with the ngen utility. Changes you make to the cache here affect the files in the <%systemroot%>\assembly\GAC directory and potentially the <%systemroot%>\ assembly\NativeImages* directories, where pre-JITted (precompiled) components are stored.

Configuring Assemblies 195

Figure 10.1
MSCORCFG.MSC

can help you to manage attributes of the .NET framework without getting your hands dirty on the underlying XML.

Figure 10.2 Maintain assemblies in the GAC by using the Assembly Cache node in the .NET Admin tool.

196 Chapter 10: Configuring Components and Applications

In addition to adding and removing items in the GAC, the Administration tool can modify the binding policy and codebases for assemblies in the cache. Figure 10.3 displays the math Properties dialog box that is used to modify these configuration items for the math assembly.

Figure 10.3 The .NET Admin tool is also useful for managing codebase and version compatibility parameters.

Remoting channels are the logical representation of communication mechanisms used to convey information between client and server programs in Remoting. You can manage the channels registered on the computer under the Remoting Services node of the .NET Admin add-in. Currently the only supported channels are the TCP and HTTP channels, and the only supported property is the delayLoadAsClientChannel property. Runtime security is a major feature of the CLR, and the Runtime Security Policy node brings the configuration settings affecting runtime behavior together in one place. Here, you can administer enterprise- and machine-level security, modifying the settings stored in enterprisesec.config and security.config, respectively. This node also provides the security configuration for the current user, in which you can modify specific security permissions applicable to your own login; the changes you make here are stored in your own login profile (under <%windir%>\Profiles on Windows 98, and \Documents and Settings on Windows 2000). The last major function of the .NET Admin add-in is application configuration. Figure 10.4 shows the application configuration dialog box for modifying the runtime binding properties of an assembly used by an application. By using this tool, you can configure binding to explicitly map between versions of an assembly at the application

Managing Resources 197

level, build in specific codebase paths, adjust Remoting properties, or just view the assemblies on which the application depends to be able to execute.

Figure 10.4 The .NET Admin tool even automates editing application-specific configuration.

The configuration parameters you set under the Applications node of the Admin addin are stored in application-specific configuration files co-located with the application itself.

Managing Resources
Resources in .NET are similar to those in the traditional Win32 environment: They carry strings, binary information such as images, data lookups, and so forth. In .NET, however, the runtime provides much more support for internationalization than Win32 ever did. By using satellite assemblies to store culture-specific information, your application can automatically change to respond to the locale configuration of the computer on which it is running. The format you use for your resource definitions varies depending on your needs. The fully general source format is the .resx format, an XML representation. If youll be storing only a string table, however, you can use a simple text file containing name/value pairs in the form string_name=value. The Framework SDK samples under the directory <%framework_dir%>\Samples\tutorials\resourcesandlocalization demonstrate both types of resources and provide some useful utilities. For example, the resxgen utility enables you to turn binary files into .resx files that you can compile into your application, and the GUI reseditor tool can add and modify resources in a .resources file. The Framework SDK also includes the winres tool for modifying

198 Chapter 10: Configuring Components and Applications

dialog layouts in .resources files. Of course, the best tools from Microsoft are found in the Visual Studio .NET product, which makes authoring and localizing resources trivial.

TIP
When working with resources, youre free to use the tool thats best for the job, even if it works only with a .resources file or only with .resx; you can translate freely between .resx and .resources files by using the resgen utility. This program compiles .resx files into .resources, but it can also go the opposite direction with equal ease.

Using Culture-Neutral Resources


As in Win32, you can compile resources into your application in the .NET environment. The resources that are placed in your applications files are called culture neutral because they are present no matter what the locale configuration is on the target computer. Even if your application is culturally aware, you should always include reasonable default settings for your application in culture-neutral resources. Creating resources in your application by using the SDK tools usually involves the following steps: 1. Create the content: text, graphics, or whatever. 2. Compile the resource into a .resources file by using the resgen utility. 3. Link the resource into your application, either by compiling them into the executable or linking them in DLLs. A great example of simple resource usage is the worldcalc example in the Framework SDK. This application uses a file called mystrings.txt to carry labels for onscreen elements, defined as in the following:
Math_Formula_Label = Formula and Results: Math_Clear_Button = Clear Math_Calc_Button = Calculate

The build script for the example runs ning the following command:

resgen

against the

mystrings.txt

file by run-

resgen MyStrings.txt MyStrings.resources

This command takes the text file and turns it into a resource file; resgen can take as input both text files of string pairs and .resx files with more complex information. The output .resources file can be incorporated into the executable in the command that compiles the main program:
csc %DEBUGSAMPLE% /target:winexe /out:WorldCalc.exe /addmodule:parser.dll /r:System.Windows.Forms.dll /r:System.Drawing.dll /r:System.dll /r:math\math.dll /res:MyStrings.resources WorldCalc.cs

Managing Resources 199

The /res:MyStrings.resources switch on the csc command causes the resource contents to be included in the output executable file as an mresource record. The code can then use the System.Resources classes to work with the resource. Listing 10.1 reproduces the relevant code from the WorldCalc.cs file. Listing 10.1 Messages
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41:

The

WorldCalc

Example Uses String Resources for Its Text

private ResourceManager rm; . . . private void InitializeComponent() { rm = new ResourceManager(MyStrings, this.GetType().Assembly); . . . this.Text = rm.GetString(Math_Greeting); this.ClientSize = new System.Drawing.Size(250, 230); lblFormula.Location = new System.Drawing.Point(8, 10); lblFormula.Text = rm.GetString(Math_Formula_Label); lblFormula.Size = new System.Drawing.Size(155, 20); txtFormula.Location = txtFormula.TabIndex = txtFormula.Size = new txtFormula.ReadOnly = new System.Drawing.Point(8, 28); 0; System.Drawing.Size(150, 25); true;

btnClear.Location = new Point(165, 28); btnClear.Size = new Size(80, 25); btnClear.Text = rm.GetString(Math_Clear_Button); btnClear.Click += new System.EventHandler(btnClearClicked); . . . btnEquals.Location = new Point(48, 180); btnEquals.Size = new Size(70, 30); btnEquals.TabIndex = 1; btnEquals.Text = rm.GetString(Math_Calc_Button); btnEquals.Click += new System.EventHandler(btnEqualsClicked); . . . }

200 Chapter 10: Configuring Components and Applications

Line 1 of Listing 10.1 declares a System.Resources.ResourceManager instance that is constructed to retrieve resources from the MyStrings resource file in the main programs assembly. The remaining code then uses the resource manager to load text strings from the resource file at lines 12, 17, 28, and 36. Resource manager enables retrieving named strings from the file by using the GetString() method, and it also contains the GetObject() method to load other resources such as icons or other graphics.

Using Culture-Specific Resources


Culture-neutral resources ensure that your application will be able to execute, but they do not respond to the needs of users of other languages or in other regions of the world. Culturally aware applications can use different layouts, messages, and other settings that tailor the program to the expectations of a diverse set of user groups. Applications can interact with internationalization features of the platform by using the classes in the System.Globalization namespace, but the behavior of the CLR in support of cultureaware applications will be sufficient for your needs. When the runtime locates resources your application has requested, it begins with the culture-neutral resources you have compiled with your application. It then looks for culture-specific resources in directories under your applications runtime directory named with the culture codes defined by RFC-1766 in the form <lang_code> or <lwr_case_lang_code>-<upr_case_region_code>, in which lang_code is the ISO 639-1 language code and region_code is the ISO 3166 region code. For example, U.S. English is identified as en-US, but the English used in Great Britain is identified as en-GB.

TIP
You will find a list of supported culture identifiers in the .NET Framework Reference. See the overview for the System.Globalization.CultureInfo class.

If a directory corresponding to the host computers locale is found, the runtime looks within that directory for a DLL named res_root.resources.dll, in which res_root is the root name of your resource file. This DLL, called a satellite assembly, should contain culture-specific resources from a resource file named res_root.locale_ id.resources. The sample program will have the culture-neutral resource Localize. resources and will use a subdirectory called FR containing a satellite DLL named Localize.resources.DLL for French-culture-specific resources. This assembly contains an embedded resource named Localize.FR.resources holding the French elements. The culture-neutral .resx file for the Localize application contains the definitions in Listing 10.2 (the program uses a .resx file because it needs to store dialog layouts and other nonstring resources).

Managing Resources 201

Listing 10.2
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:

Localize

Has Default (Culture Neutral) Messages in English

<?xml version=1.0 encoding=utf-8?> <root> . (extra definitions elided) . <data name=Pushbutton.Text> <value>Where do you want to go today?</value> </data> . . </root>

In Listing 10.2, lines 68 define the text that will be displayed on a pushbutton in a dialog box. To enable this dialog to communicate in French, I created another .resx called Localize.fr.resx, with the corresponding code in Listing 10.3. Listing 10.3 Resources in
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: Localize Can Converse Localize.fr.resx

in French with a Different Set of

<?xml version=1.0 encoding=utf-8?> <root> . . <data name=Pushbutton.Location type=System.Drawing.Point, System.Drawing> <value>24, 48</value> </data> <data name=Pushbutton.Size type=System.Drawing.Size, System.Drawing> <value>192, 23</value> </data> <data name=Pushbutton.Text> <value>O voulez-vous aller aujourdhui?</value> </data> . . </root>

Listing 10.3 at line 13 also asks where youd like to go, but in French. Figure 10.5 shows how the dialog box appears in an English locale, and Figure 10.6 demonstrates its changed interface in a French locale. The only thing that has changed in the application is that the locale setting is different; the program itself need not be modified at all.

202 Chapter 10: Configuring Components and Applications

Figure 10.5. The Localize application started out in English

Figure 10.6. but it automatically changes when the computer uses French.

The example also knows German; the tings:

DE

culture resource contains the following set-

<?xml version=1.0 encoding=utf-8?> <root> . . <data name=Pushbutton.Location type=System.Drawing.Point, System.Drawing> <value>32, 40</value> </data> <data name=Pushbutton.Size type=System.Drawing.Size, System.Drawing> <value>192, 24</value> </data> <data name=Pushbutton.Text> <value>Wo mchten Sie heute gehen?</value> </data> . . </root>

Please note that the localized examples dont only change the legend that is displayed on the button, but also the buttons size and position. This maintains a consistent look and feel in this case, but you can completely change the layout of a dialog to match your application to the culture. You can tailor text direction, icons, images, and other attributes to reflect what is locally expected.

Managing Resources 203

The steps to create a localized application may seem complicated, but when in action its not difficult, and its highly simplified over native Win32 programming. The makefile in Listing 10.4 demonstrates the steps needed to create an executable with satellite assemblies for localized resources. Listing 10.4 The Makefile for Localizing Resources
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: Localize

Demonstrates the Steps in

# makefile: 9/2001 wmr # # This file manages compiling satellite DLLs and main code for # chapter 10 of Pure C#. # # declare project files EXE=bin\localize.exe RESX=Localize.en.resx Localize.de.resx Localize.fr.resx ROOTRES=Localize.MainForm.resources RES=bin\en\Localize.MainForm.en.resources bin\fr\Localize.MainForm.fr.resources bin\de\Localize.MainForm.de.resources RESDLL=bin\en\Localize.resources.dll bin\fr\Localize.resources.dll bin\de\Localize.resources.dll EXESRC=Localize.cs AssemblyInfo.cs EXEDEP=$(EXESRC) makefile # declare build rules $(EXE): $(EXEDEP) $(RESDLL) $(ROOTRES) csc $(CFLAGS) /out:$(EXE) /t:exe $(EXESRC) /res:$(ROOTRES) Localize.MainForm.resources: resgen Localize.resx Localize.MainForm.resources bin\en\Localize.resources.dll: resgen Localize.en.resx bin\en\Localize.MainForm.en.resources al /out:bin\en\Localize.resources.dll /v:1.0.0.0 /c:en /embed:bin\en\Localize.MainForm.en.resources bin\fr\Localize.resources.dll: resgen Localize.fr.resx bin\fr\Localize.MainForm.fr.resources al /out:bin\fr\Localize.resources.dll /v:1.0.0.0 /c:fr /embed:bin\fr\Localize.MainForm.fr.resources bin\de\Localize.resources.dll: resgen Localize.de.resx bin\de\Localize.MainForm.de.resources al /out:bin\de\Localize.resources.dll /v:1.0.0.0 /c:de

204 Chapter 10: Configuring Components and Applications

Listing 10.4

continued

42: /embed:bin\de\Localize.MainForm.de.resources 43: 44: clean: 45: for %i in ( $(EXE) $(RESDLL) $(ROOTRES) $(RES) 46: $(LIBMOD) bin\*.pdb ) do del %i

Lines 720 of Listing 10.4 contain the usual declarations of targets and sources. Lines 26 and 27 compile the culture-neutral resource file Localize.resx into Localize.MainForm.resources, and then lines 23 and 24 compile the executable, incorporating the resource file with the /res switch. Lines 2942 show how to compile the culture-specific files for English, French, and German (country code DE), using the same resource root name (Localize.MainForm) with the appropriate country code, and placing the files into appropriately named subdirectories. With the unprecedented flexibility in configuration and localization it brings, the .NET platform makes managing systems across application and cultural boundaries dramatically less intimidating. The flexibility of the platform and the ease with which it can be manipulated should make secure, flexible applications much more the norm than before.

Summary
Commonly neglected and traditionally difficult to accomplish, configuration and localization stand a much better chance of being addressed by programmers on the .NET platform. .NET enables you to manage configuration across the enterprise or down to a specific user, either through management tools it supplies or by editing XML configuration files. .NET also helps to manage culture-specific behavior by automating the process of locating and loading culturally-tailored resources. In this chapter, youve seen how to utilize both configuration and localization for your programs. In Chapter 11, Using the SDK, youll see how to use the SDK tools to build applications for .NET and also to interoperate with native COM+ objects.

Using the SDK

CHAPTER 11
Using the SDK
The trend in computing products has always been to keep adding features, even those that no one really asked for, in a constant fight to tilt the value equation away from competitors. In recent times, this activity has been modified by established companies needing to increase profitability, resulting in added fees for smaller pieces of functionality instead of higher prices for complete solutions. Microsoft, for the time being, is bucking that trend with .NET. Youll still want to invest in a good programmers editor (such as SlickEdit from MicroEdge), but the .NET Framework SDK includes all the fundamental tools needed to build software for .NET. This chapter presents the steps to get from source to solution with only the tools that come in the SDK.

Compiling and Linking


The Win32 Platform SDK included headers, import libraries, and basic tools for working with various features of the platform, but you still needed to purchase compilers and other tools to actually build software for Windows. The .NET framework takes us back in timeto the days when any self-respecting OS shipped with tools to program the system.

Basic Compilation Steps


The simplest path to a working C# application is to compile the code with csc.exe and to link libraries if necessary by using al.exe, possibly under the control of the nmake.exe build management tool. Chapter 2, Working with Applications, describes the details of working with these programs, but for quick reference, Ill reiterate the high points.

206 Chapter 11: Using the SDK

Any large program will normally be broken up as a primary program executable and one or more supporting dynamic link libraries (DLLs). The steps to creating such a program are as follows: 1. Group functional elements together to identify library and main program boundaries. 2. Compile individual files into modules with csc. 3. Link supporting assemblies into DLLs with al. 4. Compile and/or link the main executable with references to supporting assemblies. This process can quickly become difficult to accomplish manually, so youll need to use either batch or make files to automate it. If you can accept the overhead of a full rebuild every time you make a change, you can use batch files; otherwise, youll need to build a make file to optimize compilation time. For example, a command-line telephone manager could implement the code in Listing 11.1 to manipulate the entries themselves. Listing 11.1 Book
PhoneList.cs

Contains the Library Code for the Telephone

/* PhoneList.cs : defines telephone book classes * * 9/2001 wmr * */ using System; using System.Collections; namespace Telephones { [Serializable] public class PhoneList { private SortedList Entries = new SortedList(); public PhoneEntry GetEntry( string FirstName, string LastName ) { return (PhoneEntry)(Entries[FirstName + LastName]); } public string this[string FirstName, string LastName] { get { return ((PhoneEntry)(Entries[FirstName + LastName])).Phone; } set

Compiling and Linking

Listing 11.1
{

Continued

if ( Entries[FirstName + LastName] == null ) { // initialize a new entry PhoneEntry NewPhone = new PhoneEntry(); NewPhone.FirstName = FirstName; NewPhone.LastName = LastName; NewPhone.Phone = value; // put it in the list Entries[FirstName + LastName] = NewPhone; } } } } [Serializable] public class PhoneEntry { // internal state variables string FirstNameProperty; string LastNameProperty; string PhoneProperty; public string FirstName { get { return FirstNameProperty; } set { if ( value.Length > 0 ) FirstNameProperty = value; else throw new ArgumentException( Invalid first name. ); } } public string LastName { get { return LastNameProperty; } set { if ( value.Length > 0 ) LastNameProperty = value; else throw new ArgumentException( Invalid last name. ); }

208 Chapter 11: Using the SDK

Listing 11.1
}

Continued

public string Phone { get { return PhoneProperty; } set { if ( value.Length > 0 ) { foreach (char c in value) if ( !(Char.IsDigit( c ) || c == - || c == .) ) throw new ArgumentException( A phone number can contain only + digits, hyphen or periods.); // if we get here we have a good number PhoneProperty = value; } else throw new ArgumentException( Invalid first name. ); } } } }

Listing 11.1 declares the PhoneEntry and PhoneList classes, which enable client code to work with telephone entries. These types should go into an assembly so that a lookup tool, an add tool, and others can work with the telephone information. Listing 11.2 shows code for one such toola command-line program that can run against a telephone list to find a number for a user. Listing 11.2 for Someone
Lookup.cs

Opens a Telephone File and Looks for an Entry

/* Lookup.cs: main file for lookup program * * usage: Lookup <firstname> <lastname> <telephone file> * * Program looks up a name in the file specified. * * 9/2001 wmr */ using using using using using System; System.IO; Telephones; System.Runtime.Serialization; System.Runtime.Serialization.Formatters.Soap;

Compiling and Linking

Listing 11.2

Continued

namespace Lookup { public class MainClass { public static void Main( string [] args ) { if ( args.Length == 3 ) { // read file from disk Stream s = File.Open( args[2], FileMode.Open ); IFormatter Soap = new SoapFormatter(); PhoneList Phones = Soap.Deserialize( s ) as PhoneList; // look up the entry requested string Phone = Phones[ args[0], args[1] ]; // answer the user if ( Phone != null ) Console.WriteLine(Entry: {0} {1}\nPhone: {2}\n, args[0], args[1], Phone ); else Console.WriteLine(No entry for {0} {1}.\n); } else Console.WriteLine( usage: Lookup <firstname> <lastname> <telephone file> ); } } }

Compiling and linking Listings 11.1 and 11.2 correctly is simple, as long as they are the only two source files with which youre working. However, to enable code sharing among the various programs that might want to manipulate phone book entries, Ill use the makefile in Listing 11.3 to place the phone list code into a DLL and the tool code into the EXE. Listing 11.3 The Makefile for the Telephone Book Is a Flexible Starting Point for Your Own
# makefile for PhoneList example # # 9/2001 wmr # tool options CFLAGS= LDFLAGS=

210 Chapter 11: Using the SDK

Listing 11.3
# library LIBSRC = LIBMOD = LIBNAME =

Continued

sources; expand as necessary for multiple libraries PhoneList.cs PhoneList.mod Phones.dll

# if youre signing the library, use a /keyX: option here # to tell the linker where to get the signature LIBKEY = # main program sources EXEREF = /r:$(LIBNAME) EXESRC = Lookup.cs EXEMOD = Lookup.mod EXENAME = Lookup.exe # incremental link requires explicit entry point ENTRY = /Main:Lookup.MainClass.Main # build rules $(EXENAME): $(LIBNAME) $(EXEMOD) al $(LDFLAGS) /out:$(EXENAME) /target:exe $(ENTRY) $(EXEMOD) $(LIBMOD): $(LIBSRC) csc $(CFLAGS) /out:$@ /t:module $*.cs $(EXEMOD): $(EXESRC) csc $(CFLAGS) /out:$@ /t:module $(EXEREF) $*.cs $(LIBNAME): $(LIBMOD) al $(LIBKEY) $(LDFLAGS) /out:$(LIBNAME) /target:library $(LIBMOD) clean: del $(EXENAME) $(LIBNAME) $(EXEMOD) $(LIBMOD)

The makefile in Listing 11.3 will handle compiling and linking the source files that go into the DLL and EXE components and compile only the necessary portions when you make changes to the code. You can use this file as a template when building your own applications if you use nmake.

Integrating with COM+


Just as youve put the finishing touches on your C# class library for a shiny new .NET framework application, your architect comes by and says, Thats great! It will work with our old VB business logic, right? The good new is that COM+ Interop works both waysyou can use COM+ objects from managed code, and COM+ clients can call into managed types. The bad news is that there are some restrictions because COM doesnt contain a feature set nearly as

Compiling and Linking

robust as that exposed by .NET. For example, you cannot expose an object to COM that defines a constructorCOM doesnt have constructors!

Exposing C# to COM
The easiest way to enable your managed types to be used from COM clients is by using the tlbexp utility to export a type library from the managed assembly and then registering it with the regasm utility. You have three choices for making the assembly visible: If youre going to install the assembly into the GAC, the assembly will then be available from there, and no further action is needed. If youll be using the assembly from only one application, copying the DLL to the applications directory will make it available. Otherwise, you must use regasm with the /codebase switch to store path information into the registry so the CLR can locate the DLL; this parallels the registration that happens to a native COM component, except that at runtime, the CLR marshals between COM and your managed code. Adding the following declarations to the makefile exports the library and registers it for use using the third method:
TLBNAME = Phones.tlb . . $(TLBNAME): $(LIBNAME) tlbexp $(LIBNAME) /out:$(TLBNAME) regasm /codebase $(LIBNAME)

The tlbexp and regasm utilities will export all public classes from your assembly and make them available via a programmatic identifier formed from the assembly metadata as <namespace>.<class_name>. For example, after exposing the telephone classes, the PhoneEntry is available with the PROGID Telephones.PhoneEntry. After adding a reference to the Phones type library, code such as the following could interact with the classes in Visual Basic 6.0:
Dim pe As PhoneEntry Dim pl As phonelist Set pl = New phonelist pl.Item(Jeff, Johnson) = 555.1234 MsgBox pl.Item(Jeff, Johnson) Set pe = pl.GetEntry(Jeff, Johnson) MsgBox pe.Phone Set pe = Nothing Set pl = Nothing

212 Chapter 11: Using the SDK

Of course, features such as the XML serialization in .NET arent available in this situation, so you would have to extend your code to expose that functionality if the client needs to use it.

Exposing COM to C#
If the supplier of the COM component you want to use in C# supplies a type library, the tlbimp utility can generate a wrapper assembly for you, and the component is then available to your code by referencing the wrapper assembly. To demonstrate, I created a simple COM object in unmanaged C++. I wont reproduce all the files here, but Listing 11.4 shows the Microsoft Interface Definition Language (MIDL) file, and Listing 11.5 shows the code for the method Ill invoke from C#. Listing 11.4
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: import import import import import [ object, uuid(A7C443D9-3F80-4A1E-883F-367E363FB9DB), dual, helpstring(IStringGet Interface), pointer_default(unique) ] interface IStringGet : IDispatch { [id(1),helpstring(method GetStr)] HRESULT

MIDL File Describing the BasicCom Components

D:\Progra~1\Micros~1.NET\Vc7\include\mshtml.idl; d:\Progra~1\Micros~1.NET\vc7\include\dimm.idl; D:\Progra~1\Micros~1.NET\Vc7\include\mshtmhst.idl; d:\Progra~1\Micros~1.NET\vc7\include\docobj.idl; D:\Progra~1\Micros~1.NET\Vc7\include\objsafe.idl;

GetStr([in] LONG nNumber, [out] BSTR *pStr , [out,retval] LONG *nChars);

};

[ version(1.0), uuid(C345531C-4458-45AF-8D0C-D600C17127EB), helpstring(BasicCom 1.0 Type Library) ] library BasicCom { importlib(stdole2.tlb); importlib(olepro32.dll); [ aggregatable, version(1.0), uuid(6EF6A8AA-A6D0-4281-93D9-E61178010E82), helpstring(StringGet Class) ] coclass CStringGet {

Compiling and Linking

Listing 11.4
35: 36: 37: } };

Continued

interface IStringGet;

Non-COM programmers, take heart. I show the MIDL file only for informational purposes. Lines 1418 of Listing 11.4 declare an interface called IStringGet that contains one method, GetStr(), which takes a LONG as a parameter, a BSTR * (pointer to a string) as an out parameter, and returns a LONG result. On line 23, the MIDL file declares a BasicCom library that will contain the COM components, and then line 34 declares that the CStringGet coclass will implement the IStringGet interface. Listing 11.5 The
GetStr()

Method in the COM Object

STDMETHODIMP CStringGet::GetStr(LONG nNumber, BSTR* pStr, LONG* nChars) { OLECHAR buf[32]; wsprintfW( buf, L%d, nNumber ); *nChars = lstrlenW( buf ); *pStr = SysAllocString( buf ); return S_OK; }

The GetStr() method in Listing 11.5 appears to clients to take two parameters: a number that it turns into a string representation and a reference to a string variable into which to place the result. It returns the count of characters in the string representation. To make the COM object available to C#, tlbimp needs to run against the type library for the object, and then csc can link the resulting DLL into the C# program. Listing 11.6 shows the makefile for the client project. Listing 11.6 The Clients Makefile Handles Importing and Linking the Type Library, in Addition to Compiling the C# Files
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: # makefile for COM Interop example # # 9/2001 wmr # tool options CFLAGS= LDFLAGS= # library LIBNAME = LIBNS = TLBIMP = sources; expand as necessary for multiple libraries LibBasicCom.dll /namespace:Basic BasicCom.tlb

214 Chapter 11: Using the SDK

Listing 11.6
14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34:

Continued

# main program sources EXEREF = /r:$(LIBNAME) EXESRC = BasicComClient.cs EXEMOD = BasicComClient.mod EXENAME = BasicComClient.exe # incremental link requires explicit entry point ENTRY = /Main:MainClass.Main # build rules $(EXENAME): $(LIBNAME) $(EXEMOD) makefile al $(LDFLAGS) /out:$(EXENAME) /target:exe $(ENTRY) $(EXEMOD) $(EXEMOD): $(EXESRC) makefile csc $(CFLAGS) /out:$@ /t:module $(EXEREF) $*.cs $(LIBNAME): $(TLBIMP) makefile tlbimp /out:$(LIBNAME) $(LIBNS) $(TLBIMP) clean: del $(EXENAME) $(LIBNAME) $(EXEMOD) $(LIBMOD) $(TLBNAME) tlbimp

Of particular interest in the makefile is line 31. The from this macro is as follows:

command that executes

tlbimp /out:LibBasicCom.dll /namespace:Basic BasicCom.tlb

This command creates an assembly called LibBasicCom.dll that contains wrapper classes for the classes and interfaces in BasicCom.tlb. You can choose whatever name you like for the DLL, but I recommend one that suggests what it is for. The /namespace option tells tlbimp to wrap the classes into a namespace called Basic. From the makefile, you probably suspect that a file, called BasicComClient.cs, is going to contain the client code. Listing 11.7 proves you right. Listing 11.7 C# Code Can Use the COM Object with No Further Work After the Classes Are Wrapped Up
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: /* BasicComClient.cs : demonstrate working with the tlbimp utility * * 9/2001 wmr */ using System; using Basic; class MainClass {

Debugging and Inspection

Listing 11.7
11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: }

Continued

public static void Main( string [] args ) { const int InVal = 12345; CStringGet Sg = new CStringGet(); string Str; int StrLen = Sg.GetStr( InVal, out Str ); Console.WriteLine( Passed in {0}, InVal.ToString() ); Console.WriteLine( Got the string: <{0}>, Str ); Console.WriteLine( String length: {0}, StrLen ); }

In MainClass.Main(), line 14 declares and instantiates an object of type CStringGet (the coclass of the COM object); the type information necessary comes from the namespace directive at line 7, which imports the symbols from the wrapper assembly. Line 17 invokes the method in Listing 11.5, and lines 1921 print out the results. Running this code produces the following output:
U:\>basiccomclient Passed in 12345 Got the string: <12345> String length: 5

COM+ Interop deserves a substantial book in its own right. For example, manually wrapping COM objects in C# is possible, but considerably more complex than I can cover here. You can find additional information in the .NET framework documentation. Still, this introduction should help you get started by introducing you to the technology.

Debugging and Inspection


Troubleshooting problems with your programs doesnt end with getting your code syntactically correct. The classic assertion, It compiles so it must work! is still risky, even with the help the managed environment gives you. Debuggers are still critical to productivity, but the .NET Framework SDK includes additional tools to discover solutions to problems. For debugging, the .NET Framework SDK ships with two debuggers: the command line, character-based cordbg and the GUI-based dbgclr. Both tools enable you to debug programs effectively, but the graphical dbgclr is much easier to use. For a quick introduction to its features, see Chapter 2. Also discussed in Chapter 2 is the ildasm graphical disassembly tool, which enables you to peer inside assemblies and executables by inspecting the metadata and MSIL instructions. Although initially it can be intimidating, ildasm can be valuable for a number of uses. For example, when creating the COM Interop examples, I used ildasm

216 Chapter 11: Using the SDK

after creating the wrapper assembly to verify what library.

tlbimp

created from the type

The .NET Framework SDK includes an additional tool worth mentioning because it is often overlooked. The fusion log viewer, fuslogvw, gives you a look into any problems that have occurred in the CLR during runtime fusion, when it is locating assemblies that are referenced in your code. The fusion-probing process can be complex, and by enabling you to trace it step by step, fuslogvw is invaluable, particularly in programs utilizing XML Web services or other distributed functions.

Deploying Your Solution


Although for simple applications its okay to install all your code and supporting files to one directory, youll soon find a need to break your installation up, if only for manageabilitys sake. The .NET frameworks solution is to provide the CLR with hints in application configuration files. As Chapter 10, Configuring Components and Applications, illustrated, youll find the mscorcfg.msc MMC add-in the easiest way to maintain these files. Deploying a simple standalone EXE (or one that uses only the platform and private assemblies) requires that you copy the files to the target computer. This XCOPY deployment is the optimal scenario for management and installation time, although it does use more disk space than if components could be shared among different applications. However, history suggests that except for OS-level services, shared libraries rarely are, so this could be the best solution for you. For complex applications, you can organize files by using subdirectories to house related assemblies so that your applications root directory isnt crammed with tens or hundreds of supporting files. If it cant find an assembly in your applications directory, the CLR automatically tries to help by looking for it in a subdirectory with the same name as the assembly. If your application needs an assembly named mathlib.dll, for example, the CLR will look in the applications directory, and failing to find the assembly there, it will look for mathlib\mathlib.dll. Youll usually not want to use a subdirectory for every assembly, although that can be a convenient method for managing code for maintenance. Therefore, .NET provides a second vehicle to enable you to help the CLR when it probes for assemblies. In your application configuration file, you can tell the CLR to search specific subdirectories for assemblies. The following demonstrates this technique:
<configuration> <runtime> <assemblyBinding xmlns=urn:schemas-microsoft-com:asm.v1> <probing privatePath=lib;bin /> <publisherPolicy apply=yes /> </assemblyBinding> <gcConcurrent enabled=true /> </runtime> </configuration>

Summary

This configuration file specifies the <probing privatePath=lib;bin tell the CLR to look for assemblies in the subdirectories lib and bin.

/> element to

Finally, you can deliver a library of assemblies rapidly across the network by serving them from a Web server and using configuration elements to tell the CLR where to locate them. Listing 11.8 demonstrates using a Web server to host the UsedClass.dll assembly from Chapter 2. Listing 11.8 Configuration Settings Make Central Code Repositories Available to the Application
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: <configuration> <runtime> <assemblyBinding xmlns=urn:schemas-microsoft-com:asm.v1> <probing privatePath= /> <dependentAssembly> <assemblyIdentity name=UsedClass publicKeyToken=55ae173e34ab6994 /> <bindingRedirect oldVersion=0.9.0.0 newVersion=1.0.0.0/> <codeBase version=1.0.0.0 href=http://codehost/dotnet/usedclass.dll /> </dependentAssembly> <publisherPolicy apply=yes /> </assemblyBinding> <gcConcurrent enabled=true /> </runtime> </configuration>

In Listing 11.8, the <dependentAssembly> element in lines 512 describes the component to the CLR (note the public key token; assemblies loaded this way must have a strong name). The codebase element at line 10 tells the runtime where to find version 1.0 of the component. Just for demonstration, Ive also told the CLR to map requests for a hypothetical version 0.9 to the production version 1.0 on lines 8 and 9. With this configuration file, any request by the application for the UsedClass DLL will send the CLR to the codehost Web server to retrieve the file /dotnet/ usedclass.dll. The CLR places the assembly in the download cache, performs its normal version checks, and then completes the call into the downloaded component.
UsedClass

Summary
For the first time ever, you can build Windows programs with just the SDK from Microsoft. Although you dont get the integrated set of tools from the SDK that you do from Visual Studio .NET, you do get compilers, linkers, inspection and disassembly tools, and everything else that is absolutely required to create and debug .NET programs for Microsoft Windows. The .NET Framework SDK goes beyond enabling Windows programming, though, by including the tools you need to interact with legacy code in COM objects. This capability can preserve your investment in existing

218 Chapter 11: Using the SDK

technology as you begin fielding more sophisticated applications that leverage the .NET platform.

TIP
This book has concentrated as much as possible on C#. In fact, Ive only explained .NET concepts where I felt it was necessary to fully understand a feature of the language. To continue exploring C# programming for GUI applications, good further reading is Weeks, Powell, C# and the .NET Framework, Sams Publishing, 2001. For Web applications, another good choice is McManus, Kinsman, C# Developers Guide to ASP.NET, XML, and ADO.NET, Sams Publishing, 2001.

This is the last of the techniques chapters, and if youve read the material up to here, youre well on your way to effective programming with C# and the .NET platform. To continue supporting your work, the following appendices provide references to the C# language syntax and to types that you will use often in your programs. With these references, the book should serve you well into the future.

PA R T I I I
REFERENCE
Microsoft includes a version of the Microsoft Developers Network (MSDN) Library with the .NET Framework SDK that describes the platform in great detail. Although the extensive information in the library is essential, it can be difficult to locate common information without searching for it. Part III helps you locate frequently used, basic information on C# and common library classes by presenting the information on its own, instead of inside a package describing everything about the .NET platform. Appendix A, The C# Language, contains the complete syntax of the C# language, so you can quickly look up language elements. Similarly, Appendix B, Key Types Quick Reference, presents a quick reference to commonly used library classes. A The C# Language B Key Types Quick Reference

The C# Language

APPENDIX A
The C# Language
C# is syntactically similar to C++, but it removes many features that would not work in a managed execution environment and extends the language in other ways to better support developers needs. This appendix presents a reference to the language, grouped into two parts: the basic structure for your program files, and the elements of the language you will use to write code within that structure.

Structural Elements
As do most languages, C# requires a specific structure for your code files. The following definitions describe how you organize elements of your code files for successful compilation.

NOTE
The subscripted opt tag in the following lines of code indicates that the element is optional.

S Y N TA X

VA L U E T Y P E S

value-type: struct-type enum-type struct-type: type-name simple-type simple-type: numeric-type bool numeric-type: integral-type

222 Appendix A: The C# Language floating-point-type decimal integral-type: sbyte byte short ushort int uint long ulong char floating-point-type: float double enum-type: type-name

Notes: The value types are lightweight types used for basic, commonly used data elements. Value types include the numeric and simple types in the C# language.

S Y N TA X

REFERENCE TYPES

reference-type: class-type interface-type array-type delegate-type class-type: type-name object string interface-type: type-name array-type: non-array-type rank-specifiers non-array-type: type rank-specifiers:

Structural Elements 223 rank-specifier rank-specifiers rank-specifier rank-specifier: [ dim-separatorsopt ] dim-separators: , dim-separators , delegate-type: type-name

Notes: Reference types implement more complex functionality than value types do. The reference types include classes, interfaces, arrays, and delegates.

S Y N TA X

INPUT SECTIONS

input: input-sectionopt input-section: input-section-part input-section input-section-part input-section-part: input-elementsopt new-line pp-directive input-elements: input-element input-elements input-element input-element: whitespace comment token

Notes: Input to the compiler, although it is usually formatted for readability, is treated by the compiler as a stream of whitespace, tokens, and optional comments.

S Y N TA X

L I N E T E R M I N AT O R S

new-line: Carriage return character (U+000D) Line feed character (U+000A)

224 Appendix A: The C# Language Carriage return character (U+000D) followed by line feed character (U+000A) Line separator character (U+2028) Paragraph separator character (U+2029)

Notes: Line separators are mostly ignored by the compiler, except as they delimit tokens. Line separators might not appear within a token.

S Y N TA X

W H I T E S PA C E

whitespace: Any character with Unicode class Zs Horizontal tab character (U+0009) Vertical tab character (U+000B) Form feed character (U+000C)

Notes: Whitespace is discarded by the compiler as it parses your code into tokens.

S Y N TA X

COMMENTS

comment: single-line-comment delimited-comment single-line-comment: // input-charactersopt input-characters: input-character input-characters input-character input-character: Any Unicode character except a new-line-character new-line-character: Carriage return character (U+000D) Line feed character (U+000A) Line separator character (U+2028) Paragraph separator character (U+2029) delimited-comment: /* delimited-comment-charactersopt */ delimited-comment-characters: delimited-comment-character delimited-comment-characters delimited-comment-character

Structural Elements 225 delimited-comment-character: not-asterisk * not-slash not-asterisk: Any Unicode character except * not-slash: Any Unicode character except /

Notes: Comments can occur almost anywhere in C# code. C# comments can be oneliners or delimited, as in other C++-based languages.

S Y N TA X

TOKENS

token: identifier keyword integer-literal real-literal character-literal string-literal operator-or-punctuator

Notes: Tokens are the individual lexical elements that the compiler extracts from your code. The compiler generates code after extracting tokens from your source (discarding whitespace, line separators, and comments).

S Y N TA X

UNICODE ESCAPE SEQUENCES

unicode-escape-sequence: \u hex-digit hex-digit hex-digit hex-digit \U hex-digit hex-digit hex-digit hex-digit hex-digit hex-digit hex-digit hex-digit

Notes: You use escape sequences to place otherwise inaccessible characters in your strings. You can also use C++-style escapes in addition to the Unicode styles, as the following example demonstrates:
String s = This is\x0d\x0a a string.; String s2 = A UNC name: \\\\server\\sharename;

This code creates s with a carriage return/line-feed pair embedded and s2 with the value A UNC name: \\server\sharename.

226 Appendix A: The C# Language

S Y N TA X

IDENTIFIERS

identifier: available-identifier @ identifier-or-keyword available-identifier: An identifier-or-keyword that is not a keyword identifier-or-keyword: identifier-start-character identifier-part-charactersopt identifier-start-character: letter-character _ (the underscore character) identifier-part-characters: identifier-part-character identifier-part-characters identifier-part-character identifier-part-character: letter-character decimal-digit-character connecting-character combining-character formatting-character letter-character: A Unicode character of classes Lu, Ll, Lt, Lm, Lo, or Nl A unicode-escape-sequence representing a character of classes Lu, Ll, Lt, Lm, Lo, or Nl combining-character: A Unicode character of classes Mn or Mc A unicode-escape-sequence representing a character of classes Mn or Mc decimal-digit-character: A Unicode character of the class Nd A unicode-escape-sequence representing a character of the class Nd connecting-character: A Unicode character of the class Pc A unicode-escape-sequence representing a character of the class Pc formatting-character: A Unicode character of the class Cf A unicode-escape-sequence representing a character of the class Cf

Structural Elements 227

Notes: Identifiers are the names you use to refer to objects, methods, or other constructs in your code. The rules for identifiers govern what is legal for naming.

S Y N TA X

KEYWORDS

keyword: one of 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

Notes: Keywords are the reserved words in C#. You may not use any of these words as an identifier.

S Y N TA X

LITERALS

literal: boolean-literal integer-literal real-literal character-literal string-literal null-literal boolean-literal: true false integer-literal: decimal-integer-literal hexadecimal-integer-literal decimal-integer-literal: decimal-digits integer-type-suffixopt

228 Appendix A: The C# Language decimal-digits: decimal-digit decimal-digits decimal-digit decimal-digit: one of 0 1 2 3 4 5 6 7 8 9 integer-type-suffix: one of U u L l UL Ul uL ul LU Lu lU lu hexadecimal-integer-literal: 0x hex-digits integer-type-suffixopt 0X hex-digits integer-type-suffixopt hex-digits: hex-digit hex-digits hex-digit hex-digit: one of 0 1 2 3 4 5 6 7 8 9 A B C D E F a b c d e f real-literal: decimal-digits . decimal-digits exponent-partopt real-type-suffixopt . decimal-digits exponent-partopt real-type-suffixopt decimal-digits exponent-part real-type-suffixopt decimal-digits real-type-suffix exponent-part: e signopt decimal-digits E signopt decimal-digits sign: one of + real-type-suffix: one of F f D d M m character-literal: character character: single-character simple-escape-sequence hexadecimal-escape-sequence unicode-escape-sequence single-character: Any character except (U+0027), \ (U+005C), and new-line-character

Structural Elements 229 simple-escape-sequence: one of \ \ \\ \0 \a \b \f \n \r \t \v hexadecimal-escape-sequence: \x hex-digit hex-digitopt hex-digitopt hex-digitopt string-literal: regular-string-literal verbatim-string-literal regular-string-literal: regular-string-literal-charactersopt regular-string-literal-characters: regular-string-literal-character regular-string-literal-characters regular-string-literal-character regular-string-literal-character: single-regular-string-literal-character hexadecimal-escape-sequence unicode-escape-sequence single-regular-string-literal-character: Any character except (U+0022), \ (U+005C), and new-line-character verbatim-string-literal: @ verbatim -string-literal-charactersopt verbatim-string-literal-characters: verbatim-string-literal-character verbatim-string-literal-characters verbatim-string-literal-character verbatim-string-literal-character: single-verbatim-string-literal-character quote-escape-sequence single-verbatim-string-literal-character: any character except quote-escape-sequence: null-literal: null

Notes: Literals are constant values you place in your code, such as a string value or the integer 1500. Literals in C# are objects themselves, enabling you to call methods

230 Appendix A: The C# Language

on them. For example, abcd.Substring(2) returns cd by calling the String.Substring( int32 ) method on the literal abcd.

S Y N TA X

O P E R AT O R S A N D P U N C T U AT O R S

operator-or-punctuator: one of { } [ ] ( ) . , : ; + - * / % & | ^ ! ~ = < > ? ++ -- && || << == != <= >= += -= *= /= |= ^= <<= >>= ->

>> %=

&=

Notes: Relevant operators are predefined by the .NET framework for most built-in types. Operators that you can overload on your own classes are listed as overloadableunary-operator and overloadable-binary-operator in the specification for classes.

S Y N TA X

PREPROCESSING DIRECTIVES

pp-directive: pp-declaration pp-conditional pp-line pp-diagnostic pp-region pp-new-line: whitespaceopt single-line-commentopt new-line conditional-symbol: Any identifier-or-keyword except true or false pp-expression: whitespaceopt pp-or-expression whitespaceopt pp-or-expression: pp-and-expression pp-or-expression whitespaceopt || whitespaceopt pp-and-expression pp-and-expression: pp-equality-expression pp-and-expression whitespaceopt && whitespaceopt pp-equalityexpression pp-equality-expression: pp-unary-expression pp-equality-expression whitespaceopt == whitespaceopt pp-unary-

Structural Elements 231 expression pp-equality-expression whitespaceopt != whitespaceopt pp-unaryexpression pp-unary-expression: pp-primary-expression ! whitespaceopt pp-unary-expression pp-primary-expression: true false conditional-symbol ( pp-expression ) pp-declaration: whitespaceopt # whitespaceopt define whitespace conditional-symbol pp-new-line whitespaceopt # whitespaceopt undef whitespace conditional-symbol pp-new-line pp-conditional: pp-if-section pp-elif-sectionsopt pp-else-sectionopt pp-endif pp-if-section: whitespaceopt # whitespaceopt if pp-expression pp-new-line conditional-sectionopt pp-elif-sections: pp-elif-section pp-elif-sections pp-elif-section pp-elif-section: whitespaceopt # whitespaceopt elif pp-expression pp-new-line conditional-sectionopt else-section: whitespaceopt # whitespaceopt else pp-new-line conditional-sectionopt endif-line: whitespaceopt # whitespaceopt endif pp-new-line conditional-section: input-section skipped-section skipped-section: skipped-section-part skipped-section skipped-section-part

232 Appendix A: The C# Language skipped-section-part: skipped-charactersopt new-line pp-directive skipped-characters: whitespaceopt not-number-sign input-charactersopt not-number-sign: Any input-character except # pp-line: whitespaceopt # whitespaceopt line whitespaceopt line-indicator ppnew-line whitespaceopt # whitespaceopt line defaultline-indicator: integer-literal whitepaceopt file-nameopt file-name: file-name-characters file-name-characters: file-name-character file-name-characters file-name-character file-name-character: Any input-character except pp-diagnostic: whitespaceopt # whitespaceopt error whitespaceopt pp-message whitespaceopt # whitespaceopt warning whitespaceopt pp-message pp-message: input-charactersopt new-line pp-region: pp-start-region conditional-sectionopt pp-end-region pp-start-region: whitespaceopt # whitespaceopt region whitespaceopt pp-message pp-end-region: whitespaceopt # whitespaceopt endregion whitespaceopt pp-message

Notes: Preprocessor directives in C# only enable conditional compilation; they do not include the macro or token-pasting features of C++. Preprocessor symbols to control the compilation can be defined using #define on the compilers command line using the /D option or in the project properties in Visual Studio .NET.

Coding Elements 2 3 3

S Y N TA X

BASIC CONCEPTS

namespace-name: namespace-or-type-name type-name: namespace-or-type-name namespace-or-type-name: identifier namespace-or-type-name . identifier

Notes: A namespace declares a scope for name resolution. You can explicitly identify a namespace for an item that exists outside your current namespace using the usual dotted notation, as in the following example:
namespace X { class AClass { void methodA() { ; } } } namespace Y { class BClass { public BClass() { X.AClass ac = new X.AClass(); AClass ac = new AClass(); } } }

// works fine // will not compile!

Coding Elements
Within the general program structure, you will create the bulk of your application by writing code in C#. The preceding section described the physical structure; the following descriptions detail the syntax of the language you use within that structure.

234 Appendix A: The C# Language

S Y N TA X

TYPES

type: value-type reference-type value-type: struct-type enum-type struct-type: type-name simple-type simple-type: numeric-type bool numeric-type: integral-type floating-point-type decimal integral-type: sbyte byte short ushort int uint long ulong char floating-point-type: float double enum-type: type-name reference-type: class-type interface-type array-type delegate-type class-type: type-name

Coding Elements 235 object string interface-type: type-name array-type: non-array-type rank-specifiers non-array-type: type rank-specifiers: rank-specifier rank-specifiers rank-specifier rank-specifier: [ dim-separatorsopt ] dim-separators: , dim-separators , delegate-type: type-name

Notes: Please see the discussion of types in Chapter 1, Language Elements.

S Y N TA X

VA R I A B L E S

variable-reference: expression

Notes: Variable declarations are described in Statements section later in this appendix. A variable reference occurs when you use a variable in an expression or argument list.

S Y N TA X

EXPRESSIONS

argument-list: argument argument-list , argument argument: expression ref variable-reference out variable-reference

236 Appendix A: The C# Language primary-expression: array-creation-expression primary-expression-no-array-creation primary-expression-no-array-creation: literal simple-name parenthesized-expression member-access invocation-expression element-access this-access base-access post-increment-expression post-decrement-expression new-expression typeof-expression sizeof-expression checked-expression unchecked-expression simple-name: identifier parenthesized-expression: ( expression ) member-access: primary-expression . identifier predefined-type . identifier predefined-type: one of bool byte char decimal double float int long object sbyte short string uint ulong ushort invocation-expression: primary-expression ( argument-listopt ) element-access: primary-expression [ expression-list ] expression-list: expression expression-list , expression this-access: this

Coding Elements 237 base-access: base . identifier base [ expression-list ] post-increment-expression: primary-expression ++ post-decrement-expression: primary-expression -new-expression: object-creation-expression array-creation-expression delegate-creation-expression object-creation-expression: new type ( argument-listopt ) array-creation-expression: new non-array-type [ expression-list ] rank-specifiersopt array-initializeropt new array-type array-initializer delegate-creation-expression: new delegate-type ( expression ) typeof-expression: typeof ( type ) typeof ( void ) checked-expression: checked ( expression ) unchecked-expression: unchecked ( expression ) unary-expression: primary-expression + unary-expression - unary-expression ! unary-expression ~ unary-expression * unary-expression pre-increment-expression pre-decrement-expression cast-expression pre-increment-expression: ++ unary-expression

238 Appendix A: The C# Language pre-decrement-expression: -- unary-expression cast-expression: ( type ) unary-expression multiplicative-expression: unary-expression multiplicative-expression * unary-expression multiplicative-expression / unary-expression multiplicative-expression % unary-expression additive-expression: multiplicative-expression additive-expression + multiplicative-expression additive-expression multiplicative-expression shift-expression: additive-expression shift-expression << additive-expression shift-expression >> additive-expression relational-expression: shift-expression relational-expression relational-expression relational-expression relational-expression relational-expression relational-expression

< shift-expression > shift-expression <= shift-expression >= shift-expression is type as type

equality-expression: relational-expression equality-expression == relational-expression equality-expression != relational-expression and-expression: equality-expression and-expression & equality-expression exclusive-or-expression: and-expression exclusive-or-expression ^ and-expression inclusive-or-expression: exclusive-or-expression inclusive-or-expression | exclusive-or-expression

Coding Elements 239 conditional-and-expression: inclusive-or-expression conditional-and-expression && inclusive-or-expression conditional-or-expression: conditional-and-expression conditional-or-expression || conditional-and-expression conditional-expression: conditional-or-expression conditional-or-expression ? expression : expression assignment: unary-expression assignment-operator expression assignment-operator: one of = += -= *= /= %= &= |= ^= <<= >>= expression: conditional-expression assignment constant-expression: expression boolean-expression: expression

Notes: Expressions are evaluated to produce either a value or a reference. For order of operations and other information on expressions, see Chapter 1, Language Elements.

S Y N TA X

S TAT E M E N T S

statement: labeled-statement declaration-statement embedded-statement embedded-statement: block empty-statement expression-statement selection-statement iteration-statement jump-statement try-statement checked-statement

240 Appendix A: The C# Language unchecked-statement lock-statement using-statement block: { statement-listopt } statement-list: statement statement-list statement empty-statement: ; labeled-statement: identifier : statement declaration-statement: local-variable-declaration ; local-constant-declaration ; local-variable-declaration: type variable-declarators variable-declarators: variable-declarator variable-declarators , variable-declarator variable-declarator: identifier identifier = variable-initializer variable-initializer: expression array-initializer local-constant-declaration: const type constant-declarators constant-declarators: constant-declarator constant-declarators , constant-declarator constant-declarator: identifier = constant-expression expression-statement: statement-expression ;

Coding Elements 241 statement-expression: invocation-expression object-creation-expression assignment post-increment-expression post-decrement-expression pre-increment-expression pre-decrement-expression selection-statement: if-statement switch-statement if-statement: if ( boolean-expression ) embedded-statement if ( boolean-expression ) embedded-statement else embedded-statement boolean-expression: expression switch-statement: switch ( expression ) switch-block switch-block: { switch-sectionsopt } switch-sections: switch-section switch-sections switch-section switch-section: switch-labels statement-list switch-labels: switch-label switch-labels switch-label switch-label: case constant-expression : default : iteration-statement: while-statement do-statement for-statement foreach-statement while-statement: while ( boolean-expression ) embedded-statement

242 Appendix A: The C# Language do-statement: do embedded-statement while ( boolean-expression ) ; for-statement: for ( for-initializeropt ; for-conditionopt ; for-iteratoropt ) embedded-statement for-initializer: local-variable-declaration statement-expression-list for-condition: boolean-expression for-iterator: statement-expression-list statement-expression-list: statement-expression statement-expression-list , statement-expression foreach-statement: foreach ( type identifier in expression ) embedded-statement jump-statement: break-statement continue-statement goto-statement return-statement throw-statement break-statement: break ; continue-statement: continue ; goto-statement: goto identifier ; goto case constant-expression ; goto default ; return-statement: return expressionopt ; throw-statement: throw expressionopt ;

Coding Elements 243 try-statement: try block catch-clauses try block finally-clause try block catch-clauses finally-clause catch-clauses: specific-catch-clauses general-catch-clauseopt specific-catch-clausesopt general-catch-clause specific-catch-clauses: specific-catch-clause specific-catch-clauses specific-catch-clause specific-catch-clause: catch ( class-type identifieropt ) block general-catch-clause: catch block finally-clause: finally block checked-statement: checked block unchecked-statement: unchecked block lock-statement: lock ( expression ) embedded-statement using-statement: using ( resource-acquisition ) embedded-statement resource-acquisition: local-variable-declaration expression compilation-unit: using-directivesopt attributesopt namespace-member-declarationsopt namespace-declaration: namespace qualified-identifier namespace-body ;opt qualified-identifier: identifier qualified-identifier . identifier

244 Appendix A: The C# Language namespace-body: { using-directivesopt namespace-member-declarationsopt } using-directives: using-directive using-directives using-directive using-directive: using-alias-directive using-namespace-directive using-alias-directive: using identifier = namespace-or-type-name ; using-namespace-directive: using namespace-name ; namespace-member-declarations: namespace-member-declaration namespace-member-declarations namespace-member-declaration namespace-member-declaration: namespace-declaration type-declaration type-declaration: class-declaration struct-declaration interface-declaration enum-declaration delegate-declaration

Notes: Most statements are delimited by semicolons; however, declarators such as those for namespaces and classes are not.

S Y N TA X

CLASSES

class-declaration: attributesopt class-modifiersopt class identifier class-baseopt class-body ;opt class-modifiers: class-modifier class-modifiers class-modifier class-modifier: new public

Coding Elements 245 protected internal private abstract

class-base: : class-type : interface-type-list : class-type , interface-type-list interface-type-list: interface-type interface-type-list , interface-type class-body: { class-member-declarationsopt } class-member-declarations: class-member-declaration class-member-declarations class-member-declaration class-member-declaration: constant-declaration field-declaration method-declaration property-declaration event-declaration indexer-declaration operator-declaration constructor-declaration destructor-declaration static-constructor-declaration type-declaration constant-declaration: attributesopt constant-modifiersopt const type constant-declarators ; constant-modifiers: constant-modifier constant-modifiers constant-modifier constant-modifier: new public protected internal private

246 Appendix A: The C# Language constant-declarators: constant-declarator constant-declarators , constant-declarator constant-declarator: identifier = constant-expression field-declaration: attributesopt field-modifiersopt type variable-declarators ; field-modifiers: field-modifier field-modifiers field-modifier field-modifier: new public protected internal private static readonly variable-declarators: variable-declarator variable-declarators , variable-declarator variable-declarator: identifier identifier = variable-initializer variable-initializer: expression array-initializer method-declaration: method-header method-body method-header: attributesopt method-modifiersopt return-type member-name ( formal-parameter-listopt ) method-modifiers: method-modifier method-modifiers method-modifier method-modifier: new public

Coding Elements 247 protected internal private static virtual sealed override abstract extern return-type: type void member-name: identifier interface-type . identifier method-body: block ; formal-parameter-list: fixed-parameters fixed-parameters , parameter-array parameter-array fixed-parameters: fixed-parameter fixed-parameters , fixed-parameter fixed-parameter: attributesopt parameter-modifieropt type identifier parameter-modifier: ref out parameter-array: attributesopt params array-type identifier property-declaration: attributesopt property-modifiersopt type member-name { accessor-declarations } property-modifiers: property-modifier property-modifiers property-modifier

248 Appendix A: The C# Language property-modifier: new public protected internal private static virtual sealed override abstract extern member-name: identifier interface-type . identifier accessor-declarations: get-accessor-declaration set-accessor-declarationopt set-accessor-declaration get-accessor-declarationopt get-accessor-declaration: attributesopt get accessor-body set-accessor-declaration: attributesopt set accessor-body accessor-body: block ; event-declaration: attributesopt event-modifiersopt event type variable-declarators ; attributesopt event-modifiersopt event type member-name { event-accessor-declarations } event-modifiers: event-modifier event-modifiers event-modifier event-modifier: new public protected internal static virtual sealed override

Coding Elements 249 abstract extern event-accessor-declarations: add-accessor-declaration remove-accessor-declaration remove-accessor-declaration add-accessor-declaration add-accessor-declaration: attributesopt add block remove-accessor-declaration: attributesopt remove block indexer-declaration: attributesopt indexer-modifiersopt indexer-declarator { accessor-declarations } indexer-modifiers: indexer-modifier indexer-modifiers indexer-modifier indexer-modifier: new public protected internal private virtual sealed override abstract indexer-declarator: type this [ formal-parameter-list ] type interface-type . this [ formal-parameter-list ] operator-declaration: attributesopt operator-modifiers operator-declarator operator-body operator-modifiers: operator-modifier operator-modifiers operator-modifier operator-modifier: public static extern

250 Appendix A: The C# Language operator-declarator: unary-operator-declarator binary-operator-declarator conversion-operator-declarator operator-body: block ; unary-operator-declarator: type operator overloadable-unary-operator ( type identifier ) overloadable-unary-operator: one of + - ! ~ ++ -- true false binary-operator-declarator: type operator overloadable-binary-operator ( type identifier , type identifier ) overloadable-binary-operator: one of + - * / % & | ^ << >> == != > < >= <= conversion-operator-declarator: implicit operator type ( type identifier ) explicit operator type ( type identifier ) constructor-declaration: attributesopt constructor-modifiersopt constructor-declarator block constructor-modifiers: constructor-modifier constructor-modifiers constructor-modifier constructor-modifier: public protected internal private extern constructor-declarator: identifier ( formal-parameter-listopt ) constructor-initializeropt constructor-initializer: : base ( argument-listopt ) : this ( argument-listopt )

Coding Elements 251 static-constructor-declaration: attributesopt static identifier ( ) block destructor-declaration: attributesopt ~ identifier ( ) block

Notes: Classes are a core feature of the C# language. C# supports single inheritance, with interface implementation to enable polymorphism. Classes are not restricted to inheritance within the same project; subject to visibility modifiers, they can inherit from any other class in a .NET assembly that is visible to the compiler.

S Y N TA X

STRUCTURES

struct-declaration: attributesopt struct-modifiersopt struct identifier structinterfacesopt struct-body ;opt struct-modifiers: struct-modifier struct-modifiers struct-modifier struct-modifier: new public protected internal private struct-interfaces: : interface-type-list struct-body: { struct-member-declarationsopt } struct-member-declarations: struct-member-declaration struct-member-declarations struct-member-declaration struct-member-declaration: constant-declaration field-declaration method-declaration property-declaration event-declaration indexer-declaration operator-declaration

252 Appendix A: The C# Language constructor-declaration static-constructor-declaration type-declaration

Notes: Structures are similar in many ways to classes, but they are value types. Because they are allocated on the stack, you do not use new to instantiate a struct variable; simply declaring it is sufficient.

S Y N TA X

A R R AY S

array-type: non-array-type rank-specifiers non-array-type: type rank-specifiers: rank-specifier rank-specifiers rank-specifier rank-specifier: [ dim-separatorsopt ] dim-separators: , dim-separators , array-initializer: { variable-initializer-listopt } { variable-initializer-list , } variable-initializer-list: variable-initializer variable-initializer-list , variable-initializer variable-initializer: expression array-initializer

Notes: Array rank (number of dimensions) is set at the point of declaration; however, the final shape is not determined until it is initialized. For example, int [] ary; defines a one-dimensional array, but not the size. The size is determined either by an array initializer in the declaration or later initialization by using new, as in int [] ar = new int[100];.

Coding Elements 253

S Y N TA X

I N T E R FA C E S

interface-declaration: attributesopt interface-modifiersopt interface identifier interface-baseopt interface-body ;opt interface-modifiers: interface-modifier interface-modifiers interface-modifier interface-modifier: new public protected internal private interface-base: : interface-type-list interface-body: { interface-member-declarationsopt } interface-member-declarations: interface-member-declaration interface-member-declarations interface-member-declaration interface-member-declaration: interface-method-declaration interface-property-declaration interface-event-declaration interface-indexer-declaration interface-method-declaration: attributesopt newopt return-type identifier ( formal-parameter-listopt ) ; interface-property-declaration: attributesopt newopt type identifier { interface-accessors } interface-accessors: attributesopt get attributesopt set attributesopt get attributesopt set

; ; ; attributesopt set ; ; attributesopt get ;

interface-event-declaration: attributesopt newopt event type identifier ;

254 Appendix A: The C# Language interface-indexer-declaration: attributesopt newopt type this [ formal-parameter-list ] { interface-accessors }

Notes: Interfaces implement polymorphism in C#. An interface can include method, event, property and indexer declarations, but it cannot declare elements that allocate storage or provide runtime behavior, such as fields or methods or property bodies.

S Y N TA X

ENUMS

enum-declaration: attributesopt enum-modifiersopt enum identifier enum-baseopt enum-body ;opt enum-base: : integral-type enum-body: { enum-member-declarationsopt } { enum-member-declarations , } enum-modifiers: enum-modifier enum-modifiers enum-modifier enum-modifier: new public protected internal private enum-member-declarations: enum-member-declaration enum-member-declarations , enum-member-declaration enum-member-declaration: attributesopt identifier attributesopt identifier = constant-expression

Notes: Enumerated types can provide sensible names for related sets of constant values when the members of the set can be identified at compile time. Common types are colors, device settings, and so forth. The following demonstrates using an enum:

Coding Elements 255 enum Periods { Morning, Noon, Night }; class EnumDemo { // create a field of the enumerated type Periods p; public void EnumDemo() { // initialize the field p = Periods.Morning; } }

S Y N TA X

D E L E G AT E S

delegate-declaration: attributesopt delegate-modifiersopt delegate return-type identifier ( formal-parameter-listopt ) ; delegate-modifiers: delegate-modifier delegate-modifiers delegate-modifier delegate-modifier: new public protected internal private

Notes: Delegates are a unique feature of C# that provide native support to the event source/event sink programming model that is ubiquitous in modern programming environments. See Chapter 1, Language Elements, and Chapter 7, Advanced Program Control, for samples of delegate use.

S Y N TA X

AT T R I B U T E S

attributes: attribute-sections attribute-sections: attribute-section attribute-sections attribute-section attribute-section: [ attribute-target-specifieropt attribute-list ] [ attribute-target-specifieropt attribute-list ,]

256 Appendix A: The C# Language attribute-target-specifier: attribute-target : attribute-target: assembly field event method module param property return type attribute-list: attribute attribute-list , attribute attribute: attribute-name attribute-argumentsopt attribute-name: type-name attribute-arguments: ( positional-argument-listopt ) ( positional-argument-list , named-argument-list ) ( named-argument-list ) positional-argument-list: positional-argument positional-argument-list , positional-argument positional-argument: attribute-argument-expression named-argument-list: named-argument named-argument-list , named-argument named-argument: identifier = attribute-argument-expression attribute-argument-expression: expression

Coding Elements 257

Notes: A key feature of .NET is its intrinsic usage of metadata throughout the execution environment. C# includes attributes to enable you to use system-defined attribute types, and also to define your own. Because attributes are full-fledged types in C#, you can extend the type system easily by declaring your own classes derived from System.Attribute.

S Y N TA X

UNSAFE CODE

type: value-type reference-type pointer-type pointer-type: unmanaged-type * void * unmanaged-type: type primary-expression-no-array-creation: ... pointer-member-access pointer-element-access sizeof-expression unary-expression: ... pointer-indirection-expression addressof-expression pointer-indirection-expression: * unary-expression pointer-member-access: primary-expression -> identifier pointer-element-access: primary-expression [ expression ] addressof-expression: & unary-expression sizeof-expression: sizeof ( unmanaged-type )

258 Appendix A: The C# Language embedded-statement: ... fixed-statement fixed-statement: fixed ( pointer-type fixed-pointer-declarators ) embedded-statement fixed-pointer-declarators: fixed-pointer-declarator fixed-pointer-declarators , fixed-pointer-declarator fixed-pointer-declarator: identifier = fixed-pointer-initializer fixed-pointer-initializer: & variable-reference expression variable-initializer: expression array-initializer stackalloc-initializer stackalloc-initializer: stackalloc unmanaged-type [ expression ]

Notes: Unsafe code, sometimes called inline C, gives you the capability to implement code that is for whatever reason incompatible with the managed environment. Unsafe code is restricted to a small subset of the functionality of C, however, and because unsafe code can produce reliability problems and must be fully trusted, you should only use unsafe code when absolutely necessary.

Key Types Quick Reference

APPENDIX B
Key Types Quick Reference
The .NET framework contains an extensive class library enabling everything from command-line I/O to sophisticated multichannel Web programming. However, its very size can make it intimidating. Therefore, this quick reference presents types and members that youre likely to use often, and should make it easier to work with these key types from the Framework Class Library.

ApplicationException
ApplicationException

Class

is a base class intended to support application-defined subclassing. Your application can create subclasses for exceptions that are meaningful in your program. This class is not used by the CLR.

ApplicationException Constructor
ApplicationException()

Initializes an empty exception.


ApplicationException(String msg)

Initializes with a given error message.


ApplicationException(String msg, Exception e)

Initializes with an error message and an inner (changed) exception.

ArgumentOutOfRangeException

Class
ArgumentOutOfRangeException is

used principally to indicate indices that exceed the allowable range, such as for array subscripts or offset parameters in I/O seek operations.

2 6 0 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e

ArgumentOutOfRangeException Constructor
ArgumentOutOfRangeException(String msg)

Initializes with a given error message.


ArgumentOutOfRangeException(String msg, String parm)

Initializes with the name of the parameter that was out of range and an error message.
ArgumentOutOfRangeException(String msg, Object val, String parm)

Initializes with the name of the parameter that was out of range, the value that was passed in for the parameter, and an error message.

Properties
object ActualValue Property (read-only)

Provides the value of the argument that was out of range.


string Message Property (read-only)

Provides the message describing the error.

ArithmeticException
ArithmeticException

Class

is the base class for dividing by zero, overflow, and NAN

exceptions.

ArithmeticException Constructor
ArithmeticException()

Initializes an empty exception.


ArithmeticException(String msg)

Initializes with a given error message.


ArithmeticException(String msg, Exception e)

Initializes with an error message and an inner (chained) exception.

Array
Array

Class
Property (read-only)

is an abstract type that serves as the base class for array types in the library.

Properties
bool IsFixedSize

Always true, indicating a fixed length.


bool IsReadOnly

Property (read-only)

Always false, indicating read/write accessibility.

Array Class 261

bool IsSynchronized

Property (read-only)

Always false because Array access is not synchronized.


int Length

Property (read-only)

The number of elements in the array.


int Rank

Property (read-only)

The number of dimensions in the array.


object SyncRoot Object

Property (read-only)

on which calling code can synchronize.

Methods
static int BinarySearch()

Method (overloaded)

Performs a binary search on a one-dimensional array; optionally, calling code can scope the search to part of the array or provide a custom IComparer object to modify the search. Prototypes:
static int BinarySearch( static int BinarySearch( static int BinarySearch( val ) static int BinarySearch( val, IComparer comp ) Array a, object val) Array a, object val, IComparer comp ) Array a, int start, int length, object Array a, int start, int length, object

static void Clear(array a, int start, int len)

Method

Clears len elements in a starting at index start.


object Clone()

Method

Creates a shallow copy of the array.


static void Copy()

Method (overloaded)

Copies one array to another, optionally starting at an offset in each array:


static void Copy( array source, array dest, int len) static void Copy( array source, int src_ofs, array dest, int dest_ofs, int len)

void CopyTo(Array a, int ofs)

Method

Copies this array to another, starting at an offset in the destination array.

2 6 2 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e static Array CreateInstance()

Method (overloaded)

Creates an array of a given type:


static Array CreateInstance( Type t, int len)

Creates a vector of Type elements.


static Array CreateInstance( Type t, int[] dims)

Creates array of given dimensions with zero bases.


static Array CreateInstance( Type t, int[] dims, int[] bases)

Creates array with given dimensions and corresponding bases.


static Array CreateInstance( Type t, int x_len, int y_len)

Creates a two-dimensional array.


static Array CreateInstance( Type t, int x_len, int y_len, int z_len)

Creates a three-dimensional array.


IEnumerator GetEnumerator()

Method

Gets an enumerator for the array; youll most often access this member indirectly through the foreach statement.
int GetLength(int dim)

Method

Retrieves the number of elements in the given dimension, in which 0 indicates the first dimension.
int GetLowerBound(int dim)

Method

Retrieves the base of the given dimension.


int GetUpperbound(int dim)

Method

Retrieves the maximum index of the given dimension.


object GetValue()

Method (overloaded)

Retrieves the value identified by index(es) in the array:


object object object object GetValue(int GetValue(int GetValue(int GetValue(int idx) x, int y) x, int y, int z) [] coord)

Retrieves the value indicated by the coordinate vector.


static int IndexOf()

Method (overloaded)

Retrieves the index of an object from a one-dimensional array:


int IndexOf(Array a, Object search_obj) int IndexOf(Array a, Object search_obj, int start_ofs) int IndexOf(Array a, Object search_obj, int start_ofs, int range)

Attribute Class 263

static int LastIndexOf()

Method (overloaded)

Retrieves the index of the last occurrence of an object from a one-dimensional array:
int LastIndexOf(Array a, Object search_obj) int LastIndexOf(Array a, Object search_obj, int start_ofs) int LastIndexOf(Array a, Object search_obj, int start_ofs, int range)

static void Reverse()

Method (overloaded)

Reverses the elements in all or a part of an array:


static void Reverse(Array a) static void Reverse(Array a, int ofs, int range)

void SetValue()

Method (overloaded)

Sets the value at a particular location in an array:


void void void void SetValue(object SetValue(object SetValue(object SetValue(object o, o, o, o, int int int int x) x, int y) x, int y, int z) [] coord)

static void Sort()

Method (overloaded)

Sorts an array using a number of () Methods to control what is sorted, optionally using a custom IComparer object. Using standard comparisons:
static static static static void void void void Sort(Array Sort(Array Sort(Array Sort(Array a) keys, Array values) a, int start, int range) keys, Array values, int start, int range)

Using custom comparison:


static void Sort(Array static void Sort(Array static void Sort(Array static void Sort(Array IComparer comp) a, IComparer comp) keys, Array values, IComparer comp) a, int start, int range, IComparer comp) keys, Array values, int start, int range

Attribute Properties
object TypeId

Class

Defines the base class for attributes in the .NET environment.

Property (read-only)

Returns a unique identifier to compare between attributes; two attributes with the same type ID are the same type.

2 6 4 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e

Methods
static Attribute GetCustomAttribute()

Method (overloaded)

Retrieves an attribute of a specified type if it is applied to the specified element; optionally searches ancestor types. This type only:
static Attribute static Attribute attrib_type) static Attribute static Attribute attrib_type) GetCustomAttribute(Assembly a, Type attrib_type) GetCustomAttribute(MemberInfo member, Type GetCustomAttribute(Module mod, Type attrib_type) GetCustomAttribute(ParameterInfo param, Type

Optionally search inherited:


static Attribute GetCustomAttribute(Assembly a, Type attrib_type, bool inherit) static Attribute GetCustomAttribute(MemberInfo member, Type attrib_type, bool inherit) static Attribute GetCustomAttribute(Module mod, Type attrib_type, bool inherit)

static Attribute [] GetCustomAttributes() GetCustomAttributes()

Method (overloaded)

retrieves an array of the attributes applied to an element. The () Method can take one, two, or three parameters, in one of the following forms:
static Attribute [] GetCustomAttribute(element) static Attribute [] GetCustomAttribute(element, inherit) static Attribute [] GetCustomAttribute(element, filter) static Attribute [] GetCustomAttribute(element, filter, inherit)

in which Token element filter bool


int GetHashCode()

Is One of Assembly, MemberInfo, Module, ParameterInfo. Any attribute type; the result will include only instances of this type or subclasses of it.
true or false indicating whether to include attributes of base classes.

Method

Retrieves hash code for this instance.

AttributeTargets Enumeration 265

bool IsDefaultAttribute()

Method

Reports whether the value of this attribute is equal to the default for this type.
static bool IsDefined()

Method

Reports whether any attributes of a given type are applied to an element. This type only:
static static static static bool bool bool bool IsDefined( IsDefined( IsDefined( IsDefined( Assembly a, Type attrib_type ) MemberInfo mi, Type attrib_type ) Module mod, Type attrib_type ) ParameterInfo pi, Type attrib_type )

Optionally search parents:


static bool inherit ) static bool inherit ) static bool inherit ) static bool inherit ) IsDefined( Assembly a, Type attrib_type, bool IsDefined( MemberInfo mi, Type attrib_type, bool IsDefined( Module mod, Type attrib_type, bool IsDefined( ParameterInfo pi, Type attrib_type, bool

AttributeTargets

Enumeration

Identifies the target against which an attribute is applied. The members are any one of the following:
All Assembly Class Constructor Delegate Enum Event Field Interface Method Module Parameter Property ReturnValue Struct

2 6 6 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e

AttributeUsageAttribute Properties
bool AllowMultiple

Class

Applied to custom attribute declarations to identify how an attribute is to be used.

Property (read/write)

Controls whether the attribute can be applied to the same element multiple times.
bool Inherited

Property (read/write)

Controls if the attribute can be inherited.


ValidOn Property

(read-only)

Retrieves the AttributeTargets value indicating the elements on which this attribute is valid.

BitConverter Fields
bool IsLittleEndian

Class

Facilitates converting base types to and from byte sequences.

Field (read-only)

Indicates if the host computer architectures byte ordering is little-endian.

Methods
static long DoubleToInt64Bits(double val)

Method

Converts a double to a long.


static byte [] GetBytes(var)

Method (overloaded)
var

Returns a byte array containing the underlying bytes for a lowing types:
bool char double float int long short uint ulong ushort

of any one of the fol-

Int64BitsToDouble()

Method

Converts a long to a double.

Boolean Structure 267

Type ToType(byte[] bytes, int ofs)

Method (overloaded)
ofs

Returns a value of a base type Type derived from the bytes at which Type is one of the following:
Boolean Char Double Int16 Int32 Int64 Single UInt16 UInt32 Int64

in array

bytes,

in

Because a string is special in that it can be of a variable length, there is an additional ToString() Method:
static string ToString(byte [] bytes, int ofs, int range)

This () Method creates a string from range bytes starting at ofs in array bytes.

Boolean Fields
FalseString

Structure

Encapsulates the bool base type.

Field (read-only)

Returns the string False.


TrueString

Field (read-only)

Returns the string True.

Methods
int CompareTo(object o)

Method

Reports a comparison between this object and another. Returns a negative value if this object is false and the other is true; positive if this object is true and the other is false or null; and returns 0 if both are equal.
bool Equals(object o)

Method

Returns true if o is Boolean and contains the same value as this object.
int GetHashCode()

Method

Returns one if this instance is true, or 0 if false.

2 6 8 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e TypeCode GetTypeCode()

Method

Returns System.TypeCode.Boolean.
static bool Parse(string s)

Method

Returns true if s contains only TrueString and optionally, whitespace. Returns false if s contains only FalseString. Any other values throw an exception.
string ToString()

Method

Returns a string representation of the value of this Boolean.

Byte Fields

Structure
Field

Encapsulates the byte base type.

byte MaxValue

Returns the maximum value of a byte, 255.


byte MinValue

Field

Returns the minimum value of a byte, 0.

Methods
int CompareTo(object o)

Method

Reports a comparison between this object and another Byte object. Returns a negative value if this object is less than the other; positive if this object is greater than o or o is null; and returns 0 if both are equal. Throws an exception if o is not a Byte or null.
bool Equals(object o)

Method

Returns true if o is a Byte and has the same value as this object; otherwise returns false. Throws an exception if o is not a Byte or null.
int GetHashCode()

Method

Returns the hash code for this Byte.


TypeCode GetTypeCode()

Method

Returns System.TypeCode.Byte.
static byte Parse(string s)

Method (overloaded)

Parses a byte value from a string:


static byte Parse(string s)

Extracts a number from a string.


static byte Parse(string s, IFormatProvider ifp)

Parse using a culture-specific formatter.

Char Structure 269

static byte Parse(string s, NumberStyles ns) Parse, validating using a format defined by ns. static byte Parse(string s, NumberStyles ns, IFormatProvider ifp)

Parse using a culture-specific formatter and validating using the format defined by ns.
string ToString()

Method (overloaded)

Creates a string from a Byte:


static byte ToString()

Creates a string representation of this value.


static byte ToString(IFormatProvider ifp)

Creates a string using a culture-specific format provider.


static byte ToString(string fmt)

Creates a string applying a format string.


static byte ToString(string fmt, IFormatProvider ifp)

Creates a string applying a format string with a culture-specific format provider.

Char Fields

Structure
Field

Encapsulates the char base type.

const char MaxValue

Returns the largest possible value of a Char; currently 0xFFFF.


const char MinValue

Field

Returns the least possible value of a Char (0).

Methods
int CompareTo(object o)

Method

Reports a comparison between this object and another Char object. Returns a negative value if this object is less than the other; positive if this object is greater than o or o is null; and returns 0 if both are equal. Throws an exception if o is not a Char or null.
bool Equals(object o)

Method

Returns true if o is a Byte and has the same value as this object; otherwise returns false. Throws an exception if o is not a Byte or null.
int GetHashCode()

Method

Returns the hash code for this Char.


static double GetNumericValue()

Method (overloaded)

Converts a character to a double:

2 7 0 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e

static double GetNumericValue(char c)

Returns the numeric value if the character is a number, or -1 if not.


static double GetNumericValue(string s, int i) Returns the numeric value of the character in s at index i -1 if not.

if it is a number, or

TypeCode GetTypeCode()

Method

Returns System.TypeCode.Char.
UnicodeCategory GetUnicodeCategory()

Method (overloaded)

Reports the Unicode category of a character:


static double GetNumericValue(char c)

Returns the Unicode category of the character.


static double GetNumericValue(string s, int i) Returns the Unicode category of the character in s at index i.

See System.Globalization for the UnicodeCategory enumeration.


bool IsCategory()

Method (overloaded)

Reports whether a character is a member of Category, in which category is one of the following:
Control Digit Letter LetterOrDigit Lower Number Punctuation Separator Surrogate Symbol Upper WhiteSpace

The function has two forms:


static bool IsCategory(char c)

Returns true if the character is a member of the category.


static double GetNumericValue(string s, int i) Returns true if the character in s at index i is a member

of the category.

char Parse(string s)

Method

Converts a single-character string to a char.


char ToLower()

Method (overloaded)

Converts a character to its lowercase:

Console Class 271

static char ToLower(char c) static char ToLower(char c, CultureInfo ci)

string ToString(char c)

Method

Converts a character value to a string.


char ToUpper()

Method (overloaded)

Converts a character to its uppercase.


static char ToUpper(char c)

Console

Class

The Console class provides access to console I/O, the standard input, output, and error streams.

Properties
static TextWriter Error

Property (read-only)

Retrieves a System.IO.TextWriter object connected to standard error.


static TextReader In

Property (read-only)

Retrieves a System.IO.TextReader object connected to standard input.


static TextWriter Out

Property (read-only)

Retrieves a System.IO.TextWriter object connected to standard out.

Methods
static Stream OpenStandardError()

Method

Retrieves a System.IO.Stream object connected to standard error. You can optionally provide an Int32 parameter indicating the appropriate buffer size.
static Stream OpenStandardInput()

Method

Retrieves a System.IO.Stream object connected to standard input. You can optionally provide an Int32 parameter indicating the appropriate buffer size.
static Stream OpenStandardOutput()

Method

Retrieves a System.IO.Stream object connected to standard output. You can optionally provide an Int32 parameter indicating the appropriate buffer size.
static int Read()

Method

Blocking operation until the user presses the return key, and then returns the next character entered.
static string ReadLine()

Method

Blocking operation until the user presses the return key, and then returns the line entered.

2 7 2 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e static void SetError(TextWriter errstream)

Method

Redirects standard error onto the supplied System.IO.TextWriter.


static void SetIn()

Method

Redirects standard input onto the supplied System.IO.TextReader.


static void SetOut()

Method

Redirects standard output onto the supplied System.IO.TextWriter.


static void Write()

Method (overloaded)

Writes information to standard out. This method is overloaded to accept single parameters of the base value types (integers, doubles, and so forth) and further provides the following special signatures:
static void Write(String fmt, params object[] args) Formats and writes an output string using fmt and drawing on the array args for replacements. static void Write(char [] buf, int start, int range) Writes range characters starting at index start from buf.

parameter

static void WriteLine()

Method (overloaded)

Overloaded and performs identically to the Write() method, but appends the end-ofline sequence to the output. Additionally, calling this method with no parameters writes only the EOL sequence.

Convert Fields

Class

Convert concentrates general conversion routines in one place for you and provides the DBNull value.

static readonly DBNull DBNull

Field

is intended for use with the semantics of an absence of information, as in the SQL definition of NULL. This null does not represent an uninitialized state; rather, no state at all.

Methods
static object ChangeType()

Method (overloaded)

Attempts to convert an object to another type; this is analogous to the cast expression (T)obj, in which T is a type and obj is an instance. The method throws an InvalidCastException if the conversion is undefined or fails:
static object ChangeType(object cand, Type t) static object ChangeType(object cand, TypeCode tc)

Convert Class 273

static object ChangeType(object cand, Type t, IFormatProvider ifp) static object ChangeType(object cand, TypeCode tc, IFormatProvider ifp) byte[] FromBase64CharArray(char [] buf, int ofs, int range)

static

Method Converts range characters starting at index array of bytes.


ofs

in

buf

from base64 encoding to an

static byte [] FromBase64String(string s)

Method

Converts the base64 encoded data in s to an array of bytes.


static TypeCode GetTypeCode(object o)

Method value for the passed in object, or

Returns the appropriate


System.TypeCode.Empty

System.TypeCode

if o is null. Method

static bool IsDBNull(object o)

Returns true if o is the DBNull value.


static int ToBase64CharArray(byte[] buf, int ofs, int range, char[] dest, int destOfs) Method

Converts range bytes starting at ofs in buf to a base64 encoded stream in dest starting at offset destOfs. Throws ArgumentOutOfRange exception if dest is too small to hold the result. Returns the number of characters written into dest.
static string ToBase64String(byte [] buf)

Method (overloaded)

Converts bytes in buf to base64 and returns the result in a string. The method is overloaded to add two additional parameters: the starting index and the number of bytes from buf to convert. This method is generally more useful than ToBase64CharArray() because the output size is dynamically calculated.
static string ToBase64String( byte [] buf ) Converts the entire sequence of data in buf to a base64 string. static string ToBase64String( byte [] buf, int ofs, int len ) Converts len bytes starting at ofs in buf to a base64 string.

Type ToType( value )

Method (overloaded)
Type

Converts a single value to another, where value is an instance of a base type. one of the following:
Boolean Byte Char DateTime Decimal Double

is

2 7 4 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e

Int16 Int32 Int64 SByte Single String UInt16 Uint32 UInt64

DateTime

Structure

DateTime represents a date and time as an offset from the epoch 12:00 A.M., 1/1/1 A.D.

to a precision of 100 nanoseconds. The type defines an extensive set of operations for working with time information. In the following, ticks is defined as the number of 100ns intervals from the epoch.

DateTime Constructors
DateTime(Int64 ticks)

Initializes to the DateTime value representing ticks.


DateTime(Int32 year, Int32 month, Int32 day)

Initializes to midnight in the morning of the given date.


DateTime(Int32 year, Int32 month, Int32 day, Calendar cal)

Initializes to midnight on the given date by the specified calendar. See System.Globalization for acceptable calendars.
DateTime(Int32 year, Int32 month, Int32 day, Int32 hour, Int32 minute, Int32 second)

Initializes to the given date and time.


DateTime(Int32 year, Int32 month, Int32 day, Int32 hour, Int32 minute, Int32 second, Calendar cal)

Initializes to the given date and time by the specified calendar. See System.Globalization for acceptable calendars.
DateTime(Int32 year, Int32 month, Int32 day, Int32 hour, Int32 minute, Int32 second, Int32 millisecond)

Initializes to the given date and time.


DateTime(Int32 year, Int32 month, Int32 day, Int32 hour, Int32 minute, Int32 second, Int32 millisecond, Calendar)

Initializes to the given date and time by the specified calendar. See System.Globalization for acceptable calendars.

DateTime Structure 275

Fields
static readonly DateTime MaxValue

Field

Returns the latest date and time supported; defined as a constant 11:59:59 P.M., 12/31/9999 A.D.
static readonly DateTime MinValue

Field

Returns the earliest data and time supported; defined as the constant DateTime(0), or 12:00 A.M., 1/1/1 A.D.

Properties
Date and Time Part Properties
DateTime

defines the following properties to extract a partial date or time:

DateTime Date int Day DayOfWeek DayOfWeek int DayOfYear int Hour int Millisecond int Minute int Month int Second long Ticks TimeSpan TimeOfDay int Year

static DateTime Now

Property (read-only)

Returns the current system date and time.


Today

Property

Returns midnight on the morning on the current system date.


UtcNow

Property

Returns the current UTC time.

Methods
DateTime Add(TimeSpan incr)

Method

Returns a DateTime containing this DateTime plus increment incr.


DateTime AddDays(double days)

Method

Returns a DateTime containing this DateTime plus days.


DateTime AddHours(double hours)

Method

Returns a DateTime containing this DateTime plus hours.

2 7 6 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e DateTime AddMilliseconds(double ms)

Method

Returns a DateTime containing this DateTime plus ms.


DateTime AddMinutes(double mins)

Method

Returns a DateTime containing this DateTime plus mins.


DateTime AddMonths(int months)

Method

Returns a DateTime containing this DateTime plus months.


DateTime AddSeconds(double secs)

Method

Returns a DateTime containing this DateTime plus secs.


DateTime AddTicks(long ticks)

Method

Returns a DateTime containing this DateTime plus ticks.


DateTime AddYears(int years)

Method

Returns a DateTime containing this DateTime plus years.


static int Compare(DateTime t1, DateTime t2)

Method
> t2,

Returns an integer indicating relative value: 1 for t1


t1 < t2. int CompareTo(DateTime t)

0 for t1

== t2,

and -1 for

Method
> t

== t2,

Returns an integer indicating relative value: 1 for this and -1 for this < t2.

or t

is null,

0 for this

static DaysInMonth(int year, int month)

Method

Returns the number of days in the given month.


bool Equals()

Method (overloaded)

Defined as static and instance, this method returns true if both parameters are equal:
bool Equals(DateTime t) Returns true if this == t.

static bool Equals(DateTime t1, DateTime t2) Returns true if t1 == t2.


static DateTime FromFileTime(long winFileTime)

Method

Creates a DateTime from an OS timestamp.


static DateTime FromOADate(double d)

Method

Creates a DateTime from a OLE Automation date (VT_DATE).

DateTime Structure 277

string [] GetDateTimeFormats()

Method (overloaded)

Gets an array of all representations of this DateTime supported by a specified format or formatting provider:
string string string string [] [] [] [] GetDateTimeFormats() GetDateTimeFormats(char fmt) GetDateTimeFormats(IFormatProvider ifp) GetDateTimeFormats(char fmt, IFormatProvider)

See System.Globalization.DateTimeFormatInfo for formatting specifiers.


int GetHashCode()

Method

Returns the hash code of this instance.


TypeCode GetTypeCode()

Method

Returns System.TypeCode.DateTime.
static bool IsLeapYear(int year)

Method

Returns true if the given year is a leap year.


static DateTime Parse()

and static

DateTime ParseExact()

Methods

The parsing methods in DateTime convert from a string. The Parse() methods attempt an optimistic approach and try to coerce a valid DateTime from data which might contain missing or extraneous data. The ParseExact() methods require that the supplied string match an expected format (or one of an array of formats) exactly:
static DateTime Parse(String s) static DateTime Parse(String s, IFormatProvider ifp) static DateTime Parse(String s, IFormatProvider ifp, DateTimeStyles dts) static DateTime ParseExact(string s, string fmt, IFormatProvider ifp) static DateTime ParseExact(string s, string [] fmts, IFormatProvider ifp, DateTimeStyles dts) static DateTime ParseExact(string s, string fmt, IFormatProvider ifp DateTimeStyles dts)

Subtract()

Method (overloaded)

Returns a value indicating the difference or subtrahend of two values:


DateTime Subtract(TimeSpan t) Returns this minus t. TimeSpan Subtract(DateTime t) Returns the difference between this

and t.

long ToFileTime()

Method

Converts this to a local file timestamp.

2 7 8 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e DateTime ToLocalTime()

Method

Converts this to the local time zone.


string ToLongDateString()

Method

Converts the date part of this to a string formatted as Tuesday, January 1, 2002.
ToLongTimeString()

Method

Converts the time part of this to a string formatted as 12:02:35 (HH:MM:SS).


double ToOADate()

Method

Converts this to an OLE Automation date.


string ToShortDateString()

Method

Converts the date part of this to a string formatted as 12/15/2002.


string ToShortTimeString()

Method

Converts the time part of this to a string formatted as 12:15 (HH:MM).


string ToString()

Method (overloaded)

string ToString() Returns this converted to a string of format MM/DD/YYYY HH:MM:SS. string ToString(IFormatProvider ifp) Returns this formatted in the format interpreted by ifp as the general format. string ToString(string fmt) Returns this formatted by applying the format provided by fmt. string ToString(string fmt, IFormatProvider ifp) Returns this formatted by ifp as it interprets fmt.

Please refer to System.Globalization for formatting specifiers.


DateTime ToUniversalTime()

Method

Converts this from its native time zone to the corresponding UTC time.

Operators
DateTime

defines the following operators: Form


DateTime + TimeSpan DateTime == DateTime DateTime > DateTime DateTime >= DateTime DateTime != DateTime

Operation Addition Equality Greater Than Greater Than Or Equal Inequality

Result DateTime bool bool bool bool

Decimal Structure 279

Operation Less Than Less Than Or Equal Subtraction Subtraction

Form
DateTime < DateTime DateTime <= DateTime DateTime - DateTime DateTime - TimeSpan

Result bool bool TimeSpan DateTime

DayOfWeek

Sunday Monday Tuesday Wednesday Thursday Friday Saturday

Enumeration

Identifies a day of the week. The members are as follows:

DBNull
DBNull

Class

represents a null in the database sensean absence of information, rather than an uninitialized value.

Fields
static readonly DBNull Value DBNull

Field

is a singleton class; this field contains the one instance of DBNull.

Methods
TypeCode GetTypeCode()

Method

Returns System.TypeCode.DBNull.
string ToString()

Method

Returns an empty string. Optionally, can take an IFormatProvider.

Decimal

Structure

Encapsulates the C# decimal type.

Decimal(Type val) (overloaded)


The Decimal type defines seven single-parameter constructors, varying in the type of the single parameter; Type can be one of the following:
Double Int16

2 8 0 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e

Int32 Int64 Single UInt32 UInt64

Decimal(Int32[] comps)

Constructs a Decimal from an array of integers containing its component parts. These are as follows: Element
comps[0] comps[1] comps[2] comps[3]

Contents low 32 bits of 96 bit integer middle 32 bits of 96 bit integer high 32 bits of 96 bit integer bits 0-15 are 0 bits 16-23 bit 31 is sign

Note

Divisor exponent bits 24-30 are 0


0

= positive, 1 = negative

The value of the decimal is the effective value of the following formula, in which the expression comps[n:s-e] is equivalent to bits s through e of comps[n]:
(comps[0] << 64 + comps[1] << 32 + comps[2]) / (10^comps[3:16-23] * (comps[3:31] == 0 ? 1 : -1) Decimal(Int32 lo, Int32 mid, Int32 hi, Boolean sign, Byte exp)

Constructs a Decimal by specifying its individual components. The values are interpreted as described for Decimal(Int32 []).

Fields
static readonly decimal MaxValue

Field

Returns the constant 79,228,162,514,264,337,593,543,950,335, the maximum value of a Decimal.


static readonly decimal MinusOne

Field

Returns the constant (decimal)-1.


static readonly decimal MinValue

Field

Returns the constant -79,228,162,514,264,337,593,543,950,335, the minimum value of a Decimal.

Decimal Structure 281

static readonly decimal One

Field

Returns the constant (decimal)1.


static readonly decimal Zero

Field

Returns the constant (decimal)0.

Methods
static decimal Add(decimal a1, decimal a2)

Method

Returns the sum of a1 and a2.


static int Compare(decimal d1, decimal d2)

Method
> d2,

Returns an integer indicating relative value: 1 for d1


d1 < d2. int CompareTo(decimal d)

0 for d1

== d2,

and -1 for

Method
> d2,

Returns an integer indicating relative value: 1 for this -1 for this < d2.
static decimal Divide(decimal d1, decimal d2)

0 for this

== d2,

and

Method

Returns quotient of d1

/ d2.

static bool Equals()

Method (overloaded)

Defined as static and instance, this method returns true if both parameters are equal:
bool Equals(Decimal d) Returns true if this == d. static bool Equals(Decimal d1, Decimal d2) Returns true if d1 == d2.

static decimal Floor(decimal d)

Method

Returns greatest whole number less than d.


static decimal FromOACurrency(long cy)

Method

Converts OLE currency value to a decimal.


static int[] GetBits(decimal d)

Method

Returns the four components of the decimal value d.


int GetHashCode()

Method

Returns hash code for this.

2 8 2 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e TypeCode GetTypeCode()

Method

Returns System.TypeCode.Decimal.
static decimal Multiply(decimal d1, decimal d2)

Method

Returns result of d1

* d2.

static decimal Negate(decimal d)

Method

Returns -1.0

* d.

decimal Parse()

Method (overloaded)

Parses a string to extract a decimal value, optionally using a custom IFormatProvider or style:
static decimal Parse(String s) static decimal Parse(String s, IFormatProvider ifp) static decimal Parse(String s, NumberStyles ns) static decimal ParseExact(string s, NumberStyles ns, IFormatProvider ifp)

Please refer to System.Globalization for more information regarding NumberStyles.


static decimal Remainder(decimal d1, decimal d2)

Method

Returns the result of d1

% d2.

static decimal Round(decimal d, int places)

Method

Rounds d to places decimal places.


static decimal Subtract(decimal d1, decimal d2)

Method

Returns result of d1

- d2.

static Type ToType(decimal d)

Method

Converts d to Type; method will throw an exception if the conversion fails or overflows. Type is one of the following: Byte Double Int16 Int32 Int64 SByte Single Uint16 UInt32 UInt64

Decimal Structure 283 static long ToOACurrency(double d)

Method

Converts d to an OLE currency value.


string ToString()

Method (overloaded)

Creates a string representation of this instance, optionally applying a custom formatter or format string:
string ToString() Returns this converted to a string using the default format. string ToString(IFormatProvider ifp) Returns this formatted by using ifp. string ToString(string fmt) Returns this formatted by applying the format provided by fmt. string ToString(string fmt, IFormatProvider ifp) Returns this formatted by ifp as it interprets fmt.

Refer to System.Globalization for formatting information.


static decimal Truncate(decimal d)

Method

Returns result of truncating the fractional portion of d without rounding.

Operators and Type Conversions


Decimal

defines the following operators: Form


d1 + d2 d1-d1 / d2 d1 == d2 d1 > d2 d1 >= d2 d1++ d1 != d2 d1 < d2 d1 <= d2 d1 % d2 d1 * d2 d1 - d2 -d +d

Operation Addition Decrement Division Equality GreaterThan GreaterThan Or Equal Increment Inequality Less Than Less Than Or Equal Modulus Multiplication Subtraction Unary Negation Unary Plus

(has no effect; returns d)

2 8 4 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e

Decimal also supports casts to and from the following types:


Byte Char Double Int16 Int32 Int64 SByte Single Uint16 UInt32 UInt64

Delegate
Delegate

Class

serves as the base class for delegates.

Delegate Constructor
Delegate

contains no public constructors.

Properties
MethodInfo Method

Property (read-only)

Returns information on the method targeted by the delegate.


object Target

Property (read-only)

Returns the object instance targeted by the delegate.

Methods
object Clone()

Method

Returns a shallow copy of the delegate.


static Delegate Combine()

Method (overloaded)

Returns a multicast delegate containing the union of invocation targets of multiple delegates:
static Delegate(delegate d1, delegate d2) // combine two delegates. static Delegate(delegate [] darray) // combine array of delegates.

static Delegate CreateDelegate()

Method (overloaded)

Creates a delegate referencing a target method:


static Delegate CreateDelegate(Type t, MethodInfo mi) // static method

Double Structure 285

static Delegate CreateDelegate(Type t, object inst, string method) // instance method static Delegate CreateDelegate(Type del_type, Type tgt_type, string method) // static method

object DynamicInvoke(object [] args)

Method
args

Invokes the target of the delegate, passing from the target.


bool Equals(object o)

parameters and returning the result

Method

Returns true if o is an delegate equivalent to this; that is, (Delegate)o contains the same invocation list as this.
int GetHashCode()

Method

Returns the hash code for this instance.


Delegate [] GetInvocationList()

Method

Returns the invocation list contained by this delegate.


static Delegate Remove(Delegate d, Delegate rem)

Method

Removes rem from the invocation list of d.

Operators
Equality Operator Evaluated as d1
== d2,

returns d1.Equals(d2).

Inequality Operator Evaluated as d1


!= d2,

returns !(d1.Equals(d2)).

Double Fields

Structure
Field

Encapsulates a double value.

const double Epsilon

Returns the smallest positive increment representable by a double.


const double MaxValue

Field

Returns the greatest possible double value, 1.79769313486232E+308.


const double MinValue

Field

Returns the least possible double value, 1.79769313486232E+308.


const double NaN

Field

Magic value indicating Not-A-Number.

2 8 6 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e const double NegativeInfinity

Field

Magic value indicating negative overflow.


const double PositiveInfinity

Field

Magic value indicating positive overflow.

Methods
int CompareTo(object o)

Method
> o, 0

Returns an integer indicating relative value: 1 for this for this < o.
bool Equals(object o)

for this

== o,

and -1

Method

Returns true if this


int GetHashCode()

== (Double)o.

Method

Returns a hash code for this instance.


TypeCode GetTypeCode()

Method

Returns System.TypeCode.Double.
static bool IsInfinity(Double d)

Method

Returns true if d
IsNaN()

== Double.PositiveInfinity or d == Double.NegativeInfinity.

Method
== Double.NaN.

Returns true if d

bool IsNegativeInfinity()

Method

Returns true if d

== Double.NegativeInfinity.

bool IsPositiveInfinity()

Method

Returns true if d
double Parse()

== Double.PositiveInfinity.

Method (overloaded)

Parses a string to extract a double value, optionally using a custom IFormatProvider or style:
static double Parse(String s) static double Parse(String s, IFormatProvider ifp) static double Parse(String s, NumberStyles ns) static double ParseExact(string s, NumberStyles ns, IFormatProvider ifp)

Refer to System.Globalization for more information regarding NumberStyles.

Evvironment Class 287

string ToString()

Method (overloaded)

Creates a string representation of this instance, optionally applying a custom formatter or format string:
string ToString() Returns this converted to a string using the default format. string ToString(IFormatProvider ifp) Returns this formatted by using ifp. string ToString(string fmt) Returns this formatted by applying the format provided by fmt. string ToString(string fmt, IFormatProvider ifp) Returns this formatted by ifp as it interprets fmt.

Refer to System.Globalization for formatting information.

Environment Properties
string CommandLine

Class

Provides access to conventional aspects of the runtime environment to client code.

Property (read-only)

Provides the command line that started this program.


string CurrentDirectory

Property (read/write)

Gets or sets the current working directory.


static int ExitCode

Property (read/write)

Sets the exit code returned by the application.


static string MachineName

Property (read-only)

Gets the network name of the host computer.


static string NewLine

Property (read-only)

Gets the end-of-line terminator sequence on the host computer.


static OperatingSystem OSVersion

Property (read-only)

Returns information describing the operating system on which the application is running.
string StackTrace

Property (read-only)

Returns a string describing the current call stack.


static string SystemDirectory

Property (read-only)

Returns the fully qualified system directory path.

2 8 8 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e static int TickCount

Property (read-only)

Returns the time the system has been running; wraps around every 25 days.
static string UserDomainName

Property (read-only)

Returns the domain from which the user logged in.


static bool UserInteractive

Property (read-only)

Reports whether the session the program is running in is an interactive one.


static string UserName

Property (read-only)

Returns the user id under which the program is running.


static Version Version

Property (read-only)

Reports version information on the program.


-static long WorkingSet

Property (read-only)

Reports the physical memory occupied by the program.

Methods
static void Exit(int retcode)

Method

Ends the process and returns retcode to the operating system.


static string ExpandEnvironmentVariables(string exp)

Method

Expands environment variables in exp and returns the result.


static string [] GetCommandLineArgs()

Method

Returns an array of strings containing each of the command-line arguments to the program.
static string GetEnvironmentVariable(string varname )

Method

Returns the value of the named variable, or null if the variable is not defined.
static IDictionary GetEnvironmentVariables()

Method

Returns an indexed list of all defined variables.


static string GetFolderPath(Environment.SpecialFolder f)

Method

Retrieves the path to the specified folder.


static string [] GetLogicalDrives()

Method

Returns an array of strings representing all logical drives on the computer.

Exception Class 289

Environment.SpecialFolder
Environment.SpecialFolder

Enumeration

enumerates special folder categories, and contains the

following members:
ApplicationData CommonApplicationData CommonProgramFiles Cookies DektopDirectory Favorites History InternetCache LocalApplicationData Personal ProgramFiles Programs Recent SendTo StartMenu Startup System Templates

EventArgs Fields

Class
Field

Base class for event argument classes.

static readonly EventArgs Empty

Defines an empty argument set.

EventHandler

Delegate

Declares the standard delegate for events. Defined as follows:


public delegate void EventHandler(object sender, EventArgs e);

Exception

Class

Defines the base class for all exceptions.

Exception Constructors
Exception()

Initializes an empty exception.

2 9 0 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e Exception(String msg)

Constructs an exception with the given message.


Exception(String msg, Exception inner)

Constructs an exception with the given message and inner exception (for chaining).

Properties
string HelpLink

Property (read/write)

Returns a URI indicating a link to help about the exception.


int HResult Property

(read/write)

Contains a COM HRESULT related to the exception.


Exception InnerException

Property (read-only)

Contains the inner, chained exception.


string Message

Property (read-only)

Contains the error message reported to the exception.


string Source

Property (read/write)

Contains a string describing the source of the exception.


string StackTrace

Property (read-only)

Contains stack trace information from the location at which the exception was generated.
MethodBase TargetSite

Property (read-only)

Contains a reference to the method that generated the exception.

Methods
Exception GetBaseException()

Method

Returns the first (innermost) exception in the chain.


string ToString()

Method

Returns a string representation of the exception; chains to inner exceptions to completely describe the exception chain.

FlagsAttribute

Class

Marks an enumeration as being flag, or bit-mapped, values; the compiler can then generate the values as unique bit positions.

GC Class 291

GC

Class

Represents the garbage collector of the CLR and defines methods for influencing its behavior. The class is never instantiated and contains only static methods and fields that interact with the runtime.

Properties
static int MaxGeneration

Property (read-only)

Contains the greatest number of generations managed by the GC.

Methods
static void Collect()

Method (overloaded)

Requests an immediate garbage collection, optionally specifying a maximum generation to collect:


static void Collect() static void Collect(int maxgen)

static int GetGeneration()

Method (overloaded)

Queries for the generation of an object or WeakReference:


static int GetGeneration(object o) static int GetGeneration(WeakReference ref)

static long GetTotalMemory(bool collect)

Method

Gets the number of allocated bytes; collect controls whether to perform a garbage collection before calculating the memory footprint.
static void KeepAlive(object o)

Method

Causes the GC to ignore o for purposes of collection for the duration of the current method; intended for unmanaged code, which might manipulate objects to which there is no managed reference.
static void ReRegisterForFinalize(object o)

Method

Adds an object back onto the finalization queue that was previously removed (usually used during resurrection).
static void SuppressFinalize(object o)

Method

Removes an object from the finalization queue; usually used by Dispose() methods to suppress finalization and reduce overhead.
static void WaitForPendingFinalizers()

Method

Suspends the current thread until the finalization thread has run all finalizers; useful to ensure all cleanup is accomplished before exiting an application.

2 9 2 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e

IComparable
IComparable

Interface

defines a standard comparison interface. This interface is used by sorted containers to order elements, for example.

Methods
int CompareTo(object o)

Method
> o, 0

Returns an integer indicating relative value: 1 for this for this < o.

for this

== o,

and -1

Int16 Fields

Structure
Field

Encapsulates the int16 type.

const short MaxValue

Contains the maximum value, 32767.


const short MinValue

Field

Contains the minimum value, 32768.

Methods
int CompareTo(object o)

Method
> o, 0

Returns an integer indicating relative value: 1 for this for this < o.
bool Equals(object o)

for this

== o,

and -1

Method

Returns true if this


int GetHashCode()

== (Int16)o.

Method

Returns the hash code of this instance.


TypeCode GetTypeCode()

Method

Returns System.TypeCode.Int16.
static short Parse()

Method (overloaded)

Parses a string to extract an int16 value, optionally using a custom IFormatProvider or style:
static static static static ifp) short short short short Parse(String s) Parse(String s, IFormatProvider ifp) Parse(String s, NumberStyles ns) ParseExact(string s, NumberStyles ns, IFormatProvider

Refer to System.Globalization for more information regarding NumberStyles.

Int32 Structure 293

string ToString()

Method (overloaded)

Creates a string representation of this instance, optionally applying a custom formatter or format string:
string ToString() Returns this converted to a string using the default format. string ToString(IFormatProvider ifp) Returns this formatted by using ifp. string ToString(string fmt) Returns this formatted by applying the format provided by fmt. string ToString(string fmt, IFormatProvider ifp) Returns this formatted by ifp as it interprets fmt.

Refer to System.Globalization for formatting information.

Int32 Fields

Structure
Field

Encapsulates the int32 type.

const short MaxValue

Contains the maximum value, 2,147,483,647.


const short MinValue

Field

Contains the minimum value, 2,147,483,648.

Methods
int CompareTo(object o)

Method
> o, 0

Returns an integer indicating relative value: 1 for this for this < o.
bool Equals(object o)

for this

== o,

and -1

Method

Returns true if this


int GetHashCode()

== (Int32)o.

Method

Returns the hash code of this instance.


TypeCode GetTypeCode()

Method

Returns System.TypeCode.Int32.
static int Parse()

Method (overloaded)

Parses a string to extract an int32 value, optionally using a custom IFormatProvider or style:
static int Parse(String s) static int Parse(String s, IFormatProvider ifp)

2 9 4 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e

static int Parse(String s, NumberStyles ns) static int ParseExact(string s, NumberStyles ns, IFormatProvider ifp)

Please refer to System.Globalization for more information regarding NumberStyles.


string ToString()

Method (overloaded)

Creates a string representation of this instance, optionally applying a custom formatter or format string:
string ToString() Returns this converted to a string using the default format. string ToString(IFormatProvider ifp) Returns this formatted by using ifp. string ToString(string fmt) Returns this formatted by applying the format provided by fmt. string ToString(string fmt, IFormatProvider ifp) Returns this formatted by ifp as it interprets fmt.

Refer to System.Globalization for formatting information.

Int64 Fields

Structure
Field

Encapsulates the int64 type.

const long MaxValue

Contains the maximum value, 9,223,372,036,854,775,807.


const long MinValue

Field

Contains the minimum value, 9,223,372,036,854,775,808.

Methods
int CompareTo(object o)

Method
> o, 0

Returns an integer indicating relative value: 1 for this for this < o.
bool Equals(object o)

for this

== o,

and -1

Method

Returns true if this


int GetHashCode()

== (Int64)o.

Method

Returns the hash code of this instance.


TypeCode GetTypeCode()

Method

Returns System.TypeCode.Int64.

MarshalByRefObject Class 295

static long Parse()

Method (overloaded)

Parses a string to extract an int64 value, optionally using a custom IFormatProvider or style:
static static static static ifp) long long long long Parse(String s) Parse(String s, IFormatProvider ifp) Parse(String s, NumberStyles ns) ParseExact(string s, NumberStyles ns, IFormatProvider

Refer to System.Globalization for more information regarding NumberStyles.


string ToString()

Method (overloaded)

Creates a string representation of this instance, optionally applying a custom formatter or format string:
string ToString() Returns this converted to a string using the default format. string ToString(IFormatProvider ifp) Returns this formatted by using ifp. string ToString(string fmt) Returns this formatted by applying the format provided by fmt. string ToString(string fmt, IFormatProvider ifp) Returns this formatted by ifp as it interprets fmt.

Refer to System.Globalization for formatting information.

MarshalByRefObject

Class

The MarshalByRefObject type serves as a base class for objects that are passed between contexts as strong references. Most programmers will not interact directly with this type.

MarshalByRefObject Constructor
MarshalByRefObject()

The default instance constructor initializes state for this object.

Methods
CreateObjRef()

Method Method

GetLifetimeService()

Retrieves the object controlling the lifetime for this object. The default behavior returns an ILease object that manages remoting leases.
InitializeLifetimeService()

Method

Provides an overridable method for derived classes to provide their own lifetime management type.

2 9 6 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e

Math
Math

Class

concentrates most mathematical functions and constants in one scope.

Fields
E

Field

Contains e, the natural logarithm base.


PI

Field

Contains an approximation of pi, the ratio of circumference to diameter of a circle.

Methods
static Type Abs(Type val)

Method (overloaded)

Absolute value of a numeric value; overloaded to accommodate the following types:


Decimal double short int long sbyte float

static double Acos(double c)

Method

Returns the radian angle corresponding to the supplied cosine.


static double Asin(double s)

Method

Returns the radian angle corresponding to the supplied sine.


static double Atan(double t)

Method

Returns the radian angle corresponding to the supplied tangent.


static double Atan2(double y, double x)

Method enables one-step calculation of the

Returns the radian angle whose tangent is angle.


static double Ceiling(double d)

y/x;

Method

Returns the smallest whole number greater than d.


static double Cos(double angle)

Method

Returns the cosine of the given radian angle.


static double Cosh(double angle)

Method

Returns the hyperbolic cosine of the given radian angle.

Math Class 297

static double Exp(double l)

Method

Converts a natural logarithm to its corresponding number by raising e to the l power.


static double Floor(double d)

Method

Returns the greatest whole number less than or equal to d.


static double IEEERemainder(double number, double divisor)

Method

Returns IEEE 754-compliant remainder from dividing number by divisor.


static double Log()

Method (overloaded)

Computes the logarithm of n.


static double Log(double n)

Computes natural logarithm.


static double Log(double n, double base) Computes logarithm of n on base base.

static double Log10(double n)

Method

Computes the base-10 logarithm of n.


static Type Max(Type a, Type b)

Method

Returns the greater of two numbers; overloaded to accommodate the following Types:
byte Decimal double short int long sbyte float ushort uint ulong

static Type Min(Type a, Type b)

Method

Returns the lesser of two numbers; overloaded to accommodate the following Types:
byte Decimal double short int long sbyte float

2 9 8 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e

ushort uint ulong

static double Pow(double base, double power)

Method

Raises base to a power.


static Round()

Method (overloaded)

Rounds a number to a whole number or to a specified number of decimal places:


static Decimal Round(Decimal d) static double Round(double d) Rounds d to a whole number. static Decimal Round(Decimal d, int places) static double Round(double d, int places)

static int Sign(Type param)

Method (overloaded)
param

Returns -1, 0, or 1 depending on whether loaded to support the following Types:


Decimal double short int long sbyte float

is negative, zero, or positive; over-

static double Sin(double angle)

Method

Computes the sine of a radian angle.


static double Sinh(double angle

Method

Computes the hyperbolic sine of a radian angle.


static double Sqrt(double angle)

Method

Computes the square root of d.


static double Tan(double angle)

Method

Computes the tangent of a radian angle.


static double Tanh(double angle)

Method

Computes the hyperbolic sine of a radian angle.

MulticastDelegate

Class

Base class for delegates supporting multiple targets.

Object Class 299

MulticastDelegate Constructor
Creates a delegate with a given target:
MulticastDelegate(Object o, String method)

Initializes a delegate targeting an instance method.


MulticastDelegate(Type t, String method)

Initializes a delegate targeting a static method.

Methods
bool Equals(object o)

Method

Returns true if o is a delegate containing the same invocation list.


int GetHashCode()

Method

Returns a hash code for this instance.


Delegate [] GetInvocationList()

Method

Returns the invocation list contained by this delegate.

Operators
The MulticastDelegate Equals() method. Operation Equality Inequality Form
d1 == d2 d1 != d2

defines the following operators, which are based on the

NonSerializedAttribute
NonSerializedAttribute

Class

marks a class or member that should not be serialized.

Object
Object

Class
Method (overloaded)

is the ultimate base class of all reference types.

Methods
bool Equals()

Compares and returns a value indicating if two objects are equal. Equality is transitive and commutative, and consistent with identity: If x.Equals(y), and y.Equals(z), then x.Equals(z) (transitive) If x.Equals(y), then y.Equals(x) (commutative) x.Equals(x) is always true (identity) x.Equals(y) always returns the same value for equivalent states of x and y (identity) x.Equals(null) is always false (identity)

3 0 0 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e Equals()

takes two formsone for class scope and one for instance scope:

static bool Equals(object o1, object o2) bool Equals(object o)

void Finalize()

Method

Cleans up unmanaged resources in derived classes.


int GetHashCode()

Method

Returns a hash code for this instance.


TypeCode GetType()

Method

Returns the appropriate System.TypeCode.


object MemberwiseClone()

Method

Creates and returns a shallow copy of this object.


static bool ReferenceEquals( object o1, object o2 )

Method

Compares two objects by reference; reference comparison causes comparison of nulls to evaluate as true.
string ToString()

Method

Returns a string representation of an object.

ObsoleteAttribute

Class

Marks an element that is deprecated and should not be used; using an element so marked causes a warning or error at compile time.

ObsoleteAttribute Constructor (overloaded)


Constructs an ObsoleteAttribute with the appropriate message and behavior:
public ObsoleteAttribute()

Constructs a warning attribute with empty message.


public ObsoleteAttribute(string message)

Constructs a warning attribute with the supplied message.


public ObsoleteAttribute(string message, bool iserror) Constructs an attribute with the specified message; if iserror is true,

using

the marked element will result in an error.

Properties
bool IsError()

Property

Tells whether using the marked element is an error.


string Message

Property (read-only)

Returns the message emitted if the marked element is used.

Random Class 301

OperatingSystem Properties
PlatformID Platform

Class

Reports information about the host operating system.

Property (read-only)

Reports the nature of the host OS; currently can be one of the following:
PlatformID.Win32NT Windows NT, 2000 or later. PlatformID.Win32S 16-bit Windows with the Win32s library. PlatformID.Win32Windows Windows 95, 98, or Millennium.

Version Version

Property (read-only)

Returns the version of the host OS.

Methods
object Clone()

Method

Returns a shallow copy of the OperatingSystem.


string ToString()

Method

Returns a string representation of the object.

Random

Class

Generates random numbers.

Random Constructor (overridden)


Initializes a new Random object:
Random()

Initializes with seed based on the system time.


Random(int seed)

Initializes with the given seed value.

Methods
int Next()

Method (overloaded)

Gets a random int:


int Next()

Returns a positive number less than int.MaxValue.


int Next(int max)

Returns a positive number less than max. int Next(int min, int max) Returns a number between min and max, inclusive.

3 0 2 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e void NextBytes(byte [] buf)

Method

Fills buf with random bytes.


double NextDouble()

Method

Gets a random double.

SByte Fields

Structure
Field

Encapsulates the sbyte type.

const sbyte MaxValue

The maximum value of an sbyte, 127.


const sbyte MinValue

Field

The minimum value of an sbyte, 128.

Methods
bool CompareTo(object o)

Method
> o, 0

Returns an integer indicating relative value: 1 for this for this < o.
bool Equals(object o)

for this

== o,

and -1

Method

Returns true if this


int GetHashCode()

== (Int64)o.

Method

Returns the hash code of this instance.


TypeCode GetTypeCode()

Method

Returns System.TypeCode.Int64.
static long Parse()

Method (overloaded)

Parses a string to extract an int64 value, optionally using a custom IFormatProvider or style:
static static static static ifp) long long long long Parse(String s) Parse(String s, IFormatProvider ifp) Parse(String s, NumberStyles ns) ParseExact(string s, NumberStyles ns, IFormatProvider

Refer to System.Globalization for more information regarding NumberStyles.

Single Structure 303

string ToString()

Method (overloaded)

Creates a string representation of this instance, optionally applying a custom formatter or format string:
string ToString() Returns this converted to a string using the default format. string ToString(IFormatProvider ifp) Returns this formatted by using ifp. string ToString(string fmt) Returns this formatted by applying the format provided by fmt. string ToString(string fmt, IFormatProvider ifp) Returns this formatted by ifp as it interprets fmt.

Refer to System.Globalization for formatting information.

SerializableAttribute Single

Class

Marks a type or member that should be included in serialization.

Structure
Field

Encapsulates the single type.


const single Epsilon

Returns the smallest positive increment representable by a single.


const single MaxValue

Field

Returns the greatest possible single value, 3.402823e38.


const single MinValue

Field

Returns the least possible single value, 3.402823e38.


const single NaN

Field

Magic value indicating Not-A-Number.


const single NegativeInfinity

Field

Magic value indicating negative overflow.


const single PositiveInfinity

Field

Magic value indicating positive overflow.

Methods
int CompareTo(object o)

Method
> o, 0

Returns an integer indicating relative value: 1 for this for this < o.

for this

== o,

and -1

3 0 4 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e bool Equals(object o)

Method

Returns true if this


int GetHashCode()

== (Single)o.

Method

Returns a hash code for this instance.


TypeCode GetTypeCode()

Method

Returns System.TypeCode.Single.
static bool IsInfinity(Single d)

Method

Returns true if d

== Single.PositiveInfinity or d == Single.NegativeInfinity.

static bool IsNaN(float f)

Method

Returns true if f

== Single.NaN.

static bool IsNegativeInfinity(float f)

Method

Returns true if f

== Single.NegativeInfinity.

static bool IsPositiveInfinity(float f)

Method

Returns true if f
single Parse()

== Single.PositiveInfinity.

Method (overloaded)

Parses a string to extract a single value, optionally using a custom IFormatProvider or style:
static single Parse(String s) static single Parse(String s, IFormatProvider ifp) static single Parse(String s, NumberStyles ns) static single ParseExact(string s, NumberStyles ns, IFormatProvider ifp)

Refer to System.Globalization for more information regarding NumberStyles.


string ToString()

Method (overloaded)

Creates a string representation of this instance, optionally applying a custom formatter or format string:
string ToString() Returns this converted to a string using the default format. string ToString(IFormatProvider ifp) Returns this formatted by using ifp. string ToString(string fmt) Returns this formatted by applying the format provided by fmt. string ToString(string fmt, IFormatProvider ifp) Returns this formatted by ifp as it interprets fmt.

Refer to System.Globalization for formatting information.

String Class 305

String

Class

Encapsulates the string type.

String Constructor
String(Char* buf)

Constructs a string from an unmanaged pointer to an array of Unicode characters.


String(Char[] buf)

Constructs a string from an array of characters.


String(SByte* buf)

Constructs a string from an array of bytes.


String(Char c, Int32 n)

Constructs a string containing n copies of the character c.


String(Char* c, Int32 start, Int32 range)

Constructs a string from an unmanaged pointer to an array of Unicode characters, starting at start and extracting range characters.
String(Char[] c, Int32 start, Int32 range)

Constructs a string from an array of Unicode characters, starting at start and extracting range characters.
String(SByte* c, Int32 start, Int32 range)

Constructs a string from an unmanaged pointer to an array of bytes, starting at start and extracting range characters.
String(SByte* c, Int32 start, Int32 range, Encoding enc)

Constructs a string from an unmanaged pointer to an array of bytes, starting at start and extracting range characters, translating the bytes by using a specific Encoding.

Fields
static readonly string Empty

Field

Contains an empty string.

Properties
char this [int i]

Property (read-only)

Indexer that returns a character at index i in the string.


Length

Property

Contains the number of characters in the string.

3 0 6 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e

Methods
String Clone()

Method

Creates a shallow copy of the string.


static int Compare()

Method (overloaded)

Compares two strings and returns an indication of their relative values. All variants return a negative value if s1 < s2, positive if s1 > s2, and 0 if the strings are equal:
static int Compare(string s1, string s2)

Compares two strings with case sensitivity.


static int Compare(string s1, string s2, bool case_insensitive)

Compares two strings, optionally not case sensitive.


static int Compare(string s1, string s2, bool case_insensitive, CultureInfo ci)

Compares two strings, optionally not case sensitive, by applying the order provided by ci.
static int Compare(string s1, int i1, string s2, int i2, int range) Compares range characters, starting at s1[i1] and s2[i2]. static int Compare(string s1, int i1, string s2, int i2, int range, bool case_insensitive) Compares range characters, starting at s1[i1] and s2[i2], optionally not

case

sensitive.
static int Compare(string s1, int i1, string s2, int i2, int range, bool case_insensitive, CultureInfo ci) Compares range characters, starting at s1[i1] and s2[i2], optionally not sensitive, by applying the order provided by ci.

case

static int CompareOrdinal()

Method (overloaded)

Compares strings by characters ordinal values, ignoring all cultural significance. Returns a negative value if s1 < s2, positive if s1 > s2, and 0 if the strings are equal:
static int CompareOrdinal(string s1, string s2) Compares s1 to s2. static int CompareOrdinal(string s1, int i1, string s2, int i2, int range) Compares range characters, starting at s1[i1] and s2[i2], optionally not case sensitive, by applying the order provided by ci.

int CompareTo()

Method (overloaded) Returns a negative value if equal:


this < s,

Compares this to another object. this > s2, and 0 if the strings are

positive if

static int CompareTo(object s) Compares this to s. s must be an object

that evaluates to a string.

String Class 307

static int CompareTo(string s) Compares this to s.

static string Concat(string, params object [])

Method

Concatenates one or more strings (or objects that can be converted to strings) to a string.
static string Copy(string s)

Method

Creates a copy of s.
void CopyTo(int start, char[] dest, int start_dest, int range)

Method starting at

Copies

ranges characters, dest[start_dest].

starting at

start

in this string, to

dest

bool EndsWith(string pattern)

Method

Returns true if this string ends with pattern.


bool Equals()

Method (overloaded)

Returns true if both strings are identical.


bool Equals( string s ) Compares s to this. static bool Equals(string s1, string s2) Compares s1 to s2.

static string Format(string s, params object [] args)

Method
args

Creates a string based on the format in replacements.


int GetHashCode()

and drawing on the objects in

for

Method

Returns a hash code for this instance.


TypeCode GetTypeCode()

Method

Returns System.TypeCode.String.
int IndexOf()

Method (overloaded)

Returns the index of a specified character or string in this string:


int IndexOf(char c) Searches for c in this. int IndexOf(string s) Searches for s in this. int IndexOf(char c, int start) Searches for c in this starting at start. int IndexOf(string s, int start) Searches for s in this starting at start.

3 0 8 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e

int IndexOf(string s, int start, int range) Searches for c in this starting at start; searches only

range characters.

int IndexOfAny()

Method (overloaded)

Returns the index of the first occurrence of any of a set of characters in this string:
int IndexOfAny(char[] c)

Searches for characters from c in this. int IndexOfAny(char [] c, int start) Searches for characters from c in this starting at start. int IndexOfAny(char [] c, int start, int range) Searches for characters from c in this starting at start, examining range characters. Method

at most

string Insert(int loc, string s)

Inserts s at loc in this string and returns the result.


static string Intern(string str)

Method

Returns a reference to the internal unique supporting str. Each string constant in your application is stored in a single internal table with no duplications. If you have two strings with identical values from a constant declaration, their individual object instances are different, but they share the same internal instance.
static string IsInterned(string str)

Method

Returns a string if the value is in the internal table, or null if not.


static string Join()

Method (overloaded)

Concatenates a set of strings into a single string using a specified separator:


static string Join(string sep, string [] items) Concatenates items using sep as a separator. static string Join(string sep, string [] items, int start, int n) Concatenates items, starting at items[start] and including n items.

int LastIndexOf()

Method (overloaded)

Returns the index of the last instance in this string of a specified character or string:
int LastIndexOf(char c) Searches for the last c in this. int LastIndexOf(string s) Searches for the last s in this. int LastIndexOf(char c, int start) Searches for the last c in this starting at start. int LastIndexOf(string s, int start) Searches for the last s in this starting at start. int LastIndexOf(string s, int start, int range) Searches for the last c in this starting at start; searches

only range characters.

String Class 309

int LastIndexOfAny()

Method (overloaded)

Returns the index of the first occurrence of any of a set of characters in this string:
int LastIndexOfAny(char[] c)

Searches for last incidence of a character from c in this. int LastIndexOfAny(char [] c, int start) Searches for last incidence of a character from c in this starting int LastIndexOfAny(char [] c, int start, int range) Searches for last incidence of a character from c in this starting examining at most range characters. Method (overloaded)

at start. at start,

string PadLeft()

Pads a string to an specified width:


string PadLeft(int width) Pads the string on the left with spaces to width. string PadLeft(int width, char padc) Pads the string on the left with the supplied padc.

string PadRight()

Method (overloaded)

Pads a string to a specified width and returns the result:


string PadRight(int width) Pads the string on the right with spaces to width. string PadRight(int width, char padc) Pads the string on the right with the supplied padc.

string Remove(int start, int n)

Method

Removes n characters from the string beginning at start and returns the result.
string Replace()

Method (overloaded)

Replaces all instances of a character or string in this and returns the result:
string Replace(char find, char replace) string Replace(string find, string replace)

string [] Split()

Method (overloaded)

Complementary function to Join(); splits a string on a set of delimiters you supply:


string [] Split(char [] delimiters) string [] Split(char [] delimiters, int maxn) Limits the number of strings to maxn.

bool StartsWith(string pat)

Method

Returns true if the string starts with pat.

3 1 0 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e string Substring()

Method (overloaded)

Returns a substring of this string:


string Substring(int start) Returns the substring from start through the end of the string Substring(int start, int n) Returns the substring from start to start + n - 1.

string.

char [] ToCharArray()

Method (overloaded)

Returns a char array containing characters from the string:


string ToCharArray(int start) Copies the substring from start through the end of the char [] ToCharArray(int start, int n) Copies the substring from start to start + n - 1.

string.

string ToLower()

Method (overloaded)

Returns a string with all characters converted to lowercase, optionally by using a specific CultureInfo:
string ToLower() string ToLower(CultureInfo ci)

string ToString()

Method

Returns this string.


string ToUpper()

Method (overloaded)

Returns a string with all characters converted to uppercase, optionally by using a specific CultureInfo:
string ToUpper() string ToUpper(CultureInfo ci)

string Trim()

Method (overloaded)

Trims characters from the beginning and end of the string and returns the result.
string Trim()

Trims whitespace.
string Trim(params char [] charsets) Trims characters in charsets.

string TrimEnd(params char [] charsets)

Method

Trims characters in charsets only from the end of the string and returns the result.
string TrimStart(params char [] charsets)

Method

Trims characters in result.

charsets

only from the beginning of the string and returns the

TimeSpan Structure 311

Operators
String

defines the following operators: Form


d1 == d2 d1 != d2

Operation Equality Inequality

ThreadStaticAttribute

Class

Used to mark elements that should not be shared across threads; each thread has a unique copy of the element that is marked ThreadStatic. Meaningful only on data not methods, indexers, or other code.

TimeSpan

Structure

Encapsulates the concept of a time interval, in contrast to a DateTime, which embodies a single point in time.

TimeSpan Constructor
TimeSpan(Int64 ticks)

Initializes a TimeSpan of length ticks.


TimeSpan(Int32 hours, Int32 minutes, Int32 seconds)

Initializes a TimeSpan of the given length.


TimeSpan(Int32 days, Int32 hours, Int32 minutes, Int32 seconds)

Initializes a TimeSpan of the given length.


TimeSpan(Int32 days, Int32 hours, Int32 minutes, Int32 seconds, Int32 milliseconds)

Initializes a TimeSpan of the given length.

Fields
static readonly TimeSpan MaxValue

Field

Contains the maximum length of a TimeSpan.


static readonly TimeSpan MinValue

Field

Contains the minimum length of a TimeSpan.


const long TicksPerDay

Field

Contains the number of 100ns intervals in a day.


const long TicksPerHour

Field

Contains the number of 100ns intervals in an hour.

3 1 2 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e const long TicksPerMillisecond

Field

Contains the number of 100ns intervals in a millisecond.


const long TicksPerMinute

Field

Contains the number of 100ns intervals in a minute.


const long TicksPerSecond

Field

Contains the number of 100ns intervals in a second.


static readonly Zero

Field

Contains a TimeSpan of zero duration.

Properties
int Days

Property (read-only)

Returns the number of days in the TimeSpan.


int Hours

Property (read-only)

Returns the number of hours in the TimeSpan.


int Milliseconds

Property (read-only)

Returns the number of milliseconds in the TimeSpan.


int Minutes

Property (read-only)

Returns the number of minutes in the TimeSpan.


int Seconds

Property (read-only)

Returns the number of seconds in the TimeSpan.


long Ticks

Property (read-only)

Returns the number of ticks in the TimeSpan.


double TotalDays

Property (read-only)

Returns the exact number of days in the TimeSpan.


double TotalHours

Property (read-only)

Returns the exact number of hours in the TimeSpan.


double TotalMilliseconds

Property (read-only)

Returns the exact number of milliseconds in the TimeSpan.


double TotalMinutes

Property (read-only)

Returns the exact number of minutes in the TimeSpan.


double TotalSeconds

Property (read-only)

Returns the exact number of seconds in the TimeSpan.

TimeSpan Structure 313

Methods
TimeSpan Add(TimeSpan ts)

Method

Adds ts to this and returns the result.


static int Compare(TimeSpan t1, TimeSpan t2)

Method

Returns an indication of the relative magnitude of its arguments; returns positive if t1 > t2, 0 if t1 == t2, and negative if t1 < t2.
int CompareTo(TimeSpan ts)

Method

Returns an indication of the relative magnitude of this and the argument; returns positive if this > t2, 0 if this == t2, and negative if this < t2.
TimeSpan Duration()

Method

Returns the absolute value of this instance (a TimeSpan can be negative).


bool Equals()

Method (overloaded)

Returns an indication of whether two TimeSpan instances are equivalent:


bool Equals(TimeSpan ts) Compares ts to this. static bool Equals( TimeSpan t1, TimeSpan t2) Compares t1 to t2.

static TimeSpan FromDays(double days)

Method

Returns a TimeSpan equivalent to the given number of days.


static TimeSpan FromHours(double hours)

Method

Returns a TimeSpan equivalent to the given number of hours.


static TimeSpan FromMilliseconds(double ms)

Method

Returns a TimeSpan equivalent to the given number of milliseconds.


static TimeSpan FromMinutes(double minutes)

Method

Returns a TimeSpan equivalent to the given number of minutes.


static TimeSpan FromSeconds(double seconds)

Method

Returns a TimeSpan equivalent to the given number of seconds.


static TimeSpan FromTicks(long ticks)

Method

Returns a TimeSpan equivalent to the given number of ticks.


int GetHashCode()

Method

Returns a hash code for this instance.

3 1 4 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e TimeSpan Negate()

Method

Returns a TimeSpan with the opposite magnitude to this.


static TimeSpan Parse(string s)

Method
string.

Attempts to parse a TimeSpan from a [sign][d.]h:m:s[.ms]


TimeSpan Subtract(TimeSpan ts)

The string should be formatted as

Method

Subtracts ts from this and returns the result.


string ToString()

Method

Returns a string representation of this.

Operators
TimeSpan

defines the following operators. Form


ts1 + ts2 ts1 == ts2 ts1 > ts2 ts1 >= ts2 ts1 != ts2 ts1 < ts2 ts1 <= ts2 ts1 - ts2 -ts +ts

Operation Addition Equality Greater Than Greater Than Or Equal Inequality Less Than Less Than Or Equal Subtraction Unary Negation Unary Plus

(has no effect; returns ts)

TimeZone Properties

Class
Property (read-only)

Represents a time zone.

static TimeZone CurrentTimeZone

Contains the local time zone.


static abstract string DaylightName

Property (read-only)

Contains the name for daylight saving time in the local time zone; if not applicable, returns String.Empty.

TypeCode Enumeration 315

static abstract string StandardName

Property (read-only)

Contains the name for standard time in the local time zone.

Methods
abstract DaylightTime GetDaylightChanges(int year)

Method

Returns a DaylightTime that describes the change to time and the period that it applies for daylight saving time in the year specified.
abstract TimeSpan GetUtcOffset(DateTime local)

Method

Returns the difference between UTC and the given local time.
bool IsDaylightSavingTime()

Method (overloaded)

Identifies whether a given time is within the daylight saving period of a time zone:
bool IsDaylightSavingTime(DateTime dt) Reports against the DaylightTime appropriate to this. static bool IsDaylightSavingTime( DateTime dt, DaylightTime dlt) Reports against the given DaylightTime.

DateTime ToLocalTime(DateTime time)

Method

Converts a given time to the local time zone.


DateTime ToUniversalTime(DateTime time)

Method

Converts a given time to UTC.

TypeCode

Boolean Byte Char DateTime DBNull Decimal Double Empty Int16 Int32 Int64 Object SByte Single String UInt16

Enumeration

Used to identify the type of an object. Defined to include the following members:

3 1 6 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e

UInt32 UInt64

UInt16 Fields

Structure
Field

Encapsulates the UInt16 type.

const short MaxValue

Contains the maximum value, 65535.


const short MinValue

Field

Contains the minimum value, 0.

Methods
int CompareTo(object o)

Method
> o,

Returns an integer indicating relative value: 1 for this for this < o.
bool Equals(object o)

0 for this

== o,

and -1

Method

Returns true if this


int GetHashCode()

== (UInt16)o.

Method

Returns the hash code of this instance.


TypeCode GetTypeCode()

Method

Returns System.TypeCode.UInt16.
static ushort Parse()

Method (overloaded)

Parses a string to extract a UInt16 value, optionally using a custom IFormatProvider or style:
static static static static ifp) int int int int Parse(String s) Parse(String s, IFormatProvider ifp) Parse(String s, NumberStyles ns) ParseExact(string s, NumberStyles ns, IFormatProvider

Refer to System.Globalization for more information regarding NumberStyles.


string ToString()

Method (overloaded)

Creates a string representation of this instance, optionally applying a custom formatter or format string.
string ToString() Returns this converted

to a string using the default format.

UInt32 Structure 317

string ToString(IFormatProvider ifp) Returns this formatted by using ifp. string ToString(string fmt) Returns this formatted by applying the format provided by fmt. string ToString(string fmt, IFormatProvider ifp) Returns this formatted by ifp as it interprets fmt.

Refer to System.Globalization for formatting information.

UInt32 Fields

Structure
Field

Encapsulates the UInt32 type.

const short MaxValue

Contains the maximum value, 4,294,967,295.


const short MinValue

Field

Contains the minimum value, 0.

Methods
int CompareTo(object o)

Method
> o, 0

Returns an integer indicating relative value: 1 for this for this < o.
bool Equals(object o)

for this

== o,

and -1

Method

Returns true if this


int GetHashCode()

== (UInt32)o.

Method

Returns the hash code of this instance.


TypeCode GetTypeCode()

Method

Returns System.TypeCode.UInt32.
static uint Parse()

Method (overloaded)

Parses a string to extract a UInt32 value, optionally using a custom IFormatProvider or style:
static static static static ifp) int int int int Parse(String s) Parse(String s, IFormatProvider ifp) Parse(String s, NumberStyles ns) ParseExact(string s, NumberStyles ns, IFormatProvider

Refer to System.Globalization for more information regarding NumberStyles.

3 1 8 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e string ToString()

Method (overloaded)

Creates a string representation of this instance, optionally applying a custom formatter or format string:
string ToString() Returns this converted to a string using the default format. string ToString(IFormatProvider ifp) Returns this formatted by using ifp. string ToString(string fmt) Returns this formatted by applying the format provided by fmt. string ToString(string fmt, IFormatProvider ifp) Returns this formatted by ifp as it interprets fmt.

Refer to System.Globalization for formatting information.

UInt64 Fields

Structure
Field

Encapsulates the UInt64 type.

const short MaxValue

Contains the maximum value, 18,446,744,073,709,551,615.


const short MinValue

Field

Contains the minimum value, 0.

Methods
int CompareTo(object o)

Method
> o, 0

Returns an integer indicating relative value: 1 for this for this < o.
bool Equals(object o)

for this

== o,

and -1

Method

Returns true if this


int GetHashCode()

== (UInt64)o.

Method

Returns the hash code of this instance.


TypeCode GetTypeCode()

Method

Returns System.TypeCode.UInt64.
static ulong Parse()

Method (overloaded)

Parses a string to extract a UInt64 value, optionally using a custom IFormatProvider or style:
static int Parse(String s) static int Parse(String s, IFormatProvider ifp)

Uri Class 319

static int Parse(String s, NumberStyles ns) static int ParseExact(string s, NumberStyles ns, IFormatProvider ifp)

Refer to System.Globalization for more information regarding NumberStyles.


string ToString()

Method (overloaded)

Creates a string representation of this instance, optionally applying a custom formatter or format string:
string ToString() Returns this converted to a string using the default format. string ToString(IFormatProvider ifp) Returns this formatted by using ifp. string ToString(string fmt) Returns this formatted by applying the format provided by fmt. string ToString(string fmt, IFormatProvider ifp) Returns this formatted by ifp as it interprets fmt.

Refer to System.Globalization for formatting information.

Uri

Class

Encapsulates a Uniform Resource Identifier (URI).

Uri Constructor
Uri(String uri)

Initializes with the specified URI.


Uri(String uri, Boolean has_escapes)

Initializes with the specified URI, indicating whether the string is already escaped.
Uri(Uri base, String relative)

Creates a URI by combining the provided base URI and relative path.
Uri(Uri base, String relative, Boolean has_escapes)

Creates a URI by combining the provided whether the string is already escaped.

base

URI and

relative

path, indicating

Fields
static readonly string SchemeDelimiter

Field

Contains the scheme delimiter for URIs.


static readonly string UriSchemeFile

Field

Contains the scheme to use for file URIs.

3 2 0 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e static readonly string UriSchemeFtp

Field

Contains the scheme to use for FTP URIs.


static readonly string UriSchemeGopher

Field

Contains the scheme to use for gopher URIs.


static readonly string UriSchemeHttp

Field

Contains the scheme to use for HTTP URIs.


static readonly string UriSchemeHttps

Field

Contains the scheme to use for HTTP SSL URIs.


static readonly string UriSchemeMailto

Field

Contains the scheme to use for e-mail URIs.


static readonly string UriSchemeNews

Field

Contains the scheme to use for NNTP URIs.


static readonly string UriSchemeNntp

Field

Contains the scheme to use for NNTP URIs.

Properties
string AbsolutePath

Property (read-only)

Reports the path portion of the URI, sans scheme, host, and GET parameters or fragment identifier.
string AbsoluteUri

Property (read-only)

Reports the entire URI.


string Authority

Property (read-only)

Reports the host and port.


string Fragment

Property (read-only)

Reports the fragment identifier, if any.


string Host

Property (read-only)

Reports the host address.


UriHostNameType HostNameType

Property (read-only)

Reports the type of hostname (DNS name, IP address, or unknown).


bool IsDefaultPort

Property (read-only)

Reports whether the port in the URI is the default for the protocol.

Uri Class 321 bool IsFile

Property (read-only)

Reports whether the URI is a file reference.


bool IsLoopback

Property (read-only)

Reports whether the URI references the local host.


bool IsUnc

Property (read-only)

Reports whether the URI refers to a Universal Naming Convention (UNC) resource.
string LocalPath

Property (read-only)

Reports whether the URI points to a local object.


string PathAndQuery

Property (read-only)

Reports the path and parameter portions of the URI.


int Port

Property (read-only)

Contains the port number referred to by the URI.


string Query

Property (read-only)

Reports the query portion (GET parameters) of the URI.


string Scheme

Property (read-only)

Reports the scheme used in the URI.


string [] Segments

Property (read-only)

Contains an array of the parsed segments of the URI.


bool UserEscaped

Property (read-only)
Uri

Contains true if the URI was escaped before this contain it.
string UserInfo

instance was created to

Property (read-only)

Reports user ID, login information, and so on regarding the user associated with this Uri.

Methods
static UriHostNameType CheckHostName(string host)

Method

Checks a given hostname and reports its type if valid in format; this method does not actually resolve the name to verify the hosts existence.
static bool CheckSchemeName(string scheme)

Method

Checks a given scheme name for RFC2396 compliance.

3 2 2 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e bool Equals(object o)

Method

Returns true if o is a Uri and contains the same URI.


static string EscapeString(string uri)

Method

Processes a given URI and inserts the appropriate escapes, returning the results.
static int FromHex(char digit)

Method

Returns the integer value corresponding to a given hexadecimal digit.


int GetHashCode()

Method

Returns a hash code for this instance.


string GetLeftPart(UriPartial partialid)

Method

Gets the left portion of a URI according to partialid, which identifies when to stop going from right to left as the scheme, authority, or path.
static string HexEscape(char character)

Method

Returns the escape string for a given character.


static char HexUnescape(string s, ref int i)

Method

Returns a character unescaped starting at s[i] and increments i to the index of the character following the characters consumed by the process. This facilitates processing through a URI and converting it to its native form without escapes.
static bool IsHexDigit(char c)

Method

Reports whether a given character is a valid hexadecimal digit.


static bool IsHexEncoding(string s, int loc)

Method

Reports whether there is a valid hex encoded value at s[loc].


string MakeRelative(Uri rel)

Method

If rel is a URI that differs from this only in the right end of its path, returns a relative path from this; otherwise, MakeRelative() returns the full path from rel.
string ToString()

Method

Returns the URI path from this.

UriBuilder

Class

Facilitates creating a URI from component parts.

UriBuilder Constructor (overridden)


UriBuilder

can be constructed by specifying an increasing number of its components:

UriBuilder Class 323

UriBuilder() UriBuilder(String uri) UriBuilder(Uri uri) UriBuilder(String scheme, UriBuilder(String scheme, UriBuilder(String scheme, UriBuilder(String scheme, String query_or_fragment)

String String String String

host) host, Int32 port) host, Int32 port, String path) host, Int32 port, String path,

Properties
string Fragment

Property (read/write)

Gets or sets the fragment identifier, if any.


string Host

Property (read/write)

Gets or sets the host address.


string Password

Property (read/write)

Gets or sets the users password.


string Path

Property (read/write)

Gets or sets the path portion of the URI.


int Port

Property (read/write)

Contains the port number referred to by the URI.


string Query

Property (read/write)

Gets or sets the query portion (GET parameters) of the URI.


string Scheme

Property (read/write)

Gets or sets the scheme used in the URI.


Uri Uri

Property (readonly)

Returns a Uri constructed from the other properties of this Uri.


string UserName

Property (read/write)

Gets or sets the user ID.

Methods
bool Equals(object o)

Method

Returns true if o is a Uri and contains a URI equivalent to that which this UriBuilder would construct.
int GetHashCode()

Method

Returns a hash code for this instance.

3 2 4 A p p e n d i x B : K e y Ty p e s Q u i c k R e f e r e n c e string ToString()

Method

Constructs and returns a string containing a URI.

UriHostNameType

Enumeration

Used to identify a host type contained by a Uri instance. Defines the following values:
Basic (unknown host type) Dns IPv4 IPv6 Unknown (host not set)

UriPartial

Authority Path Scheme

Enumeration

Used to identify a stopping point for a partial URI. Defines the following values:

Version

Class

Encapsulates the concept of a software version.

Version Constructor
The Version constructor is overloaded to enable you to provide successively more detail.
Version(String version) Version(Int32 major,Int32 minor) Version(Int32 major,Int32 minor, Int32 build) Version(Int32 major,Int32 minor, Int32 build, Int32 revision)

Properties
int Build

Property (read-only)

Contains the build number.


int Major

Property (read-only)

Contains the major version number.


int Minor

Property (read-only)

Contains the minor version number.


int Revision

Property (read-only)

Contains the revision number.

Version Class 325

Methods
Version Clone()

Method

Creates and returns a copy of this.


int CompareTo(object o)

Method
== o,

Returns an integer indicating relative value: 1 for this newer than o, 0 for this and -1 for this older than o.
bool Equals(object o)

Method
== (Version)o.

Returns true if o is a Version and this


int GetHashCode()

Method

Returns a hash code for this instance.


string ToString()

Method (overloaded)

Returns a string representation of the version.


string ToString()

Returns a string containing all elements


string ToString(Int32 nelems)

Returns a string containing the stated number of elements.

Operators
Version

defines the following operators: Form


ver1 == ver2 ver1 > ver2 ver1 >= ver2 ver1 != ver2 ver1 < ver2 ver1 <= ver2

Operation Equality Greater Than Greater Than Or Equal Inequality Less Than Less Than Or Equal

INDEX
SYMBOLS
// (double slash), 6

A
access deconflicting, 155 modifiers, 13 adding strings, 98-104 al utility, 61-66 ApplicationException, 259 applications CLR (Common Language Runtime), 48-49 debugging, 67 applying DbgCLR, 68-69, 71 viewing assemblies, 71-72 deploying, 216-217 development tools, 55 al, 61-66 csc, 55-58 gacutil, 66-67 nmake, 58-61 sn, 61-66 intermediate language, 47-48 SDK (Software Development Kit), 205-210 synchronization, 151-156

328 applying

applying attributes, 177-180 culture-neutral resources, 198-200 culture-specific resources, 200-204 events, 160-162 memory with Platform Invoke, 167-170 architecture, BCL (Base Class Library), 73 ArgumentOutOfRangeException, 259-260 ArithmeticException, 260 Array class, 260-263 arrays, 117, 252 assemblies, 49 al, 61-66 attributes, 52-53 configuring, 193-196 dynamic binding, 184-191 gacutil, 66-67 makefiles, 63 sn, 61-66 static binding, 183-184 viewing, 71-72 assignment operators, overloading, 17 association of attributes, 177-180 Attribute class, 263-265 attributes, 177, 255-257 applying, 177-180 assemblies, 52-53 classes, 13 components, 53-55 customizing, 180-182 modifying, 183 AttributeTargets, 265 AttributeUsageAttribute, 266-267

B
Base Class Library. See BCL base types, CTS (Common Type System), 48-49 BasicCom components, 212-213, 215 BCL (Base Class Library), 50 architecture, 73 collections, 78, 80-81 helper classes, 94-96 I/O (input/output), 85-88 network communication, 88-89 profiles, 74 regular expressions, 76-77 serialization, 82, 84 sockets, 89, 91-94 strings, 74-75 behavior methods, declaring, 117-121 binary operators assembly attributes, 52-53 overloading, 16-19 binding dynamic, 184-191 static, 183-184 Boolean structures, 267-268 boxing, 7 building assemblies al, 61-66 sn, 61-66 classes, 106-107 built-in value types, 7 enum value types, 10-11 intrinsic operators, 7-9 struct, 10-11 variables, 9 Byte structures, 268

code 329

C
caches, GAC (Global Assembly Cache), 50 callbacks, implementing, 157 calling external functions, 40 casts, string conversions, 103-104 catch exception, 30-34 char structure, 269-270 characters, 102 class SimpleStart {} statement, 6 classes, 105, 244-251 ApplicationException, 259 ArgumentOutOfRangeException, 259-260 ArithmeticException, 260 Array, 260-263 Attribute, 263-265 attributes, 13 AttributeUsageAttribute, 266-267 building, 106-107 components, 51-55 Console, 271-272 containers, 78 Convert, 272-274 DBNull, 279 declarations, 6 defining, 115 Delegate, 284-285 Environment, 287-288 EventArgs, 289 FlagsAttribute, 290 GC, 291 helper, 94-96 MarshalByRefObject, 295 Math, 296-298 MulticastDelegate, 298-299 namespaces, 128-130 NonSerializedAttribute, 299 Object, 299-300

ObsoleteAttribute, 300 OperatingSystem, 301 properties, 121-128 Random, 301-303 SerializationAttribute, 303 String, 305-311 ThreadStaticAttribute, 311 TimeZone, 314-315 types, 11-14 custom operators, 16-19 events, 20 indexers, 19-20 methods, 14-16 properties, 19-20 value fields, 14 Uri, 319-322 UriBuilder, 322-323 Version, 324-325 clients, enabling, 211-212 CLR (Common Language Runtime), 48-49 assemblies, 49-53 DbgCLR, 68-71 fusion, 50-51 regular expressions, 76-77 strings, 74-75 code deploying, 216-217 elements, 233-240, 242-244, 246-258 events, 160-162 execution assemblies, 49-53 components, 51-55 fusion, 50-51 flow of control delegating, 28-29 exceptions, 29-34 managing, 23-28 throwing exceptions, 34-36

330 code

preprocessor directives, 42-46 SDK (Software Development Kit), 205-210 synchronization, 152-156 troubleshooting, 40, 215 calling external functions, 40 writing unsafe code, 41-42 unsafe applying memory with Platform Invoke, 167-170 memory management, 174-175 pointers, 165-166 unsafe context, 170-171 unsafe language elements, 171-174 collections, BCL (Base Class Library), 78-81 COM+ BasicCom components, 212-213, 215 integrating, 210-212 comments, 224-225 Common Type System. See CTS communication helper classes, 94-96 networks, 88-89 sockets, 89, 91-94 compilation with csc, 55-58 elements, 221-227, 229-233 nmake, 58-61 SDK (Software Development Kit), 205-210 components, 51 attributes, 53-55 BasicCom, 212-215 configuration assemblies, 193-196 files, 194-196

Console class, 271-272 constructors, 15-16 DateTime, 274 Delegate class, 284 exceptions, 289-290 MarshalByRefObject class, 295 MulticastDelegate class, 298-299 ObsoleteAttribute class, 300 Random class, 301 String class, 305 TimeSpan, 311 Uri class, 319 UriBuilder class, 322-323 Version class, 324 constructs parameters, 121-128 synchronization, 151-156 containers, 78 context, unsafe, 170-174 control statements, 23-28 delegating, 28-29 exceptions, 29-36 controlling synchronization, 151-156 conversions Decimal structure, 283-284 operators, 19 strings, 98-104 Convert class, 272-274 cryptographic storage provider. See CSP csc, compiling C# with, 55-58 CSP (cryptographic storage provider), 62 CTS (Common Type System), 48 base types, 48-49 simple data types, 97-98 culture-neutral resources, 198-200 culture-specific resources, 200-204

enum value types 331

customization attributes, 180-182 operators, 16-17, 19

D
databases, interface interaction, 108-110 date specifiers, 101 DateTime structure, 274-279 DayOfWeek enumeration, 279 DBNull class, 279 debugging applications, 67 DbgCLR, 68-69, 71 viewing assemblies, 71-72 code, 215 Decimal structure, 279-284 declarations, 7 classes, 6, 11-14 custom operators, 16-19 events, 20 indexers, 19-20 methods, 14-16 properties, 19-20 value fields, 14 custom attributes, 180 delegates, 158 events, 162 interfaces, 21-22 methods, 117-121 namespaces, 128-130 variables, 9 deconflicting access, 155 definitions classes, 115 entities, 115 Delegate class, 284-285

delegates, 156-159, 255 EventHandler, 289 events, 160-162 flow of control, 28-29 deployment assemblies, 49-53 SDK (Software Development Kit), 216-217 destructors, 15-16 development tools, 55 al, 61-67 csc, 55-58 DbgCLR, 68-69, 71 gacutil, 66-67 nmake, 58-61 sn, 61-66 directives preprocessing, 230-232 preprocessor, 42-46 disassembly switches, 72 documents, enumerating types, 111-112 double slash (//), 6 Double structure, 285-287 dynamic binding, reflection of, 184-191

E
elements coding, 233-240, 242-244, 246-258 dynamic binding, 184-191 static binding, 183-184 structural, 221-233 unsafe language, 171-174 enabling managed types, 211-212 entities, defining, 115 enum value types, 10-11

332 enumeration

enumeration, 254 AttributeTargets, 265 DayOfWeek, 279 Environment.SpecialFolder, 289 TypeCode, 315-316 types, 111-112 UriHostNameType, 324 UriPartial, 324 Environment class, 287-288 Environment.SpecialFolder enumeration, 289 escape sequences, 225 EventArgs class, 289 EventHandler delegate, 289 events code, 160-162 declaring, 20 exceptions code, 29-34 throwing, 34-36 constructors, 289-290 flow of control, 29-36 execution applications CLR (Common Language Runtime), 48-49 MSIL (Microsoft-defined intermediate language), 47-48 code, 49 assemblies, 49-53 components, 51-55 flow of control, 23-28 fusion, 50-51 threads, 147-151 .EXE files, deploying, 216-217 expressions, 97-98, 235-239 external functions, calling, 40

F
fields AttributeUsageAttribute class, 266 Boolean structure, 267 Byte structure, 268 char structure, 269 Convert class, 272 DateTime, 275 DBNull class, 279 Decimal structure, 280 Double structure, 285 EventArgs class, 289 Int16 structure, 292 Int32 structure, 293 Int64 structure, 294 Math class, 296 Random class, 302 String class, 305 TimeSpan, 311-312 UInt16 structures, 316 UInt32 structures, 317 UInt64 structures, 318 Uri class, 319 values, 14 files configuration, 194-196 elements, 221-233 .EXE, 216-217 I/O (input/output), 85-88 makefiles, 59-63 finalizers, 137-142 finally exception, 30-34 fixed parameters, 117 fixed statements, 144-145 FlagsAttribute class, 290 floating-point values, 98 flow of control delegating, 28-29 exceptions, 29-36 managing, 23-28

legacy library code 333

formatters, 82 formatting date/time, 101 strings, 100 freeing resources, 134. See also memory functionality of regular expressions, 76-77 functions delegates, 156-159 external, 40 ROT13, 173-174 fusion, 50-51

G
GAC (Global Assembly Cache), 50 gacutil utility, 66-67 GC (garbage collector), 131, 291 GUI (graphical user interface), 20

HI
handling exceptions, 30. See also exceptions heaps, 133 helper classes, BCL (Base Class Library), 94-96 I/O (input/output), BCL (Base Class Library), 85-88 IComparable interface, 292 IdatabaseServer, 109 identifiers, 225-227 IDisposable, 134-137 ilasm utility, 72 ildasm.exe tool, 48

implementation callbacks, 157 finalizers, 137-142 IDatabaseServer, 109 IDisposable, 134, 136-137 properties, 121-128 indexers, declaring, 19-20 inheritance, 36-39 input sections, 223 inspection of code, 215 instances, constructors, 15 instantiation, simple data types, 97-98 Int16 structure, 292-293 Int32 structure, 293-294 Int64 structure, 294-295 integration of COM+, 210-212 interaction interfaces, 108, 110 legacy library code, 172 interfaces, 252-254 GUI (graphical user interface), 20 IComparable, 292 interaction, 108, 110 types, 21-22 intermediate language, 47-48 Internet Protocol (IP), 88 intrinsic operators, 7-9 invocation of metadata, 187-191 IP (Internet Protocol), 88

KL
keywords, 227 languages, unsafe elements, 171-174 legacy library code, interaction of, 172

334 levels

levels, configuring assemblies, 193-194 libraries (BCL) architecture, 73 collections, 78-81 fusion, 50-51 helper classes, 94-96 I/O (input/output), 85-88 makefiles, 59-61 network communication, 88-89 profiles, 74 regular expressions, 76-77 serialization, 82, 84 sockets, 89-94 strings, 74-75 lines, terminators, 223 linking SDK (Software Development Kit), 209-210 literals, 97-98, 227-229 loading, 184-191 logical binary operators, overloading, 17

M
makefiles, 59-63 management assemblies, 66-67 compilation with nmake, 58-61 configuration files, 194-196 flow of control, 23-28 memory, 131-132 finalizers, 137-142 IDisposable, 134-137 optimizing, 145 unsafe code, 174-175 weak references, 142-143 resources, 197 culture-neutral, 198-200 culture-specific, 200-204

manifests, 49 manipulation of strings, 74-75, 98-104 marking code, 170-171 MarshalByRefObject class, 295 Math class, 296-298 memory fixed statements, 144-145 heaps, 133 management, 174-175 managing, 131-132 finalizers, 137-142 IDisposable, 134-137 optimizing, 145 weak references, 142-143 pointers, 165-170 using statements, 144-145 metadata attributes applying, 177-180 customizing, 180-182 reflection, 183 dynamic binding, 184-191 static binding, 183-184 methods Array class, 261-263 Attribute class, 264-265 AttributeUsageAttribute class, 266-267 Boolean structure, 267-268 Byte structure, 268 char structure, 269-270 code, 170-171 Console class, 271-272 constructors, 15-16 Convert class, 272-274 DateTime, 275-278 DBNull class, 279 Decimal structure, 281-283 declaring, 14-15, 117-121

notification of delegates 335

Delegate class, 284 destructors, 15-16 Double structure, 286-287 Environment class, 288 exception constructors, 290 GC class, 291 Icomparable interface, 292 Int16 structure, 292-293 Int32 structure, 293-294 Int64 structure, 294-295 MarshalByRefObject class, 295 Math class, 296-298 MulticastDelegate class, 299 Object class, 299-300 OperatingSystem class, 301 properties, 187-191 Random class, 301-303 single structures, 303-304 String class, 306-310 TimeSpan, 313 TimeZone class, 315 UInt16 structures, 316-317 UInt32 structures, 317 UInt64 structures, 318-319 Uri class, 321-322 UriBuilder class, 323 Version class, 325 Microsoft Interface Definition Language (MIDL), 212 Microsoft-defined intermediate language (MSIL), 47-48 modifiers access, 13 attributes, 183 events, 20 fields, 14 unsafe, 41-42 MSCORCFG.MSC, 195 MSIL (Microsoft-defined intermediate language), 47-48

MulticastDelegate class, 298-299 multicasting delegates, 158 multiple index parameters, 20 Mutex, 155

NO
names, strong, 49 namespaces, 6, 128-130, 233 .NET applications DbgCLR, 68-69, 71 debugging, 67 format specifiers, 100 picture characters, 102 .NET Admin tool, 196 .NET Framework collections, 78-81 helper classes, 94-96 I/O (input/output), 85-88 memory management, 131-132 finalizers, 137-142 IDisposable, 134-137 optimizing, 145 weak references, 142-143 network communication, 88-89 SDK (Software Development Kit), 55 serialization, 82, 84 sockets, 89-94 networks communication, 88-89 helper classes, 94-96 nmake utility, 58-61 nmake.exe build management tool, 205 NonSerializedAttribute class, 299 notification of delegates, 160-162

336 Object class

Object class, 299-300 objects COM+, 210-212 memory finalizers, 137-142 IDisposable, 134-137 managing, 131-132 weak references, 142-143 methods, 117-121 ObsoleteAttribute class, 300 opening metadata, 185 OperatingSystem class, 301 operators, 230 custom, 16-17, 19 DateTime, 278-279 Decimal structure, 283-284 Delegate class, 285 intrinsic, 7-9 MulticastDelegate class, 299 String class, 311 strings, 98-104 TimeSpan, 314 Version class, 325 optimization of memory management, 145 organization elements, 221-233 structures, 110 overloading operators, 16-19

P
parameters arrays, 117 fixed, 117 multiple index, 20 PE (Portable Executable), 49 pictures, characters, 102 Platform Invoke, applying, 167-170 Portable Executable (PE), 49

preprocessor directives, 42-46, 230-232 private assemblies, 50 profiles, BCL (Base Class Library), 74 properties, 260 Array class, 260 Attribute class, 263 AttributeUsageAttribute class, 266 Console class, 271 constructs, 121-128 DateTime, 275 declaring, 19-20 Delegate class, 284 Environment class, 287 exception constructors, 290 GC class, 291 ObsoleteAttribute class, 300 OperatingSystem class, 301 String class, 305 TimeSpan, 312 TimeZone class, 314 Uri class, 320-321 UriBuilder class, 323 Version class, 324 protocols IP (Internet Protocol), 88 TCP (Transmission Control Protocol), 89 UDP (User Datagram Protocol), 89 punctuators, 230

R
RAM (random access memory), 165. See also memory Random class, 301-303 reading metadata from dynamically-bound assemblies, 185-187 properties, 187-191

struct value types 337

references classes, 11-14 custom operators, 16-19 events, 20 indexers, 19-20 methods, 14-16 properties, 19-20 value fields, 14 fusion, 50-51 types, 222-223 weak, 142-143 reflection, 177, 183 dynamic binding, 184-191 static binding, 183-184 regasm utility, 211-212 regular expressions, BCL (Base Class Library), 76-77 relationships, 36 resolution, fusion, 50-51 resources. See also memory culture-neutral, 198-200 culture-specific, 200-204 managing, 197 restrictions of COM+, 210-212 ROM (read-only memory), 165 ROT13, 173-174 runtime, CLR (Common Language Runtime), 48-49

S
SDK (Software Development Kit), 55 compiling, 205-210 debugging, 215 deploying, 216-217 sections, input, 223 sequences, escape, 225 serialization, BCL (Base Class Library), 82-84

SerializationAttribute class, 303 servers classes, 106-107 databases, 108-110 shared assemblies, 50 simple data types, 97, 97-98 single structures, 303-304 sn utility, 61-66 sockets, 89-94 Software Development Kit. See SDK specifiers dates, 101 .NET format, 100 time, 101 types, 98 stackalloc statements, 174-175 starting threads, 147-151 statements, 239-244 control, 23-28 delegating, 28-29 exceptions, 29-34 throwing exceptions, 34-36 fixed, 144-145 SimpleStart {}, 6 stackalloc, 174-175 streams, 6 using, 144-145 static binding, 183-184 status documents, 111-112 streams sockets, 92 statements, 6 String class, 305-311 strings, 98-104 BCL (Base Class Library), 74-75 strong names, 49 struct value types, 10-11

338 structures

structures, 5-6, 251-252 Boolean, 267-268 Byte, 268 char, 269-270 DateTime, 274-279 Decimal, 279-284 Double, 285-287 elements, 221-233 Int16, 292-293 Int32, 293-294 Int64, 294-295 organizing, 110 single, 303-304 TimeSpan, 311-314 UInt16, 316-317 UInt32, 317 UInt64, 318-319 switches al, 65-66 compilers, 55 disassembly, 72 gacutil, 67 nmake, 59 sn, 62 symbols, namespaces, 128-130 synchronization, 151-156 syntax, 5-6, 66

T
TCP (Transmission Control Protocol), 89 terminators, lines, 223 threads, execution of, 147-151 ThreadStaticAttribute class, 311 throwing exceptions, 34-36 time, specifiers, 101 TimeSpan structures, 311-314 TimeZone class, 314-315

tlbimp utility, 212-215 tokens, 225 tools DbgCLR, 68-69, 71 development, 55 al, 61-66 csc, 55-58 gacutil, 66-67 nmake, 58-61 sn, 61-66 ilasm, 72 ildasm.exe, 48 nmake.exe build management, 205 regasm, 211-212 tlbimp, 212-213, 215 Transmission Control Protocol (TCP), 89 troubleshooting access, 155 code, 40, 215 calling external functions, 40 writing unsafe code, 41-42 memory, 174-175 pointers, 165-166 applying memory with Platform Invoke, 167-170 unsafe context, 170-171 unsafe language elements, 171-174 try exception, 30-34 TypeCode enumeration, 315-316 types, 7, 233-234 attributes, 177-180 converting, 103-104 CYS base types, 48-49 Decimal structure, 283-284 enumerating, 111-112 interfaces, 21-22 references, 222-223 simple data, 97-98

writing unsafe code 339

specifiers, 98 values, 7, 221-222 built-in, 7-11 classes, 11-20

U
UDP (User Datagram Protocol), 89 UInt16 structure, 316-317 UInt32 structure, 317 UInt64 structure, 318-319 unary operators, overloading, 16-17, 19 unicode escape sequences, 225 unsafe code, 40, 257-258 external functions, 40 memory, 174-175 pointers, 165-166 applying memory with Platform Invoke, 167-170 context, 170-171 language elements, 171-174 writing, 41-42 Uri class, 319-322 UriBuilder class, 322-323 UriHostNameType enumeration, 324 UriPartial enumeration, 324

User Datagram Protocol (UDP), 89 using statements, 144-145 utilities. See also tools; troubleshooting regasm, 211-212 ROT13, 173 tlbimp, 212-213, 215

VW
values fields, 14 floating-point, 98 types, 7, 221-222 built-in, 7-11 classes, 11-20 variables, 235 built-in values, 9 instantiation, 97-98 Version class, 324-325 viewing assemblies, 71-72 weak references, 142-143 whitespace, 224 wrapping ROT13 utility functions, 173 writing unsafe code, 41-42

Anda mungkin juga menyukai