C# - Net
C# - Net
AND .NET
PRENTICE HALL
CORE SERIES
Stephen C. Perry
Part I
FUNDAMENTALS OF C# PROGRAMMING AND
INTRODUCTION TO .NET 2
C ha p t er 1
INTRODUCTION TO .NET AND C# 4
1.1 Overview of the .NET Framework 6
Microsoft .NET and the CLI Standards 7
1.2 Common Language Runtime 9
Compiling .NET Code 10
Common Type System 11
Assemblies 13
1.3 Framework Class Library 18
v
vi Contents
Chapter 2
C# LANGUAGE FUNDAMENTALS 38
2.1 The Layout of a C# Program 40
General C# Programming Notes 42
2.2 Primitives 45
decimal 47
bool 47
char 48
byte, sbyte 48
short, int, long 48
single, double 49
Using Parse and TryParse to Convert a Numeric String 49
2.3 Operators: Arithmetic, Logical, and Conditional 50
Arithmetic Operators 50
Conditional and Relational Operators 51
Control Flow Statements 52
if-else 53
switch 54
2.4 Loops 55
while loop 55
Contents vii
do loop 56
for loop 56
foreach loop 57
Transferring Control Within a Loop 58
2.5 C# Preprocessing Directives 59
Conditional Compilation 60
Diagnostic Directives 60
Code Regions 61
2.6 Strings 61
String Literals 61
String Manipulation 63
2.7 Enumerated Types 66
Working with Enumerations 66
System.Enum Methods 68
Enums and Bit Flags 69
2.8 Arrays 69
Declaring and Creating an Array 70
Using System.Array Methods and Properties 71
2.9 Reference and Value Types 73
System.Object and System.ValueType 73
Memory Allocation for Reference and Value Types 74
Boxing 75
Summary of Value and Reference Type Differences 77
2.10 Summary 78
2.11 Test Your Understanding 78
Chapter 3
CLASS DESIGN IN C# 80
3.1 Introduction to a C# Class 82
3.2 Defining a Class 82
Attributes 83
Access Modifiers 85
viii Contents
Chapter 4
WORKING WITH OBJECTS IN C# 144
4.1 Object Creation 145
Example: Creating Objects with Multiple Factories 148
4.2 Exception Handling 149
System.Exception Class 150
Writing Code to Handle Exceptions 151
Example: Handling Common SystemException Exceptions 153
How to Create a Custom Exception Class 155
Unhandled Exceptions 157
Exception Handling Guidelines 159
4.3 Implementing System.Object Methods in a Custom Class 160
ToString() to Describe an Object 161
Equals() to Compare Objects 163
Cloning to Create a Copy of an Object 165
4.4 Working with .NET Collection Classes and Interfaces 167
Collection Interfaces 168
System.Collections Namespace 177
Stack and Queue 177
ArrayList 179
Hashtable 181
System.Collections.Generic Namespace 184
4.5 Object Serialization 187
Binary Serialization 188
4.6 Object Life Cycle Management 192
.NET Garbage Collection 192
4.7 Summary 198
4.8 Test Your Understanding 198
x Contents
Par t II
CREATING APPLICATIONS USING THE
.NET FRAMEWORK CLASS LIBRARY 200
Chapter 5
C# TEXT MANIPULATION AND FILE I/O 202
5.1 Characters and Unicode 204
Unicode 204
Working with Characters 205
5.2 The String Class 209
Creating Strings 209
Overview of String Operations 211
5.3 Comparing Strings 212
Using String.Compare 213
Using String.CompareOrdinal 215
5.4 Searching, Modifying, and Encoding a String’s Content 216
Searching the Contents of a String 216
Searching a String That Contains Surrogates 217
String Transformations 217
String Encoding 219
5.5 StringBuilder 220
StringBuilder Class Overview 221
StringBuilder Versus String Concatenation 222
5.6 Formatting Numeric and DateTime Values 223
Constructing a Format Item 224
Formatting Numeric Values 225
Formatting Dates and Time 227
5.7 Regular Expressions 232
The Regex Class 232
Creating Regular Expressions 237
A Pattern Matching Example 239
Contents xi
Chapter 6
BUILDING WINDOWS FORMS APPLICATIONS 266
6.1 Programming a Windows Form 268
Building a Windows Forms Application by Hand 268
6.2 Windows.Forms Control Classes 271
The Control Class 272
Working with Controls 274
Control Events 279
6.3 The Form Class 285
Setting a Form’s Appearance 286
Setting Form Location and Size 290
Displaying Forms 292
The Life Cycle of a Modeless Form 292
xii Contents
Chapter 7
WINDOWS FORMS CONTROLS 318
7.1 A Survey of .NET Windows Forms Controls 319
7.2 Button Classes, Group Box, Panel, and Label 323
The Button Class 323
The CheckBox Class 324
The RadioButton Class 325
The GroupBox Class 327
The Panel Class 328
The Label Class 330
7.3 PictureBox and TextBox Controls 331
The PictureBox Class 331
The TextBox Class 333
7.4 ListBox, CheckedListBox, and ComboBox Classes 335
The ListBox Class 335
Contents xiii
Chapter 8
.NET GRAPHICS USING GDI+ 378
8.1 GDI+ Overview 380
The Graphics Class 380
The Paint Event 384
8.2 Using the Graphics Object 388
Basic 2-D Graphics 388
Pens 393
Brushes 395
Colors 400
A Sample Project: Building a Color Viewer 402
xiv Contents
Chapter 9
FONTS, TEXT, AND PRINTING 426
9.1 Fonts 428
Font Families 428
The Font Class 430
9.2 Drawing Text Strings 433
Drawing Multi-Line Text 434
Formatting Strings with the StringFormat Class 435
Using Tab Stops 436
String Trimming, Alignment, and Wrapping 438
9.3 Printing 439
Overview 439
PrintDocument Class 441
Printer Settings 442
Page Settings 445
PrintDocument Events 446
PrintPage Event 448
Previewing a Printed Report 449
A Report Example 450
Creating a Custom PrintDocument Class 454
9.4 Summary 457
9.5 Test Your Understanding 458
Contents xv
Chapter 10
WORKING WITH XML IN .NET 460
10.1 Working with XML 462
Using XML Serialization to Create XML Data 462
XML Schema Definition (XSD) 466
Using an XML Style Sheet 468
10.2 Techniques for Reading XML Data 472
XmlReader Class 472
XmlNodeReader Class 477
The XmlReaderSettings Class 479
Using an XML Schema to Validate XML Data 480
Options for Reading XML Data 481
10.3 Techniques for Writing XML Data 482
10.4 Using XPath to Search XML 485
Constructing XPath Queries 486
XmlDocument and XPath 489
XPathDocument and XPath 490
XmlDataDocument and XPath 491
10.5 Summary 493
10.6 Test Your Understanding 494
Chapter 11
ADO.NET 496
11.1 Overview of the ADO.NET Architecture 498
OLE DB Data Provider in .NET 498
.NET Data Provider 499
11.2 Data Access Models: Connected and Disconnected 502
Connected Model 502
Disconnected Model 504
11.3 ADO.NET Connected Model 506
Connection Classes 506
xvi Contents
ChapteR 12
DATA BINDING WITH WINDOWS FORMS CONTROLS 544
12.1 Overview of Data Binding 546
Simple Data Binding 546
Complex Data Binding with List Controls 549
One-Way and Two-Way Data Binding 550
Using Binding Managers 552
12.2 Using Simple and Complex Data Binding in an Application 555
Binding to a DataTable 555
Binding Controls to an ArrayList 558
Adding an Item to the Data Source 560
Identifying Updates 561
Update Original Database with Changes 562
12.3 The DataGridView Class 563
Properties 564
Contents xvii
Events 571
Setting Up Master-Detail DataGridViews 576
Virtual Mode 579
12.4 Summary 585
12.5 Test Your Understanding 585
Part III
ADVANCED USE OF C# AND THE .NET FRAMEWORK 588
Chapter 13
ASYNCHRONOUS PROGRAMMING
AND MULTITHREADING 590
13.1 What Is a Thread? 592
Multithreading 592
13.2 Asynchronous Programming 595
Asynchronous Delegates 596
Examples of Implementing Asynchronous Calls 599
13.3 Working Directly with Threads 609
Creating and Working with Threads 609
Multithreading in Action 613
Using the Thread Pool 617
Timers 618
13.4 Thread Synchronization 620
The Synchronization Attribute 622
The Monitor Class 623
The Mutex 625
The Semaphore 627
Avoiding Deadlock 628
Summary of Synchronization Techniques 630
13.5 Summary 631
13.6 Test Your Understanding 631
xviii Contents
Chapter 14
CREATING DISTRIBUTED
APPLICATIONS WITH REMOTING 636
14.1 Application Domains 638
Advantages of AppDomains 638
Application Domains and Assemblies 639
Working with the AppDomain Class 640
14.2 Remoting 643
Remoting Architecture 644
Types of Remoting 648
Client-Activated Objects 650
Server-Activated Objects 650
Type Registration 652
Remoting with a Server-Activated Object 654
Remoting with a Client-Activated Object (CAO) 664
Design Considerations in Creating a Distributed Application 670
14.3 Leasing and Sponsorship 671
Leasing 672
Sponsorship 675
14.4 Summary 678
14.5 Test Your Understanding 678
Chapter 15
CODE REFINEMENT, SECURITY, AND DEPLOYMENT 680
15.1 Following .NET Code Design Guidelines 682
Using FxCop 683
15.2 Strongly Named Assemblies 686
Creating a Strongly Named Assembly 687
Delayed Signing 688
Global Assembly Cache (GAC) 689
Versioning 690
Contents xix
Part IV
PROGRAMMING FOR THE INTERNET 730
Chapter 16
ASP.NET WEB FORMS AND CONTROLS 732
16.1 Client-Server Interaction over the Internet 734
Web Application Example: Implementing a BMI Calculator 735
Using ASP.NET to Implement a BMI Calculator 740
Inline Code Model 741
xx Contents
Chapter 17
THE ASP.NET APPLICATION ENVIRONMENT 806
17.1 HTTP Request and Response Classes 808
HttpRequest Object 808
Contents xxi
Chapter 18
XML WEB SERVICES 868
18.1 Introduction to Web Services 870
Discovering and Using a Web Service 871
18.2 Building an XML Web Service 875
Creating a Web Service by Hand 875
Creating a Web Service Using VS.NET 878
xxii Contents
Appendix A
FEATURES SPECIFIC TO .NET 2.0 AND C# 2.0 920
Appendix B
DATAGRIDVIEW EVENTS AND DELEGATES 924
INDEX 952
Chapter
xxiii
This page intentionally left blank
Chapter
Learning a new programming language, or even a new version of one, can be a lot
like traveling to a foreign country. Some things will look familiar. Some things will
look very odd. You can usually get by if you stick to the familiar; but, who wants to
just “get by”? Often times, it’s the odd things in a country, culture, or language where
you can realize many interesting and valuable benefits.
To do it right, however, you’ll need a proper guide or guide book. This is essential.
Just as a good guide book will tell you what to see, when to see it, what to eat, what to
avoid, and a few tips, so will a good programming book tell you what frameworks and
classes to use, how to call their methods, what bad practices to avoid, and a few pro-
ductivity tips and best practices.
This is exactly what Steve has provided you.
If you were to approach C# 2.0 from a 1.0 perspective, or from some other lan-
guage background, you’d be missing out on all its new offerings. For example, I’m a
seasoned developer and set in my ways. If you’re like me, then you probably still
write methods, loops, and other design patterns the same way you have for many
years. You know it’s not producing the most efficient code, but it’s quick. It’s easy to
read and understand; and it works.
Steve has literally written the “Core” C# book here. He begins by introducing you
to the important C# concepts and their interaction with the .NET Framework; but,
then he deviates from the other C# reference books on the market and jumps right
into the application of C#. These include most of the common, daily tasks that you
could be asked to perform, such as working with text, files, databases, XML, Win-
dows forms and controls, printing, ASP.NET Web applications, Web services, and
xxv
xxvi Foreword
remoting. Steve even provides you with asynchronous, multithreaded, security, and
deployment topics as a bonus. You won’t need another book on your shelf.
So, what are you waiting for? I think it’s time to break a few of your familiar habits
and experience some new culture!
Richard Hundhausen
Author, Introducing Microsoft Visual Studio 2005 Team System
Chapter
Thirty-seven years later, programmers still experience the same creative satisfaction
from developing a well-crafted program. It can be 10 lines of recursive code that
pops into one’s head at midnight, or it can be an entire production management sys-
tem whose design requires a year of midnights. Then, as now, good programs still
convey an impression of logic and naturalness—particularly to their users.
But the challenges have evolved. Software is required to be more malleable—it
may be run from a LAN, the Internet, or a cellular phone. Security is also a much
bigger issue, because the code may be accessible all over the world. This, in turn,
raises issues of scalability and how to synchronize code for hundreds of concurrent
users. More users bring more cultures, and the concomitant need to customize pro-
grams to meet the language and culture characteristics of a worldwide client base.
.NET—and the languages written for it—addresses these challenges as well as any
unified development environment. This book is written for developers, software
architects, and students who choose to work with the .NET Framework. All code in
the book is written in C#, although only one chapter is specifically devoted to the
syntactical structure of the C# language.
This book is not an introduction to programming—it assumes you are experienced
in a computer language. This book is not an introduction to object-oriented program-
xxvii
xxviii Preface
Although some will disagree, if you really want to learn C# and .NET, shut down
your IDE, pull out your favorite text editor, and learn how to use the C# compiler
from the command line. After you have mastered the fundamentals, you can switch
to VS.NET and any other IDE for production programming.
Finally, a word about .NET and Microsoft: This book was developed using
Microsoft .NET 1.x and Whidbey betas. It includes topics such as ADO.NET and
ASP.NET that are very much Microsoft proprietary implementations. In fact,
Microsoft has applied to patent these methodologies. However, all of C# and many of
the .NET basic class libraries are based on a standard that enables them to be ported
to other platforms. Now, and increasingly in the future, many of the techniques
described in this book will be applicable to .NET like implementations (such as the
Mono project, http://www.mono-project.com/Main_Page) on non-Windows
platforms.
Chapter
I have received assistance from a great number of people over the 21 months that
went into the research and development of this book. I wish to thank first my wife,
Rebecca, who tirelessly read through pages of unedited manuscripts, and used her
systems programming background to add valuable recommendations. Next, I wish to
thank the reviewers whose recommendations led to better chapter organization,
fewer content and code errors, and a perspective on which topics to emphasize.
Reviewers included Greg Beamer, James Edelen, Doug Holland, Curtiss Howard,
Anand Narayanaswamy, and Gordon Weakliem. Special thanks go to Richard
Hundhausen whose recommendations went well beyond the call of duty; and Cay
Horstmann, who read every preliminary chapter and whose Java allegiance made
him a wonderful and influential “Devil’s Advocate.” I also wish to thank Dr. Alan
Tharp who encouraged the idea of writing a book on .NET and remains my most
respected advisor in the computer profession.
Finally, it has been a pleasure working with the editors and staff at Prentice Hall
PTR. I’m particularly grateful for the efforts of Stephane Nakib, Joan Murray, Ebony
Haight, Jessica D’Amico, Kelli Brooks, and Vanessa Moore. This book would not
exist without the efforts of my original editor Stephane Nakib. The idea for the book
was hers, and her support for the project kept it moving in the early stages. My other
editor, Joan Murray, took over midway through the project and provided the over-
sight, advice, and encouragement to complete the project. Production editor Vanessa
Moore and copy editor Kelli Brooks performed the “dirty work” of turning the final
manuscript—with its myriad inconsistencies and word misuse—into a presentable
book. To them, I am especially grateful. Working with professionals such as these was
of inestimable value on those days when writing was more Sisyphean than satisfying.
xxx
This page intentionally left blank
FUNDAMENTALS OF
C# PROGRAMMING
AND
INTRODUCTION TO
.NET
I
■ Chapter 1
Introduction to .NET and C# 4
■ Chapter 2
C# Language Fundamentals 38
■ Chapter 3
Class Design in C# 80
■ Chapter 4
Working with Objects in C# 144
INTRODUCTION TO
.NET AND C#
The effective use of a language requires a good deal more than learning the syntax
and features of the language. In fact, the greater part of the learning curve for new
technology is now concentrated in the programming environment. It is not enough to
be proficient with the C# language; the successful developer and software architect
must also be cognizant of the underlying class libraries and the tools available to
probe these libraries, debug code, and check the efficiency of underlying code.
The purpose of this chapter is to provide an awareness of the .NET environment
before you proceed to the syntax and semantics of the C# language. The emphasis is
on how the environment, not the language, will affect the way you develop software.
If you are new to .NET, it is necessary to embrace some new ideas. .NET changes
the way you think of dealing with legacy code and version control; it changes the way
program resources are disposed of; it permits code developed in one language to be
used by another; it simplifies code deployment by eliminating a reliance on the sys-
tem registry; and it creates a self-describing metalanguage that can be used to deter-
mine program logic at runtime. You will bump into all of these at some stage of the
software development process, and they will influence how you design and deploy
your applications.
To the programmer’s eye, the .NET platform consists of a runtime environment
coupled with a base class library. The layout of this chapter reflects that viewpoint. It
contains separate sections on the Common Language Runtime (CLR) and the
Framework Class Library (FCL). It then presents the basic tools that a developer
may use to gain insight into the inner workings of the .NET Framework, as well as
manage and distribute applications. As a prelude to Chapter 2, the final section intro-
duces the C# compiler with examples of its use.
5
6 Chapter 1 ■ Introduction to .NET and C#
Data Classes
ADO.NET, XML, SQL
Base Classes
System.IO, System.Drawing, System.Threading
Operating System
1. ECMA International was formerly known as the European Computer Manufacturers Association
and is referred to herein simply as the ECMA.
1.1 Overview of the .NET Framework 7
Runtime Infrastructure
Library
Base Class
Library
Kernel Profile
Compact Profile
The good news for developers—and readers of this book—is that the additional
Microsoft features are being adopted by CLI open source initiatives. Mono2, one of
the leading CLI projects, already includes major features such as ADO.NET, Win-
dows Forms, full XML classes, and a rich set of Collections classes. This is particularly
significant because it means the knowledge and skills obtained working with Microsoft
.NET can be applied to implementations on Linux, BSD, and Solaris platforms. With
that in mind, let’s take an overview of the Microsoft CLI implementation.
Base Class
IL + Metadata Libraries
CPU
2. http://www.mono-project.com/Main_Page
10 Chapter 1 ■ Introduction to .NET and C#
• The most important use is by the JIT compiler, which gathers all the
type information it needs for compiling directly from the metacode. It
also uses this information for code verification to ensure the program
performs correct operations. For example, the JIT ensures that a
method is called correctly by comparing the calling parameters with
those defined in the method’s metadata.
• Metadata is used in the Garbage Collection process (memory
management). The garbage collector (GC) uses metadata to know
when fields within an object refer to other objects so that the GC can
determine what objects can and can’t have their memory reclaimed.
• .NET provides a set of classes that provide the functionality to read
metadata from within a program. This functionality is known
collectively as reflection. It is a powerful feature that permits a
program to query the code at runtime and make decisions based on its
discovery. As we will see later in the book, it is the key to working with
custom attributes, which are a C#-supported construct for adding
custom metadata to a program.
1.2 Common Language Runtime 11
Object
Class Primitives
Interface Structures
Array Enums
Two things stand out in this figure. The most obvious is that types are categorized
as reference or value types. This taxonomy is based on how the types are stored and
accessed in memory: reference types are accessed in a special memory area (called a
heap) via pointers, whereas value types are referenced directly in a program stack.
The other thing to note is that all types, both custom and .NET defined, must inherit
from the predefined System.Object type. This ensures that all types support a
basic set of inherited methods and properties.
12 Chapter 1 ■ Introduction to .NET and C#
Core Note
A compiler that is compliant with the CTS specifications is guaranteed that its
types can be hosted by the Common Language Runtime. This alone does not guaran-
tee that the language can communicate with other languages. There is a more restric-
tive set of specifications, appropriately called the Common Language Specification
(CLS), that provides the ultimate rules for language interoperability. These specifica-
tions define the minimal features that a compiler must include in order to target the
CLR.
Table 1-1 contains some of the CLS rules to give you a flavor of the types of fea-
tures that must be considered when creating CLS-compliant types (a complete list is
included with the .NET SDK documentation).
Feature Rule
Visibility (Scope) The rules apply only to those members of a type that are avail-
able outside the defining assembly.
Characters and casing For two variables to be considered distinct, they must differ by
more than just their case.
Primitive types The following primitive data types are CLS compliant:
Byte, Int16, Int32, Int64, Single, Double, Boolean, Char,
Decimal, IntPtr, and String.
Constructor invocation A constructor must call the base class’s constructor before it can
access any of its instance data.
Array bounds All dimensions of arrays must have a lower bound of zero (0).
Method signature All return and parameter types used in a type or member
signature must be CLS compliant.
These rules are both straightforward and specific. Let’s look at a segment of C#
code to see how they are applied:
1.2 Common Language Runtime 13
Even if you are unfamiliar with C# code, you should still be able to detect where
the code fails to comply with the CLS rules. The second rule in the table dictates that
different names must differ by more than case. Obviously, Metric fails to meet this
rule. This code runs fine in C#, but a program written in Visual Basic.NET—which
ignores case sensitivity—would be unable to distinguish between the upper and low-
ercase references.
Assemblies
All of the managed code that runs in .NET must be contained in an assembly. Logi-
cally, the assembly is referenced as one EXE or DLL file. Physically, it may consist of
a collection of one or more files that contain code or resources such as images or
XML data.
Assembly
Name: FabricLib
Manifest Other files:
Public types:
Type: Private
Version Number: 1.1.3.04
Metadata
Strong Name:
IL
FabricLib.dll
Manifest. Each assembly must have one file that contains a manifest. The man-
ifest is a set of tables containing metadata that lists the names of all files in the
assembly, references to external assemblies, and information such as name and
version that identify the assembly. Strongly named assemblies (discussed later)
also include a unique digital signature. When an assembly is loaded, the CLR’s
first order of business is to open the file containing the manifest so it can identify
the members of the assembly.
IL. The role of Intermediate Language has already been discussed. Before the
CLR can use IL, it must be packaged in an EXE or DLL assembly. The two are
not identical: an EXE assembly must have an entry point that makes it execut-
able; a DLL, on the other hand, is designed to function as a code library holding
type definitions.
The assembly is more than just a logical way to package executable code. It forms
the very heart of the .NET model for code deployment, version control, and security:
As mentioned, an assembly may contain multiple files. These files are not
restricted to code modules, but may be resource files such as graphic images and text
files. A common use of these files is to permit resources that enable an application to
provide a screen interface tailored to the country or language of the user. There is no
limit to the number of files in the assembly. Figure 1-6 illustrates the layout of a
multi-file assembly.
Assembly
Manifest Metadata
Metadata IL (MSIL)
ApparelLib.dll
IL
FabricLib.dll Schematic.jpg
In the multi-file assembly diagram, notice that the assembly’s manifest contains
the information that identifies all files in the assembly.
Although most assemblies consist of a single file, there are several cases where
multi-file assemblies are advantageous:
Multi-file assemblies can be created by executing the C# compiler from the com-
mand line or using the Assembly Linker utility, Al.exe. An example using the C#
compiler is provided in the last section of this chapter. Notably, Visual Studio.NET
2005 does not support the creation of multi-file assemblies.
Public assemblies are usually located in the assembly directory located beneath
the system directory of the operating system (WINNT\ on a Microsoft Windows 2000
operating system). As shown in Figure 1-7, the assemblies are listed in a special for-
mat that displays their four attributes (.NET Framework includes a DLL file that
extends Windows Explorer to enable it to display the GAC contents). Let’s take a
quick look at these four attributes:
Assembly Name. Also referred to as the friendly name, this is the file name of
the assembly minus the extension.
Version. Every assembly has a version number that applies to all files in the
assembly. It consists of four numbers in the format
<major number>.<minor number>.<build>.<revision>
1.2 Common Language Runtime 17
Typically, the major and minor version numbers are updated for changes that
break backward compatibility. A version number can be assigned to an assembly
by including an AssemblyVersion attribute in the assembly’s source code.
Public Key Token. To ensure that a shared assembly is unique and authentic,
.NET requires that the creator mark the assembly with a strong name. This pro-
cess, known as signing, requires the use of a public/private key pair. When the
compiler builds the assembly, it uses the private key to generate a strong name.
The public key is so large that a token is created by hashing the public key and
taking its last eight bytes. This token is placed in the manifest of any client
assembly that references a shared assembly and is used to identify the assembly
during execution.
Core Note
Precompiling an Assembly
After an assembly is loaded, the IL must be compiled to the machine’s native code. If
you are used to working with executables already in a machine code format, this
should raise questions about performance and whether it’s possible to create equiva-
lent “executables” in .NET. The answer to the second part of the statement is yes;
.NET does provide a way to precompile an assembly.
The .NET Framework includes a Native Image Generator (Ngen) tool that is used
to compile an assembly into a “native image” that is stored in a native image cache—
a reserved area of the GAC. Any time the CLR loads an assembly, it checks the cache
to see if it has an associated native image available; if it does, it loads the precompiled
code. On the surface, this seems a good idea to improve performance. However, in
reality, there are several drawbacks.
Ngen creates an image for a hypothetical machine architecture, so that it will run,
for example, on any machine with an x86 processor. In contrast, when the JIT in
.NET runs, it is aware of the specific machine it is compiling for and can accordingly
18 Chapter 1 ■ Introduction to .NET and C#
make optimizations. The result is that its output often outperforms that of the pre-
compiled assembly. Another drawback to using a native image is that changes to a
system’s hardware configuration or operating system—such as a service pack
update—often invalidate the precompiled assembly.
Core Recommendation
Code Verification
As part of the JIT compile process, the Common Language Runtime performs two
types of verification: IL verification and metadata validation. The purpose is to
ensure that the code is verifiably type-safe. In practical terms, this means that param-
eters in a calling and called method are checked to ensure they are the same type, or
that a method returns only the type specified in its return type declaration. In short,
the CLR searches through the IL and metadata to make sure that any value assigned
to a variable is of a compatible type; if not, an exception occurs.
Core Note
A benefit of verified code is that the CLR can be certain that the code cannot
affect another application by accessing memory outside of its allowable range. Con-
sequently, the CLR is free to safely run multiple applications in a single process or
address space, improving performance and reducing the use of OS resources.
any language that targets the CLR. This is significant, because it means that libraries
are no longer tied to specific compilers. As a developer, you can familiarize yourself
with the types in a library and be assured that you can use this knowledge with what-
ever .NET language you choose.
The resources within the FCL are organized into logical groupings called
namespaces. For the most part, these groupings are by broad functionality. For exam-
ple, types used for graphical operations are grouped into the System.Drawing and
System.Drawing.Drawing2D namespaces; types required for file I/O are members
of the System.IO namespace. Namespaces represent a logical concept, not a physi-
cal one.
The FCL comprises hundreds of assemblies (DLLs), and each assembly may con-
tain multiple namespaces. In addition, a namespace may span multiple assemblies.
To demonstrate, let’s look inside an FCL assembly.
Table 1-2 lists some of the most important namespaces in .NET. For reference,
the last column in each row includes a chapter number in this book where you’ll find
the namespace(s) used.
Namespaces provide a roadmap for navigating the FCL. For example, if your
applications are Web based, you’ll spend most of your time exploring the types in the
System.Web.* namespaces. After you have learned the basics of .NET and gained
proficiency with C#, you’ll find that much of your time is spent familiarizing yourself
with the built-in types contained in the Framework Class Library.
3. http://msdn.microsoft.com/netframework/downloads/updates/
default.aspx
1.4 Working with the .NET Framework and SDK 23
\winnt\Microsoft.NET\Framework\v1.0.3705
\winnt\Microsoft.NET\Framework\v1.1.4322
\winnt\Microsoft.NET\Framework\v2.0.40607
The installation of any new software version raises the question of compatibility
with applications developed using an older version. .NET makes it easy to run exist-
ing applications against any framework version. The key to this is the application con-
figuration file (discussed in much greater detail in Chapter 15). This text file contains
XML tags and elements that give the CLR instructions for executing an application.
It can specify where external assemblies are located, which version to use, and, in this
case, which versions of the .NET Framework an application or component supports.
The configuration file can be created with any text editor, although it’s preferable to
rely on tools (such as the Framework Configuration tool) designed for the task. Your
main use of the configuration file will be to test current applications against new
framework releases. Although it can be done, it usually makes no sense to run an
application against an earlier version than it was originally compiled against.
Many of these are better discussed within the context of later chapters. However,
it is useful to be aware of which tools are available for performing these tasks; and a
24 Chapter 1 ■ Introduction to .NET and C#
few, such as those for exploring classes and assemblies, should be mastered early in
the .NET learning curve.
Table 1-3 lists some of the useful tools available to develop and distribute your
applications. Three of these, Ildasm.exe, wincv.exe, and the .NET Framework
Configuration tool, are the subject of further discussion.
Tool Description
c:\Program Files\Microsoft.NET\SDK\v2.0\Bin
To execute the tools at the command line (on a Windows operating system) while
in any directory, it is first necessary to place the path to the utilities in the system
Path variable. To do this, follow these steps:
If you have Visual Studio installed, a simpler approach is to use the preconfigured
Visual Studio command prompt. It automatically initializes the path information that
enables you to access the command-line tools.
Ildasm.exe
The Intermediate Language Disassembler utility is supplied with the .NET Frame-
work SDK and is usually located in the Bin subdirectory along the path where the
SDK is installed. It is invaluable for investigating the .NET assembly environment
and is one of the first tools you should become familiar with as you begin to work
with .NET assemblies and code.
The easiest way to use the utility is to type in
C:\>Ildasm /adv
Double clicking on the Metric method brings up a screen that displays its IL
(Figure 1-10).
Ildasm can be used as a learning tool to solidify the concepts of IL and assemblies.
It also has some practical uses. Suppose you have a third-party component (assem-
bly) to work with for which there is no documentation. Ildasm provides a useful start-
ing point in trying to uncover the interface details of the assembly.
Core Suggestion
Ildasm has a File – Dump menu option that makes it useful for saving
program documentation in a text file. Select Dump Metainfo to create a
lengthy human-readable form of the assembly’s metadata; select Dump
Statistics to view a profile of the assembly that details how many bytes
each part uses.
Source
Compile MSIL CLR
Code
The obfuscated code is functionally equivalent to the assembly’s IL code and pro-
duces identical results when run by the CLR. How does it do this? The most com-
mon trick is to rename meaningful types and members with names that have no
intrinsic meaning. If you look at obfuscated code, you’ll see a lot of types named “a”
28 Chapter 1 ■ Introduction to .NET and C#
or “b,” for example. Of course, the obfuscation algorithm must be smart enough not
to rename types that are used by outside assemblies that depend on the original
name. Another common trick is to alter the control flow of the code without chang-
ing the logic. For example, a while statement may be replaced with a combination
of goto and if statements.
An obfuscator is not included in the .NET SDK. Dotfuscator Community Edition,
a limited-feature version of a commercial product, is available with Visual Stu-
dio.NET. Despite being a relatively unsophisticated product—and only available for
the Microsoft environment—it is a good way to become familiar with the process.
Several vendors now offer more advanced obfuscator products.
wincv.exe
WinCV is a class viewer analogous to the Visual Studio Object Viewer, for those not
using Visual Studio. It is located in the Program Files\Microsoft.Net\
SDK\V1.x\Bin directory and can be run from the command prompt. When the win-
dow appears, type the name of the class you want to view into the Searching For box
(see Figure 1-12).
Figure 1-12 Using WinCV to view type definition of the Array class
WinCV provides a wealth of information about any type in the base class libraries.
The four highlighted areas provide a sampling of what is available:
1.4 Working with the .NET Framework and SDK 29
To illustrate a practical use of the configuration tool, let’s look at how it can be
used to address one of the most common problems that plagues the software devel-
opment process: the need to drop back to a previous working version when a current
application breaks. This can be a difficult task when server DLLs or assemblies are
involved. .NET offers a rather clever solution to this problem: Each time an applica-
tion runs, it logs the set of assemblies that are used by the program. If they are
unchanged from the previous run, the CLR ignores them; if there are changes, how-
ever, a snapshot of the new set of assemblies is stored.
30 Chapter 1 ■ Introduction to .NET and C#
When an application fails, one option for the programmer is to revert to a previous
version that ran successfully. The configuration tool can be used to redirect the applica-
tion to an earlier assembly. However, there may be multiple assemblies involved. This
is where the configuration tool comes in handy. It allows you to view previous assembly
configurations and select the assemblies en masse to be used with the application.
To view and select previous configurations, select Applications – Fix an Applica-
tion from the Configuration tool menu. Figure 1-13 combines the two dialog boxes
that subsequently appear. The main window lists applications that have run and been
recorded. The smaller window (a portion of a larger dialog) is displayed when you
click on an application. This window lists the most recent (up to five) configurations
associated with the application. You simply select the assembly configuration that you
want the application to use.
Visual Studio
IL + Metacode
SRC1
Third-Party IDE
SharpDevelop CSC.EXE APP.EXE
SRC2
Text Editor .cs
External
LIB.DLL Assembly
Figure 1-14 shows the basic steps that occur in converting source code to the final
compiled output. The purpose of this section is to demonstrate how a text editor and
the C# compiler can be used to build an application. Along the way, it will provide a
detailed look at the many compiler options that are hidden by the IDE.
C:\winnt\Microsoft.NET\Framework\v2.0.40607
Of course, this may vary depending on your operating system and the version of
Framework installed. To make the compiler available from the command line in any
32 Chapter 1 ■ Introduction to .NET and C#
current directory, you must add this path to the system Path variable. Follow the
steps described in the previous section for setting the path for the SDK utilities.
Type in the following statement at the command line to verify that the compiler
can be accessed:
C:\>csc /help
Both statements compile the source into an executable (.exe) file—the default
output from the compiler. As shown in Table 1-4, the output type is specified using
the /t: flag. To create a DLL file, set the target value to library. For a WinForms
application, specify /t:winexe. Note that you can use /t:exe to create a Win-
Forms application, but the console will be visible as background window.
Option Description
/delaysign Builds an assembly using delayed signing of the strong name. This
is discussed in Chapter 15.
/keyfile Specifies the path to the .snk file containing the key pair used for
strong signing (see Chapter 15).
/out Name of the file containing compiled output. The default is the
name of the input file with .exe suffix.
1.5 Understanding the C# Compiler 33
Option Description
/resource Used to embed resource files into the assembly that is created.
The real value of working with the raw compiler is the ability to work with multi-
ple files and assemblies. For demonstration purposes, create two simple C# source
files: client.cs and clientlib.cs.
client.cs
using System;
public class MyApp
{
static void Main(string[] args)
{
ShowName.ShowMe("Core C#");
}
}
clientlib.cs
using System;
public class ShowName
{
public static void ShowMe(string MyName)
{
Console.WriteLine(MyName);
}
}
It’s not important to understand the code details, only that the client routine
calls a function in clientlib that writes a message to the console. Using the C#
compiler, we can implement this relationship in a number of ways that not only dem-
onstrate compiler options but also shed light on the use of assemblies.
34 Chapter 1 ■ Introduction to .NET and C#
The output is an assembly named clientlib.dll. Now, compile the client code
and reference this external assembly:
The output is an assembly named client.exe. If you examine this with Ildasm,
you see that the manifest contains a reference to the clientlib assembly.
These examples, shown in Figure 1-15, illustrate the fact that even a simple appli-
cation presents the developer with multiple architectural choices for implementing
an application.
4. The PE format defines the layout for executable files that run on 32- or 64-bit Windows systems.
1.6 Summary 35
Example 1: Example 2:
Multiple Source Reference External Example 3:
Files Assembly Multi-File Assembly
IL
IL IL IL IL
clientlib.
client.exe client.exe clientlib.dll client.exe netmodule
1.6 Summary
The .NET Framework consists of the Common Language Runtime (CLR) and the
Framework Class Library (FCL). The CLR manages all the tasks associated with
code execution. It first ensures that code is CLR compliant based on the Common
Language Specification (CLS) standard. It then loads an application and locates all
dependent assemblies. Its Just-in-Time (JIT) compiler converts the IL contained in
an application’s assembly, the smallest deployable code unit in .NET, into native
machine code. During the actual program execution, the CLR handles security, man-
ages threads, allocates memory, and performs garbage collection for releasing
unused memory.
All code must be packaged in an assembly in order for the CLR to use it. An
assembly is either a single file or grouping of multiple physical files treated as a single
unit. It may contain code modules as well as resource files.
The FCL provides a reusable set of classes and other types that are available to all
CLR-compliant code. This eliminates the need for compiler-specific libraries.
Although the FCL consists of several physical DLLs containing over a thousand
types, it’s made manageable by the use of namespaces that impose a logical hierarchy
over all the types.
To assist the developer in debugging and deploying software, .NET includes a set
of utilities that enables an administrator to perform such tasks as managing assem-
blies, precompiling assemblies, adding files to an assembly, and viewing class details.
In addition, a wealth of open source .NET tools is becoming available to aid the
development process.
36 Chapter 1 ■ Introduction to .NET and C#
3. What is the difference between the Common Type System and the
Common Language Specification?
4. How does the CLR allow code from different compilers to interact?
8. Describe what these commonly used acronyms stand for: CLR, GAC,
FCL, IL.
This page intentionally left blank
C# LANGUAGE
FUNDAMENTALS
39
40 Chapter 2 ■ C# Language Fundamentals
The code in Figure 2-1 consists of a class MyApp that contains the program logic
and a class Apparel that contains the data. The program creates an instance of
Apparel and assigns it to myApparel. This object is then used to print the values of
the class members FabType and Price to the console. The important features to
note include the following:
C:\> MyApparel 5 6
Core Note
The contents of the command line are passed as an argument to the
Main() method. The System.Environment.CommandLine property
also exposes the command line’s contents.
42 Chapter 2 ■ C# Language Fundamentals
Naming Conventions
The ECMA standard provides naming convention guidelines to be followed in your
C# code. In addition to promoting consistency, following a strict naming policy can
minimize errors related to case sensitivity that often result from undisciplined nam-
ing schemes. Table 2-1 summarizes some of the more important recommendations.
Note that the case of a name may be based on two capitalization schemes:
Enum Type Pascal • Use Pascal case for the enum value names.
• Use singular name for enums.
public enum WarmColor { Orange, Yellow, Brown}
Event Pascal • The method that handles events should have the suffix
EventHandler.
• Event argument classes should have the suffix EventArgs.
Local Variable Camel • Variables with public access modifier use Pascal.
int myIndex
2.1 The Layout of a C# Program 43
Namespace Pascal • Do not have a namespace and class with the same name.
• Use prefixes to avoid namespaces having the same name.
For example, use a company name to categorize
namespaces developed by that company.
Acme.GraphicsLib
The rule of thumb is to use Pascal capitalization everywhere except with parame-
ters and local variables.
Commenting a C# Program
The C# compiler supports three types of embedded comments: an XML version and
the two single-line (//) and multi-line (/* */) comments familiar to most program-
mers:
An XML comment begins with three slashes (///) and usually contains XML tags
that document a particular aspect of the code such as a structure, a class, or class
member. The C# parser can expand the XML tags to provide additional information
and export them to an external file for further processing.
The <remarks> tag—shown in Figure 2-1—is used to describe a type (class). The
C# compiler recognizes eight other primary tags that are associated with a particular
program element (see Table 2-2). These tags are placed directly above the lines of
code they refer to.
44 Chapter 2 ■ C# Language Fundamentals
Tag Description
<include file="myXML"> file attribute is set to name of another XML file that is
to be included in the XML documentation produced by
this source code.
<permission cref= ""> Most of the time this is set to the following:
///<permission cref="System.Security.Permis-
sionSet"> </permission>
The value of the XML comments lies in the fact that they can be exported to a
separate XML file and then processed using standard XML parsing techniques. You
must instruct the compiler to generate this file because it is not done by default.
The following line compiles the source code consoleapp.cs and creates an
XML file consoleXML:
If you compile the code in Figure 2-1, you’ll find that the compiler generates
warnings for all public members in your code:
Warning CS1591: Missing XML comment for publicly visible type ...
2.2 Primitives 45
Core Note
2.2 Primitives
The next three sections of this chapter describe features that you’ll find in most pro-
gramming languages: variables and data types, operators, expressions, and statements
that control the flow of operations. The discussion begins with primitives. As the
name implies, these are the core C# data types used as building blocks for more com-
plex class and structure types. Variables of this type contain a single value and always
have the same predefined size. Table 2-3 provides a formal list of primitives, their
corresponding core data types, and their sizes.
As the table shows, primitives map directly to types in the base class library and
can be used interchangeably. Consider these statements:
They all generate exactly the same Intermediate Language (IL) code. The shorter
version relies on C# providing the keyword int as an alias for the System.Int32
type. C# performs aliasing for all primitives.
Here are a few points to keep in mind when working with primitives:
• The keywords that identify the value type primitives (such as int) are
actually aliases for an underlying structure (struct type in C#).
Special members of these structures can be used to manipulate the
primitives. For example, the Int32 structure has a field that returns
the largest 32-bit integer and a method that converts a numeric string
to an integer value:
The remainder of this section offers an overview of the most useful primitives with
the exception of string, which is discussed later in the chapter.
decimal
The decimal type is a 128-bit high-precision floating-point number. It provides 28
decimal digits of precision and is used in financial calculations where rounding can-
not be tolerated. This example illustrates three of the many methods available to
decimal type. Also observe that when assigning a literal value to a decimal type,
the M suffix must be used.
bool
The only possible values of a bool type are true and false. It is not possible to cast
a bool value to an integer—for example, convert true to a 1, or to cast a 1 or 0 to a
bool.
bool bt = true;
string bStr = bt.ToString(); // returns "true"
bt = (bool) 1; // fails
48 Chapter 2 ■ C# Language Fundamentals
char
The char type represents a 16-bit Unicode character and is implemented as an
unsigned integer. A char type accepts a variety of assignments: a character value
placed between individual quote marks (' '); a casted numeric value; or an escape
sequence. As the example illustrates, char also has a number of useful methods pro-
vided by the System.Char structure:
byte, sbyte
A byte is an 8-bit unsigned integer with a value from 0 to 255. An sbyte is an 8-bit
signed integer with a value from –128 to 127.
single, double
These are represented in 32-bit single-precision and 64-bit double-precision formats.
In .NET 1.x, single is referred to as float.
• The single type has a value range of 1.5 × 10 –45 to 3.4 × 1038 with
7-decimal digit precision.
• The double type has a value range of 5 × 10–324 to 1.7 × 10308 with
15- to 16-decimal digit precision.
• Floating-point operations return NaN (Not a Number) to signal that
the result of the operation is undefined. For example, dividing 0.0 by
0.0 results in NaN.
• Use the System.Convert method when converting floating-point
numbers to another type.
Note that the F suffix is used when assigning a literal value to a single type, and
D is optional for a double type.
avoid formal exception handling code. The following example uses an Int32 type to
demonstrate the two forms of TryParse:
int result;
// parse string and place result in result parameter
bool ok = Int32.TryParse("100", out result);
bool ok = Int32.TryParse("100", NumberStyles.Integer, null,
out result);
In the second form of this method, the first parameter is the text string being
parsed, and the second parameter is a NumberStyles enumeration that describes
what the input string may contain. The value is returned in the fourth parameter.
Arithmetic Operators
Table 2-4 summarizes the basic numerical operators. The precedence in which these
operators are applied during the evaluation of an expression is shown in parentheses,
with 1 being the highest precedence.
++ (1) Prefix/postfix x = 5;
-- Increment/decrement Console.WriteLine(x++) // x = 5
Console.WriteLine(++x) // x = 6
Core Note
== Equality if (x == y) {...}
!= Inequality
Note the two forms of the logical AND/OR operations. The && and || operators
do not evaluate the second expression if the first is false—a technique known as short
circuit evaluation. The & and | operators always evaluate both expressions. They are
used primarily when the expression values are returned from a method and you want
to ensure that the methods are called.
In addition to the operators in Table 2-5, C# supports a ?: operator for condition-
ally assigning a value to a variable. As this example shows, it is basically shorthand for
using an if-else statement:
string pass;
int grade=74;
If(grade >= 70) pass="pass"; else pass="fail";
// expression ? op1 : op2
pass = (grade >= 70) ? "pass" : "fail";
If the expression is true, the ?: operator returns the first value; if it’s false, the
second is returned.
if-else
Syntax:
C# if statements behave as they do in other languages. The only issue you may
encounter is how to format the statements when nesting multiple if-else clauses.
// Nested if statements
if (age > 16) if (age > 16)
{ if (sex == "M")
if (sex == "M") type = "Man";
{ else
type = "Man"; type = "Woman" ;
} else { else
type = "Woman" ; type = "child";
}
} else {
type = "child";
}
54 Chapter 2 ■ C# Language Fundamentals
Both code segments are equivalent. The right-hand form takes advantage of the
fact that curly braces are not required to surround single statements; and the subor-
dinate if clause is regarded as a single statement, despite the fact that it takes several
lines. The actual coding style selected is not as important as agreeing on a single style
to be used.
switch
Syntax:
The expression is one of the int types, a character, or a string. The switch block
consists of case labels—and an optional default label—associated with a constant
expression that must implicitly convert to the same type as the expression. Here is an
example using a string expression:
• C# does not permit execution to fall through one case block to the
next. Each case block must end with a statement that transfers
control. This will be a break, goto. or return statement.
• Multiple case labels may be associated with a single block of code.
• The switch statement is case sensitive; in the example, "Cotton"
and "COTTON" represent two different values.
2.4 Loops
C# provides four iteration statements: while, do, for, and foreach. The first three
are the same constructs you find in C, C++, and Java; the foreach statement is
designed to loop through collections of data such as arrays.
while loop
Syntax:
The statement(s) in the loop body are executed until the boolean expression is
false. The loop does not execute if the expression is initially false.
Example:
do loop
Syntax:
This is similar to the while statement except that the evaluation is performed at
the end of the iteration. Consequently, this loop executes at least once.
Example:
for loop
Syntax:
The for construct contains initialization, a termination condition, and the itera-
tion statement to be used in the loop. All are optional. The initialization is executed
once, and then the condition is checked; as long as it is true, the iteration update
occurs after the body is executed. The iteration statement is usually a simple incre-
ment to the control variable, but may be any operation.
Example:
If any of the clauses in the for statement are left out, they must be accounted for
elsewhere in the code. This example illustrates how omission of the for-iteration
clause is handled:
foreach loop
Syntax:
The type and identifier declare the iteration variable. This construct loops once
for each element in the collection and sets the iteration variable to the value of the
current collection element. The iteration variable is read-only, and a compile error
occurs if the program attempts to set its value.
For demonstration purposes, we will use an array as the collection. Keep in mind,
however, that it is not restricted to an array. There is a useful set of collection classes
defined in .NET that work equally well with foreach. We look at those in Chapter 4,
“Working with Objects in C#.”
Example:
int totVal = 0;
foreach (int arrayVal in r)
{
totVal += arrayVal;
}
There are few occasions where the use of a goto statement improves program
logic. The goto default version may be useful in eliminating redundant code inside
a switch block, but aside from that, avoid its use.
C# Preprocessing
Symbol Description
#line Changes the line number sequence and can identify which file is
the source for the line.
#region Used to specify a block of code that you can expand or collapse
#endregion when using the outlining feature of Visual Studio.NET.
The three most common uses for preprocessing directives are to perform condi-
tional compilation, add diagnostics to report errors and warnings, and define code
regions.
60 Chapter 2 ■ C# Language Fundamentals
Conditional Compilation
The #if related directives are used to selectively determine which code is included
during compilation. Any code placed between the #if statement and #endif state-
ment is included or excluded based on whether the #if condition is true or false.
This is a powerful feature that is used most often for debug purposes. Here is an
example that illustrates the concept:
#define DEBUG
using System;
public class MyApp
{
public static void Main()
{
#if (DEBUG)
Console.WriteLine("Debug Mode");
#else
Console.WriteLine("Release Mode");
#endif
}
}
Any #define directives must be placed at the beginning of the .cs file. A condi-
tional compilation symbol has two states: defined or undefined. In this example, the
DEBUG symbol is defined and the subsequent #if (DEBUG) statement evaluates to
true. The explicit use of the #define directive permits you to control the debug
state of each source file. Note that if you are using Visual Studio, you can specify a
Debug build that results in the DEBUG symbol being automatically defined for each
file in the project. No explicit #define directive is required.
You can also define a symbol on the C# compile command line using the /Define
switch:
Diagnostic Directives
Diagnostic directives issue warning and error messages that are treated just like any
other compile-time errors and warnings. The #warning directive allows compilation
to continue, whereas the #error terminates it.
2.6 Strings 61
#define CLIENT
#define DEBUG
using System;
public class MyApp
{
public static void Main()
{
#if DEBUG && INHOUSE
#warning Debug is on.
#elif DEBUG && CLIENT
#error Debug not allowed in Client Code.
#endif
// Rest of program follows here
In this example, compilation will terminate with an error message since DEBUG
and CLIENT are defined.
Code Regions
The region directives are used to mark sections of code as regions. The region direc-
tive has no semantic meaning to the C# compiler, but is recognized by Visual Stu-
dio.NET, which uses it to hide or collapse code regions. Expect other third-party
source management tools to take advantage of these directives.
#region
// any C# statements
#endregion
2.6 Strings
The System.String, or string class, is a reference type that is represented inter-
nally by a sequence of 16-bit Unicode characters. Unlike other reference types, C#
treats a string as a primitive type: It can be declared as a constant, and it can be
assigned a literal string value.
String Literals
Literal values assigned to string variables take two forms: literals enclosed in quota-
tion marks, and verbatim strings that begin with @" and end with a closing double
quote ("). The difference between the two is how they handle escape characters.
Regular literals respond to the meaning of escape characters, whereas verbatim
62 Chapter 2 ■ C# Language Fundamentals
strings treat them as regular text. Table 2-9 provides a summary of the escape charac-
ters that can be placed in strings.
\a System alert
\b Backspace
\f Form feed
\r Carriage return
\t Horizontal tab
\u Unicode character
\v Vertical tab
\0 Null character
A verbatim string serves the purpose its name implies: to include any character
placed between the beginning and ending double quote. The following segment pro-
vides several examples of using literals:
The regular literal string is normally your best choice because it supports the
escape sequences. The verbatim is to be favored when the text contains backslashes.
Its most common use is with file path values and Regular Expression matching pat-
terns (discussed in Chapter 5, “C# Text Manipulation and File I/O”).
String Manipulation
The System.String class contains a variety of string manipulation members. These
include ways to determine a string’s length, extract a substring, compare strings, and
convert a string to upper- or lowercase. The following examples illustrate some of the
more common operations.
String Concatenation
The + operator is used for concatenating two strings: s1 + s2 . Only one of these has
to be a string type; the other can be any type, and its ToString method is called
automatically to convert it.
Each loop results in the creation of a new string consisting of the previous string
plus the new appended name and tag. A better approach is to use the String-
Builder class as a replacement for the concatenation operator. This class sets aside
memory to operate on strings and thus avoids the copying and memory allocation
drawbacks of the concatenation (+) operator. It includes methods to append, insert,
delete, remove, and replace characters. StringBuilder is discussed in Chapter 5.
The IndexOf method locates the next occurrence of a character pattern within a
string. It searches for the occurrence from the beginning of the string or a specified
location. Listing 2-1 illustrates this.
IndexOf() performs a case-sensitive search. To ensure both upper- and lower-
case instances are counted, you could convert the original string to lowercase
(ToLower()) before searching it. Note that there is also a LastIndexOf method
that locates the last instance of a character pattern within a string.
2.6 Strings 65
Comparing Strings
This topic is more complex than one would expect. The first hint of this is when you
look at the System.String members and discover that there are four comparison
methods: Compare, CompareOrdinal, CompareTo, and Equals. The choice of a
comparison method is based on factors such as whether the comparison should be
case sensitive and whether it should take culture into account.
The .NET environment is designed to handle international character sets, cur-
rencies, and dates. To support this, the handling and representation of strings can be
tailored to different countries and cultures. Consider, for example, how to compare
the same date in U.S. and European format. The dates “12/19/04” and “19/12/04” are
logically equal, but do not have the same code value. Only a comparison method that
takes culture into consideration would consider them equal. Chapter 5 explains how
the various comparison methods work and the factors to be considered in selecting
one.
For the majority of applications, nothing more than the standard equality (==)
operator is required. This code segment illustrates its use:
bool isMatch;
string title = "Ancient Mariner";
isMatch = (title == "ANCIENT MARINER"); // false
66 Chapter 2 ■ C# Language Fundamentals
Note that the == operator is just a syntactical shortcut for calling the Equals
method; it is actually faster to call Equals()directly.
Syntax:
Example:
Note: If the enum symbols are not set to a value, they are set automatically to the
sequence 0, 1, 2, 3, and so on.
The access modifiers define the scope of the enum. The default is internal,
which permits it to be accessed by any class in its assembly. Use public to make it
available to any class in any assembly.
The optional enum-base defines the underlying type of the constants that corre-
spond to the symbolic names. This must be an integral value of the type byte,
sbyte, short, ushort, int, uint, long, or ulong. The default is int.
remain valid. Another advantage is that enumerated types are strongly typed. This
means, for example, that when an enum type is passed as a parameter, the receiving
method must have a matching parameter of the same type; otherwise, a compiler
error occurs.
The code segment in Listing 2-2 illustrates these ideas using the Fabric enum
from the preceding example.
Things to note:
This example shows how easy it is to obtain the symbol name or constant value
when the instance of an enum is known—that is, Cotton. But suppose there is a
need to determine whether an enum contains a member with a specific symbol or
constant value. You could use foreach to loop through the enum members, but
there is a better solution. Enumerations implicitly inherit from System.Enum, and
this class contains a set of methods that can be used to query an enumeration about
its contents.
System.Enum Methods
Three of the more useful System.Enum methods are Enum.IsDefined,
Enum.Parse, and Enum.GetName. The first two methods are often used together to
determine if a value or symbol is a member of an enum, and then to create an
instance of it. The easiest way to understand them is to see them in use. In this exam-
ple, the enum Fabric is queried to determine if it contains a symbol matching a
given string value. If so, an instance of the enum is created and the GetName method
is used to print one of its values.
The IsDefined method takes two parameters: an enumeration type that the
typeof operator returns and a string representing the symbol to be tested for.
Another form of this method tests for a specified constant value if a numeric value is
passed as the second parameter.
The Parse method takes the same arguments and creates an instance of an enu-
merated type. The variable fab created here is equivalent to the one created in List-
ing 2-2. It is important to ensure that the enum member exists before using the
Parse method. If it does not, an exception is thrown.
The GetName method returns a string value of the enum whose value is passed as
the second argument to the method. In this example, "Silk" is returned because its
constant value is 2.
2.8 Arrays 69
[Flags]
enum Fabric :short {
2.8 Arrays
C#, like most programming languages, provides the array data structure as a way to
collect and manipulate values of the same type. Unlike other languages, C# provides
three types of arrays. One is implemented as an ArrayList object; another as a
generic List object; and a third is derived from the System.Array class. The latter,
which is discussed here, has the traditional characteristics most programmers associ-
ate with an array. The ArrayList is a more flexible object that includes special
methods to insert and delete elements as well as dynamically resize itself. The List
is a type-safe version of the ArrayList that was introduced with .NET 2.0 and may
eventually replace the ArrayList. The List and ArrayList are discussed in
Chapter 4, along with other Collection classes.
Before looking at the details of creating and using an array, you should be familiar
with its general features:
Example:
// Set to an enum
Fabric[] enumArray = new Fabric[2];
enumArray[0] = Fabric.Cotton;
The size of the array is determined from the explicit dimensions or the number of
elements in the optional initializer list. If a dimension and an initializer list are both
included, the number of elements in the list must match the dimension(s). If no ini-
tialization values are specified, the array elements are initialized to 0 for numeric
types or null for all others. The CLR enforces bounds checking—any attempt to
reference an array index outside its dimensions results in an exception.
2.8 Arrays 71
IndexOf, Static Returns the index of the first or last occurrence of a value in a
LastIndexOf method one-dimensional array.
Clone Instance Copies the contents of an array into a new array. The new array
method is a shallow copy of the original—that is, reference pointers, not
values, are copied.
The members are classified as static or instance. A static member is not associated
with any particular array. It operates as a built-in function that takes any array as a
parameter. The instance members, on the other hand, are associated with a specific
instance of an array. The example shown in Listing 2-3 demonstrates how to use
many of these class members.
Things to note:
• The Sort method has many overloaded forms. The simplest takes a
single-dimensional array as a parameter and sorts it in place. Other
forms permit arrays to be sorted using an interface defined by the
programmer. This topic is examined in Chapter 4.
• The Clone method creates a copy of the artists array and assigns it
to artClone. The cast (string[]) is required, because Clone
returns an Object type. The Object.ReferenceEquals method is
used to determine if the cloned array points to the same address as the
original. Because string is a reference type, the clone merely copies
pointers to the original array contents, rather than copying the
contents. If the arrays had been value types, the actual contents would
have been copied, and ReferenceEquals would have returned false.
• The Copy method copies a range of elements from one array to
another and performs any casting as required. In this example, it takes
the following parameters:
System.Object
System.ValueType
System.Array
Struct
System.String
System.Enum
System.Exception
Primitives
Class, Interface Boolean Int16
Byte Int32
Char Int64
User-defined
System-defined Decimal Single
Double
appStatus 0
myApparel •
myApparel2 •
fabType • "Synthetic"
price 250
Overhead
1. The CLR allocates memory for the object on the top of the managed
heap.
2. Overhead information for the object is added to the heap. This infor-
mation consists of a pointer to the object’s method table and a
SyncBlockIndex that is used to synchronize access to the object
among multiple threads.
3. The myApparel object is created as an instance of the Apparel class,
and its Price and FabType fields are placed on the heap.
4. The reference to myApparel is placed on the stack.
5. When a new reference variable myApparel2 is created, it is placed on
the stack and given a pointer to the existing object. Both reference
variables—myApparel and myApparel2—now point to the same
object.
Creating a reference object can be expensive in time and resources because of the
multiple steps and required overhead. However, setting additional references to an
existing object is quite efficient, because there is no need to make a physical copy of
the object. The reverse is true for value types.
Boxing
.NET contains a special object type that accepts values of any data type. It provides
a generic way to pass parameters and assign values when the type of the value being
passed or assigned is not tied to a specific data type. Anything assigned to object
must be treated as a reference type and stored on the heap. Consider the following
statements:
The first statement creates the variable age and places its value on the stack; the
second assigns the value of age to a reference type. It places the value 17 on the
heap, adds the overhead pointers described earlier, and adds a stack reference to it.
This process of wrapping a value type so that it is treated as a reference type is known
as boxing. Conversely, converting a reference type to a value type is known as unbox-
ing and is performed by casting an object to its original type. Here, we unbox the
object created in the preceding example:
Note that the value being unboxed must be of the same type as the variable to
which it is being cast.
In general, boxing can be ignored because the CLR handles the details transpar-
ently. However, it should be considered when designing code that stores large
amounts of numeric data in memory. To illustrate, consider the System.Array and
ArrayList classes mentioned earlier. Both are reference types, but they perform
quite differently when used to store simple data values.
The ArrayList methods are designed to work on the generic object type. Con-
sequently, the ArrayList stores all its items as reference types. If the data to be
stored is a value type, it must be boxed before it can be stored. The array, on the
other hand, can hold both value and reference types. It treats the reference types as
the ArrayList does, but does not box value types.
The following code creates an array and an ArrayList of integer values. As
shown in Figure 2-4, the values are stored quite differently in memory.
The array stores the values as unboxed int values; the ArrayList boxes each
value. It then adds overhead required by reference types. If your application stores
large amounts of data in memory and does not require the special features of the
ArrayList, the array is a more efficient implementation. If using .NET 2.0 or later,
the List class is the best choice because it eliminates boxing and includes the more
flexible ArrayList features.
2.9 Reference and Value Types 77
int = 4
int = 3
int = 2
int = 1
int = 4 •
int = 3 •
int = 2 •
int = 1 •
ages Overhead Overhead ages
Array ArrayList
Figure 2-4 Memory layout comparison of Array and ArrayList
Releasing Memory
Memory on the stack is freed when a variable goes out of scope. A garbage collection
process that occurs when a system memory threshold is reached releases memory on
the heap. Garbage collection is controlled by .NET and occurs automatically at
unpredictable intervals. Chapter 4 discusses it in detail.
Variable Assignments
When a variable is set to a reference type, it receives a pointer to the original
object—rather than the object value itself. When a variable is set to a value type, a
field-by-field copy of the original variable is made and assigned to the new variable.
78 Chapter 2 ■ C# Language Fundamentals
2.10 Summary
This chapter offers an overview of the C# language, providing the syntax and exam-
ples for using the list of features that form the core of this and just about any pro-
gramming language. These features include basic data types, numerical and
relational operators, loop constructs, strings, enums, and arrays. The final section
stresses how all .NET types can be classified as a value or reference type. It explains
the different memory allocation schemes used for the two. In addition, it looks at the
concepts of boxing and unboxing: converting a value type to a reference type and
converting a reference type back to a value type.
2. What are the three types of inline comments available in C#? Which
can be exported?
3. What is a primitive?
a. c = c+ i;
b. s += i;
c. c += s;
d. d += i;
9. Name the two base classes from which all value types inherit.
10. What prime value is printed by the final statement in this code?
int[] primes = new int[6] {1,3,5,7,11,13};
int[] primesClone = new int[6];
Array.Copy(primes,1,primesClone,1,5);
Console.WriteLine("Prime: "+primesClone[3]);
CLASS DESIGN
IN C#
This chapter provides an advanced introduction to using classes within the .NET
environment. It is not a primer on object-oriented programming (OOP) and
assumes you have some familiarity with the principles of encapsulation, inheritance,
and polymorphism. C# is rich in object-oriented features, and the first challenge in
working with classes is to understand the variety of syntactical contstructs. At the
same time, it is necessary to appreciate the interaction between C# and .NET. Not
only does the Framework Class Library (FCL) provide thousands of predefined
classes, but it also provides a hierarchy of base classes from which all C# classes are
derived.
The chapter presents topics in a progressive fashion—each section building on the
previous. If you are new to C#, you should read from beginning to end; if you’re
familiar with the concepts and syntax, read sections selectively. The chapter begins
by presenting the syntactical construct of a class and then breaks it down in detail.
Attributes, modifiers, and members of the class body (constructors, properties,
fields, and methods) are all explained. Sprinkled throughout are recommended
.NET guidelines and best practices for designing and using custom classes. The
objective is to show not only how to use classes, but also encourage good design prac-
tices that result in efficient code.
81
82 Chapter 3 ■ Class Design in C#
Attribute
[assembly:CLSCompliant(true)]
Class public class Furniture
Declaration {
Constant const double salesTax = .065;
private double purchPrice;
Fields private string vendor, inventoryID;
public Furniture (string vendor, string invenID,
double purchPrice)
{
Constructor this.vendor = vendor;
this.inventoryID = invenID;
this.purchPrice = purchPrice;
}
public string MyVendor
Property { get {return vedor;} }
public double CalcSalesTax(double salePrice)
Method
{ return salePrice * salesTax; }
}
see in the discussion of inheritance, this is important because a class can explicitly
inherit from only one class.
Attributes
The optional attribute section consists of a pair of square brackets surrounding a
comma-separated list of one or more attributes. An attribute consists of the attribute
name followed by an optional list of positional or named arguments. The attribute
may also contain an attribute target—that is, the entity to which the attribute applies.
Examples
The attribute section contains an attribute name only:
[ClassDesc]
[ClassDesc(Author="Knuth", 0)]
[ClassDesc(Author="Knuth"), ClassDesc(Author="James")]
Description
Attributes provide a way to associate additional information with a target entity. In
our discussion, the target is a newly created class; but attributes may also be associ-
ated with methods, fields, properties, parameters, structures, assemblies, and mod-
ules. Their simple definition belies a truly innovative and powerful programming
tool. Consider the following:
Core Note
.NET supports two types of attributes: custom attributes and standard attributes.
Custom attributes are defined by the programmer. The compiler adds them to the
metadata, but it’s up to the programmer to write the reflection code that incorporates
this metadata into the program. Standard attributes are part of the .NET Framework
and recognized by the runtime and .NET compilers. The Flags attribute that was
discussed in conjunction with enums in Chapter 2, “C# Language Fundamentals,” is
an example of this; another is the conditional attribute, described next.
Conditional Attribute
The conditional attribute is attached to methods only. Its purpose is to indicate
whether the compiler should generate Intermediate Language (IL) code to call the
method. The compiler makes this determination by evaluating the symbol that is part
of the attribute. If the symbol is defined (using the define preprocessor directive),
code that contains calls to the method is included in the IL. Here is an example to
demonstrate this:
#define DEBUG
using System;
using System.Diagnostics; // Required for conditional attrib.
public class AttributeTest
{
[Conditional("TRACE")]
public static void ListTrace()
{ Console.WriteLine("Trace is On"); }
[Conditional("DEBUG")]
public static void ListDebug()
{ Console.WriteLine("Debug is On"); }
}
3.2 Defining a Class 85
#define TRACE
using System;
public class MyApp {
static void Main()
{
Console.WriteLine("Testing Method Calls");
AttributeTest.ListTrace();
AttributeTest.ListDebug();
}
}
Access Modifiers
The primary role of modifiers is to designate the accessibility (also called scope or
visibility) of types and type members. Specifically, a class access modifier indicates
whether a class is accessible from other assemblies, the same assembly, a containing
class, or classes derived from a containing class.
Core Note
Class Identifier
This is the name assigned to the class. The ECMA standard recommends the follow-
ing guidelines for naming the identifier:
Example
// .. FCL Interface and user-defined base class
public interface System.Icomparable
{Int32 CompareTo(Object object); }
class Furniture { }
// .. Derived Classes
class Sofa: Furniture { ... } // Inherits from one base class
// Following inherits from one base class and one interface.
class Recliner: Furniture, IComparable {...}
The C# language does not permit multiple class inheritance, thus the base list can
contain only one class. Because there is no limit on the number of inherited inter-
faces, this serves to increase the role of interfaces in the .NET world.
Core Note
Method Class, Structure, A function associated with the class that defines
Interface an action or computation.
Events Class, Structure, A way for a class or object to notify other classes
Interface or objects that its state has changed.
Access Modifiers
* Not applicable
Constants
C# uses the const keyword to declare variables that have a fixed, unalterable value.
Listing 3-1 provides an example of using constants. Although simple, the code illus-
trates several basic rules for defining and accessing constants.
90 Chapter 3 ■ Class Design in C#
The most important thing to recognize about a constant is that its value is deter-
mined at compile time. This can have important ramifications. For example, suppose
the Furniture class in Figure 3-1 is contained in a DLL that is used by other assem-
blies as a source for the sales tax rate. If the rate changes, it would seem logical that
assigning the new value to SalesTax and recompiling the DLL would then make
the new value to external assemblies. However, the way .NET handles constants
requires that all assemblies accessing the DLL must also be recompiled. The prob-
lem is that const types are evaluated at compile time.
3.4 Constants, Fields, and Properties 91
When any calling routine is compiled against the DLL, the compiler locates all
constant values in the DLL’s metadata and hardcodes them in the executable code of
the calling routine. This value is not changed until the calling routine is recompiled
and reloads the value from the DLL. In cases such as tax, where a value is subject to
change, it’s preferable to define the value as a readonly field—sometimes referred
to as a runtime constant.
Core Approach
Fields
A field is also used to store data within a class. It differs from a const in two sig-
nificant ways: Its value is determined at runtime, and its type is not restricted to
primitives.
Field Modifiers
In addition to the access modifiers, fields have two additional modifiers: static and
readonly (see Table 3-3).
Modifier Definition
static The field is part of the class’s state rather than any instances of the class.
This means that it can be referenced directly (like a constant) by specifying
classname.fieldname without creating an instance of the class.
readonly The field can only be assigned a value in the declaration statement or class
constructor. The net effect is to turn the field into a constant. An error
results if code later attempts to change the value of the field.
As a rule of thumb, fields should be defined with the private attribute to ensure
that the state of an object is safe from outside manipulation. Methods and properties
should then be used to retrieve and set the private data if outside access is required.
92 Chapter 3 ■ Class Design in C#
Core Note
If a field is not initialized, it is set to the default value for its type: 0 for
numbers, null for a reference type, single quotation marks ('') for a
string, and false for boolean.
There is one case where setting a field to public makes sense: when your pro-
gram requires a global constant value. By declaring a field to be public static
readonly, you can create a runtime constant. For example, this declaration in Fig-
ure 3-1:
Properties
A property is used to control read and write access to values within a class. Java and
C++ programmers create properties by writing an accessor method to retrieve field
data and a mutator method to set it. Unlike these languages, the C# compiler actually
recognizes a special property construct and provides a simplified syntax for creating
and accessing data. In truth, the syntax is not a whole lot different than a comparable
C++ implementation, but it does allow the compiler to generate more efficient code.
Syntax:
[attributes] <modifier> <data type> <property name>
94 Chapter 3 ■ Class Design in C#
{
[access modifier] get
{ ...
return(propertyvalue)
}
[access modifier] set
{ ... Code to set a field to the keyword value }
}
Note:
The syntax for accessing the property of a class instance is the same as for a field:
The get block of code serves as a traditional accessor method and the set block
as a mutator method. Only one is required. Leave out the get block to make the
property write-only or the set block to make it read-only.
All return statements in the body of a get block must specify an expression that
is implicitly convertible to the property type.
In this example, the code in the set block checks to ensure that the property is set
to a value greater than 0. This capability to check for invalid data is a major argument
in favor of encapsulating data in a property.
If you were to examine the underlying code generated for this example, you would
find that C# actually creates a method for each get or set block. These names are
created by adding the prefix get or set to the property name—for example,
get_PricePerYard. In the unlikely case you attempt to create a method with the
same name as the internal one, you will receive a compile-time error.
The use of properties is not necessarily any less efficient than exposing fields
directly. For a non-virtual property that contains only a small amount of code, the JIT
(Just-in-Time) compiler may replace calls to the accessor methods with the actual
code contained in the get or set block. This process, known as inlining, reduces the
overhead of making calls at runtime. The result is code that is as efficient as that for
fields, but much more flexible.
Indexers
An indexer is often referred to as a parameterized property. Like a property, it is
declared within a class, and its body may contain get and set accessors that share
the same syntax as property accessors. However, an indexer differs from a property in
two significant ways: It accepts one or more parameters, and the keyword this is
used as its name. Here is the formal syntax:
Syntax:
Example:
Note: The static modifier is not supported because indexers work only with
instances.
In a nutshell, the indexer provides a way to access a collection of values main-
tained within a single class instance. The parameters passed to the indexer are used
as a single- or multi-dimensional index to the collection. The example in Listing 3-4
should clarify the concept.
96 Chapter 3 ■ Class Design in C#
The Fabrics class contains an indexer that uses the get and set accessors to
control access to an internal array of Upholstery objects. A single instance of the
Fabrics class is created and assigned to sofaFabric. The indexer allows the inter-
nal array to be directly accessed by an index parameter passed to the object:
The advantage of using an indexer is that it hides the array handling details from
the client, and it can also perform any validation checking or data modification
before returning a value. Indexers are best used with objects whose properties can be
represented as a collection, rather than a scalar value. In this example, the various
fabrics available for sofas form a collection. We could also use the indexer to create a
collection of fabrics used for curtains:
3.5 Methods
Methods are to classes as verbs are to sentences. They perform the actions that
define the behavior of the class. A method is identified by its signature, which con-
sists of the method name and the number and data type of each parameter. A signa-
98 Chapter 3 ■ Class Design in C#
ture is considered unique as long as no other method has the same name and
matching parameter list. In addition to parameters, a method has a return type—
void if nothing is returned—and a modifier list that determines its accessibility and
polymorphic behavior.
For those who haven’t recently boned up on Greek or object-oriented principles,
polymorphism comes from the Greek poly (many) and morphos (shape). In program-
ming terms, it refers to classes that share the same methods but implement them dif-
ferently. Consider the ToString method implemented by all types. When used with
an int type, it displays a numeric value as text; yet on a class instance, it displays the
name of the underlying class—although this default should be overridden by a more
meaningful implementation.
One of the challenges in using .NET methods is to understand the role that
method modifiers play in defining the polymorphic behavior of an application. A
base class uses them to signal that a method may be overridden or that an inheriting
class must implement it. The inheriting class, in turn, uses modifiers to indicate
whether it is overriding or hiding an inherited method. Let’s look at how all this fits
together.
Method Modifiers
In addition to the access modifiers, methods have seven additional modifiers shown
in Table 3-4. Five of these—new, virtual, override, sealed, and abstract—
provide a means for supporting polymorphism.
Modifier Description
static The method is part of the class’s state rather than any instances of the class.
This means that it can be referenced directly by specifying class-
name.method (parameters) without creating an instance of the class.
virtual Designates that the method can be overridden in a subclass. This cannot be
used with static or private access modifiers.
override Specifies that the method overrides a method of the same name in a base
class. This enables the method to define behavior unique to the subclass.
The overriden method in the base class must be virtual.
Modifier Description
Static Modifier
As with other class members, the static modifier defines a member whose behav-
ior is global to the class and not specific to an instance of a class. The modifier is most
commonly used with constructors (described in the next section) and methods in
helper classes that can be used without instantiation.
In this example, the Conversions class contains methods that convert units from
the English to metric system. There is no real reason to create an instance of the
class, because the methods are invariant (the formulas never change) and can be con-
veniently accessed using the syntax classname.method(parameter).
Class Fiber
Class Natural
Class Cotton
Figure 3-2 Relationship between base class and subclasses for Listing 3-6
A subclass can inherit a virtual method without overriding it. If the Cotton class
does not override ShowMe(), it uses the method defined in its base class Natural. In
that case, the call to fib2.ShowMe() would return “Natural”.
class A {
public virtual void PrintID{….}
}
class B: A {
sealed override public void PrintID{…}
}
class C:B {
// This is illegal because it is sealed in B.
override public void PrintID{…}
}
3.5 Methods 103
Passing Parameters
By default, method parameters are passed by value, which means that a copy of the
parameter’s data—rather than the actual data—is passed to the method. Conse-
quently, any change the target method makes to these copies does not affect the orig-
inal parameters in the calling routine. If the parameter is a reference type, such as an
instance of a class, a reference to the object is passed. This enables a called method
to change or set a parameter value.
C# provides two modifiers that signify a parameter is being passed by reference:
out and ref. Both of these keywords cause the address of the parameter to be
passed to the target method. The one you use depends on whether the parameter is
initialized by the calling or called method. Use ref when the calling method initial-
izes the parameter value, and out when the called method assigns the initial value.
By requiring these keywords, C# improves code readability by forcing the program-
mer to explicitly identify whether the called method is to modify or initialize the
parameter.
The code in Listing 3-8 demonstrates the use of these modifiers.
104 Chapter 3 ■ Class Design in C#
In this example, the class MyApp is used to invoke three methods in the
TestParms class:
C# includes one other parameter modifier, params, which is used to pass a vari-
able number of arguments to a method. Basically, the compiler maps the variable
number of arguments in the method invocation into a single parameter in the target
method. To illustrate, let’s consider a method that calculates the average for a list of
numbers passed to it:
Except for the params modifier, the code for this method is no different than that
used to receive an array. Rather than sending an array, however, the invoking code
passes an actual list of arguments:
double avg;
avg = TestParms.GetAvg(12,15, 22, 5, 7 ,19);
avg = TestParms.GetAvg(100.50, 200, 300, 55,88,99,45);
When the compiler sees these method calls, it emits code that creates an array,
populates it with the arguments, and passes it to the method. The params modifier is
essentially a syntactical shortcut to avoid the explicit process of setting up and passing
an array.
106 Chapter 3 ■ Class Design in C#
Core Note
The params keyword can only be used with the last parameter to a
method.
3.6 Constructors
The Common Language Runtime (CLR) requires that every class have a construc-
tor—a special-purpose method that initializes a class or class instance the first time it
is referenced. There are three basic types of constructors: instance, private (a special
case of instance), and static.
Instance Constructor
Syntax:
The syntax is the same as that of the method except that it does not have a return
data type and adds an initializer option. There are a number of implementation
and behavioral differences:
.NET constructs an object by allocating memory, zeroing out the memory, and
calling the instance constructor. The constructor sets the state of the object. Any
fields in the class that are not explicitly initialized are set to zero or null, depending
on the associated member type.
If you try to compile this, you’ll get an error stating that “no overload for Apparel
takes 0 arguments”. Two factors conspire to cause this: first, because Apparel has an
explicit constructor, the compiler does not add a default parameterless constructor to
its class definition; and second, as part of compiling the constructor in the derived
class, the compiler includes a call to the base class’s default constructor—which in
this case does not exist. The solution is either to add a parameterless constructor to
Apparel or to include a call in the derived class to the explicit constructor. Let’s look
at how a constructor can explicitly call a constructor in a base class.
108 Chapter 3 ■ Class Design in C#
Using Initializers
The C# compiler provides the base initializer as a way for a constructor in an inher-
ited class to invoke a constructor in its base class. This can be useful when several
classes share common properties that are set by the base class constructor. Listing
3-9 demonstrates how a derived class, Shirt, uses a base initializer to call a construc-
tor in Apparel.
The compiler matches the signature of this initializer with the instance construc-
tor in the base class that has a matching signature. Thus, when an instance of Shirt
is created, it automatically calls the constructor in Apparel that has one parameter.
This call is made before any code in Shirt is executed.
A second version of the initializer, one that uses the keyword this rather than
base, also indicates which constructor is to be called when the class is instantiated.
However, this refers to a constructor within the class instance, rather than the base
class. This form of the initializer is useful for reducing the amount of compiler code
generated in a class having multiple constructors and several fields to be initialized.
For example, if you examine the generated IL code for the following class, you
would find that the fields fiberType and color are defined separately for each
constructor.
For more efficient code, perform the field initialization in a single constructor and
have the other constructors invoke it using the this initializer.
Private Constructor
Recall that the private modifier makes a class member inaccessible outside its
class. When applied to a class constructor, it prevents outside classes from creating
instances of that class. Although somewhat non-intuitive (what good is a class that
cannot be instantiated?), this turns out to be a surprisingly powerful feature.
Its most obvious use is with classes that provide functionality solely through static
methods and fields. A classic example is the System.Math class found in the Frame-
work Class Library.
It has two static fields, pi and the e (natural logarithmic base), as well as several
methods that return trigonometric values. The methods behave as built-in functions,
and there is no reason for a program to create an instance of the math class in order
to use them.
In the earlier discussion of static methods, we presented a class (refer to Listing
3-5) that performs metric conversions. Listing 3-10 shows this class with the pri-
vate constructor added.
Although a simple example, this illustrates a class that does not require instantia-
tion: The methods are static, and there is no state information that would be associ-
ated with an instance of the class.
A natural question that arises is whether it is better to use the private construc-
tor or an abstract class to prevent instantiation. The answer lies in understanding
the differences between the two. First, consider inheritance. Although an abstract
class cannot be instantiated, its true purpose is to serve as a base for derived classes
(that can be instantiated) to create their own implementation. A class employing a
private constructor is not meant to be inherited, nor can it be. Secondly, recall that
a private constructor only prevents outside classes from instantiating; it does not
prevent an instance of the class from being created within the class itself.
The traits of the private constructor can also be applied to managing object cre-
ation. Although the private constructor prevents an outside method from instanti-
ating its class, it does allow a public method in the class (sometimes called a factory
method) to create an object. This means that a class can create instances of itself,
control how the outside world accesses them, and control the number of instances
created. This topic is discussed in Chapter 4, “Working with Objects in C#.”
Static Constructor
Also known as a class or type constructor, the static constructor is executed after
the type is loaded and before any one of the type members is accessed. Its primary
purpose is to initialize static class members. This limited role results from the many
restrictions that distinguish it from the instance constructor:
Although it does not have a parameter, do not confuse it with a default base con-
structor, which must be an instance constructor. The following code illustrates the
interplay between the static and instance constructors.
class BaseClass
{
private static int callCounter;
// Static constructor
static BaseClass(){
Console.WriteLine("Static Constructor: "+callCounter);
}
// Instance constructors
public BaseClass()
112 Chapter 3 ■ Class Design in C#
{
callCounter+= 1;
Console.WriteLine("Instance Constructor: "+callCounter);
}
// ... Other class operations
}
Output:
Static Constructor: 0
Instance Constructor: 1
Instance Constructor: 2
Instance Constructor: 3
The compiler first emits code to initialize the static field to 0; it then executes the
static constructor code that displays the initial value of callCounter. Next, the base
constructor is executed. It increments the counter and displays its current value,
which is now 1. Each time a new instance of BaseClass is created, the counter is
incremented. Note that the static constructor is executed only once, no matter how
many instances of the class are created.
object1
OnClick
Click
object2
OnClick
delegate object
Figure 3-3 illustrates the fundamental relationship between events and event han-
dlers that is described in this section. You’ll often see this relationship referred to in
terms of publisher/subscriber, where the object setting off the event is the publisher
and the method handling it is the subscriber.
Delegates
Connecting an event to the handling method(s) is a delegate object. This object
maintains a list of methods that it calls when an event occurs. Its role is similar to that
of the callback functions that Windows API programmers are used to, but it repre-
sents a considerable improvement in safeguarding code.
In Microsoft Windows programming, a callback occurs when a function calls
another function using a function pointer it receives. The calling function has no way
of knowing whether the address actually refers to a valid function. As a result, pro-
gram errors and crashes often occur due to bad memory references. The .NET del-
egate eliminates this problem. The C# compiler performs type checking to ensure
that a delegate only calls methods that have a signature and return type matching
that specified in the delegate declaration. As an example, consider this delegate
declaration:
When the delegate is declared, the C# compiler creates a sealed class having the
name of the delegate identifier (MyString). This class defines a constructor that
accepts the name of a method—static or instance—as one of its parameters. It also
contains methods that enable the delegate to maintain a list of target methods. This
means that—unlike the callback approach—a single delegate can call multiple event
handling methods.
A method must be registered with a delegate for it to be called by that delegate.
Only methods that return no value and accept a single string parameter can be reg-
istered with this delegate; otherwise, a compilation error occurs. Listing 3-11 shows
how to declare the MyString delegate and register multiple methods with it. When
114 Chapter 3 ■ Class Design in C#
the delegate is called, it loops through its internal invocation list and calls all the reg-
istered methods in the order they were registered. The process of calling multiple
methods is referred to as multicasting.
Note that the += operator is used to add a method to the invocation list. Con-
versely, a method can be removed using the -= operator:
In the preceding example, the delegate calls each method synchronously, which
means that each succeeding method is called only after the preceding method has
completed operation. There are two potential problems with this: a method could
3.7 Delegates and Events 115
“hang up” and never return control, or a method could simply take a long time to
process—blocking the entire application. To remedy this, .NET allows delegates to
make asynchronous calls to methods. When this occurs, the called method runs on a
separate thread than the calling method. The calling method can then determine
when the invoked method has completed its task by polling it, or having it call back a
method when it is completed. Asynchronous calls are discussed in Chapter 13,
“Asynchronous Programming and Multithreading.”
1. Design Patterns by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides;
Addison-Wesley, 1995.
116 Chapter 3 ■ Class Design in C#
To implement an event handler you must provide the signature defined by the
delegate. You can find this in documentation that describes the declaration of the
MouseEventHandler delegate:
with this code that creates a delegate and includes the code to be executed when the
delegate is invoked:
The code block, which replaces OnMouseDown, requires no method name and is
thus referred to as an anonymous method. Let’s look at its formal syntax:
To further clarify the use of anonymous methods, let’s use them to simplify the
example shown earlier in Listing 3-11. In the original version, a custom delegate is
declared, and two callback methods are implemented and registered with the dele-
gate. In the new version, the two callback methods are replaced with anonymous
code blocks:
// delegate declaration
public delegate void MyString(string s);
When the delegate is called, it executes the code provided in the two anonymous
methods, which results in the input string being printed in all lower- and uppercase
letters, respectively.
Let’s look at a simple example that illustrates the interaction of an event and
delegate:
This code declares the event DataReceived and uses it in the FireReceived-
Event method to fire the event. For demonstration purposes, FireReceivedEvent
is assigned a public access modifier; in most cases, it would be private to ensure
that the event could only be fired within the IOMonitor class. Note that it is good
3.7 Delegates and Events 119
practice to always check the event delegate for null before publishing the event.
Otherwise, an exception is thrown if the delegate’s invocation list is empty (no client
has subscribed to the event).
Only a few lines of code are required to register a method with the delegate and
then invoke the event:
The delegate signature should define a void return type, and have an object and
EventArgs type parameter. The sender parameter identifies the publisher of the
event; this enables a client to use a single method to handle and identify an event that
may originate from multiple sources.
The second parameter contains the data associated with the event. .NET provides
the EventArgs class as a generic container to hold a list of arguments. This offers
several advantages, the most important being that it decouples the event handler
method from the event publisher. For example, new arguments can be added later to
the EventArgs container without affecting existing subscribers.
Creating an EventArgs type to be used as a parameter requires defining a new
class that inherits from EventArgs. Here is an example that contains a single
string property. The value of this property is set prior to firing the event in which it
is included as a parameter.
If an event does not generate data, there is no need to create a class to serve as the
EventArgs parameter. Instead, simply pass EventArgs.Empty.
Core Note
class MyApp
{
public static void Main()
{
EventHandlerClass eClass= new EventHandlerClass();
Seller sell = new Seller();
// Register two event handlers for stocksold event
sell.StockSold += new TraderDelegate(
eClass.HandleStockSale);
sell.StockSold += new TraderDelegate(
eClass.LogTransaction);
// Invoke method to sell stock(symbol, curr price,
sell price)
sell.StartUp("HPQ",100, 26);
}
}
3.8 Operator Overloading 123
The class Seller is at the heart of the application. It performs the stock transac-
tion and signals it by publishing a StockSold event. The client requesting the trans-
action registers two event handlers, HandleStockSale and LogTransaction, to be
notified when the event occurs. Note also how the TraderEvents class exposes the
transaction details to the event handlers.
Operator overloading with classes does not have to be limited to geometric or spa-
tial objects. The example shown in Listing 3-14 demonstrates how to use the concept
to maintain stocks in a portfolio. It contains two classes: one that represents a stock
(defined by its price, number of shares, and risk factor) and one that represents the
portfolio of stocks. Two overloaded operators (+ and -) add and remove stocks from
the portfolio.
124 Chapter 3 ■ Class Design in C#
The addition or deletion of a stock causes the portfolio total value and weighted
risk factor to be adjusted. For example, when both stocks are added to the portfolio,
the risk is 1.07. Remove ibm and the risk is 1.10, the risk of hpq alone.
When choosing to implement operator overloading, be aware that .NET lan-
guages are not required to support it. The easiest way to provide interoperability for
those languages (such as Visual Basic.NET) lacking this feature is to include an addi-
tional class member that performs the same function:
In this case, the code exposes a public method whose implementation calls the over-
loaded method.
Another approach is to take advantage of the fact that language interaction occurs
at the Intermediate Language level and that each operator is represented in the IL
by a hidden method. Thus, if a language knows how to invoke this method, it can
access the operator.
The ECMA standard provides a list of method names that correspond to each
operator. For example, the + and & used in the preceding code are represented by
op_Addition and op_BitwiseAnd, respectively. A language would access the over-
loaded + operator with its own syntactic variation of the following code:
newPortfolio = P.op_Addition(P, s)
126 Chapter 3 ■ Class Design in C#
Either approach works, but relying on assembly language is probably less appeal-
ing than providing a custom public method.
Although the discussion has been on classes, operator overloading also can be
applied to simple data types. For example, you could define your own exponentiation
operator for integers.
3.9 Interfaces
Syntax:
The syntax of the interface declaration is identical to that of a class except that the
keyword interface replaces class. This should not be surprising, because an
interface is basically a class that declares, but does not implement, its members. An
instance of it cannot be created, and classes that inherit from it must implement all of
its methods.
This sounds similar to an abstract class, which also cannot be instantiated and
requires derived classes to implement its abstract methods. The difference is that an
abstract class has many more capabilities: It may be inherited by subclasses, and it
may contain state data and concrete methods.
One rule of thumb when deciding whether to use a class or interface type is the
relationship between the type and its inheriting classes. An interface defines a behav-
ior for a class—something it “can do.” The built-in .NET ICloneable interface,
which permits an object to create a copy of itself, is an example of this. Classes, on
the other hand, should be used when the inheriting class is a “type of” the base class.
For example, you could create a shape as a base class, a circle as a subclass of shape,
and the capability to change the size of the shape as an interface method.
Aside from their differing roles, there are numerous implementation and usage
differences between a class and an interface:
The first statement creates an instance of the Circle class. The second statement
creates a variable that refers to this circle object. However, because it is specified as
an IShapeFunction type, it can only access members of that interface. This is why
an attempt to reference the ShowMe method fails. By using the interface type, you
effectively create a filter that restricts access to members of the interface only.
One of the most valuable aspects of working with interfaces is that a programmer
can treat disparate classes in a similar manner, as long at they implement the same
interface.
3.9 Interfaces 129
This code creates an array that can contain any class that implements the IShape-
Function interface. Its only interest is in using the GetArea method, and it neither
has nor requires any knowledge of other class members. We can easily extend this
example to create a class to work with this array that has no knowledge of the Circle
or Rectangle class.
The code in this class can be used with any concrete class that implements the
IShapeFunction interface.
Core Approach
This not only permits a class to implement multiple methods, but has the added
effect of limiting access to this method to interface references only. For example, the
following would result in an error:
3.10 Generics
To understand and appreciate the concept of generics, consider the need to create a
class that will manage a collection of objects. The objects may be of any type and are
specified at compile time by a parameter passed to the class. Moreover, the collection
class must be type-safe—meaning that it will accept only objects of the specified
type.
In the 1.x versions of .NET, there is no way to create such a class. Your best option
is to create a class that contains an array (or other container type) that treats every-
thing as an object. As shown here, casting, or an as operator, is then required to
access the actual object type. It is also necessary to include code that verifies the
stored object is the correct type.
Generics, introduced with .NET 2.0, offer an elegant solution that eliminates the
casting, explicit type checking, and boxing that occurs for value type objects. The pri-
mary challenge of working with generics is getting used to the syntax, which can be
used with a class, interface, or structure.
The best way to approach the syntax is to think of it as a way to pass the data type
you’ll be working with as a parameter to the generic class. Here is the declaration for
a generic class:
The type parameter T is placed in brackets and serves as a placeholder for the
actual type. The compiler recognizes any reference to T within the body of the class
and replaces it with the actual type requested. As this statement shows, creating an
instance of a generic class is straightforward:
In this case, string is a type argument and specifies that the class is to work with
string types only. Note that more than one type parameter may be used, and that
132 Chapter 3 ■ Class Design in C#
the type parameter can be any name, although Microsoft uses (and recommends)
single characters in its generic classes.
Although a class may be generic, it can restrict the types that it will accept. This is
done by including an optional list of constraints for each type parameter. To
declare a constraint, add the where keyword followed by a list of parameter/require-
ment pairs. The following declaration requires that the type parameter implement
the ISerializable and IComparable interfaces:
A parameter may have multiple interface constraints and a single class restraint.
In addition, there are three special constraints to be aware of:
The following code demonstrates how a client could access the generic class
described in Listing 3-16:
Generics and the .NET generic collection classes are discussed further in
Chapter 4.
134 Chapter 3 ■ Class Design in C#
3.11 Structures
A .NET structure—struct in C# syntax—is often described as a lightweight class. It
is similar to a class in that its members include fields, methods, properties, events,
and constructors; and it can inherit from interfaces. But there are also implementa-
tion differences and restrictions that limit its capabilities vis-à-vis a class:
Because a struct is a value type, it is stored on the stack where a program works
directly with its contents. This generally provides quicker access than indirectly
accessing data through a pointer to the heap. On the downside, structs can slow
things down when passed back and forth as parameters. Rather than passing a refer-
ence, the CLR copies a struct and sends the copy to the receiving method. Also, a
struct faces the boxing and unboxing issue of value types.
When deciding whether to use a struct to represent your data, consider the fact
that types that naturally have value semantics (objects that directly contain their
value as opposed to a reference to a value) are often implemented underneath as a
struct. Examples include the primitives discussed in Chapter 2, the color struc-
ture whose properties (red, aqua, and so on) define colors in .NET, the DateTime
structure used for date-related operations, and various graphics structures used to
represent objects such as points and rectangles.
Defining Structures
Syntax:
Example:
// constructor
public DressShirt(float collar, int sleeve)
{
this.CollarSz = collar;
this.SleeveLn = sleeve;
}
}
The syntax clearly resembles a class. In fact, replace struct with class and the
code compiles. It has a public modifier that permits access from any assembly. The
default modifier is internal, which restricts access to the containing assembly. No
interfaces are specified—although a struct can inherit from an interface—so the
struct is not required to implement any specific methods.
Core Note
The first statement creates an instance that relies on the user-defined constructor
to initialize the field values. When designing such a constructor, be aware that you
must initialize all fields within the constructor; otherwise, the compiler will issue an
error.
The second statement also creates an instance of the struct by calling the
default constructor, which initializes the fields to default values of zero (0). Note the
difference here between a struct and a class: A struct always has a default param-
eterless constructor; a class has the default parameterless constructor only if there
are no explicitly defined constructors.
136 Chapter 3 ■ Class Design in C#
The most important thing to note about this code is that it could be cut and pasted
into a class and run with no changes. Although a struct doesn’t support all of the
features of a class, the ones it does are implemented using identical syntax.
Structure Class
It is clear from the table that structures possess many of the features and capabili-
ties of classes. Consequently, a developer may have difficulty deciding which is the
better choice. The answer lies in understanding the few—but significant—differ-
ences between the two.
3.13 Summary
The goal of the C# language architects was to create a “component-oriented” lan-
guage based on the traditional object-oriented principles of encapsulation, inherit-
ance, and polymorphism. Toward this end, they included language features that
make properties, events, and attributes first-class language constructs. Properties
now have their own get and set syntax; events can be created with the event key-
word and linked to delegates that call registered methods when the event occurs;
custom or built-in attributes can be attached to a class or selected class members
to add descriptive information to an assembly’s metacode.
C# provides several forms of method declarations: a virtual method permits a
derived class to implement its own version of the method; sealed prevents a derived
class from overriding it; and abstract requires a derived class to implement its own
version of the method.
In some cases, a structure or interface provides a better programming solution
than a class. The C# struct is an efficient, simple way to represent self-contained
data types that don’t require the overhead of classes. An interface is a practical way to
define a behavior that can be passed on to inheriting classes. In .NET, its value is
enhanced because a class (or struct) can inherit from any number of interfaces—
but from only one class.
140 Chapter 3 ■ Class Design in C#
2. How many classes can a class directly inherit? How many interfaces?
a. x=60 y=40
b. x=20 y=60
c. x=20 y=40
d. x=60 y=60
3.14 Test Your Understanding 141
7. What is the best way to ensure that languages that do not recognize
operator overloading can access C# code containing this feature?
8. Name two ways that you can prevent a class from being instantiated.
10. What happens if you attempt to compile and run the following code?
using System;
public class Base
{
public void aMethod(int i, String s)
{
Console.WriteLine("Base Method");
}
public Base()
{
Console.WriteLine("Base Constructor");
}
}
The purpose of this chapter is to consider what happens to a class when it becomes
an object. This metamorphosis raises some interesting questions: What is the best
way to create an object? How do you ensure that an object handles errors gracefully?
How do you prevent an object from wasting resources (the dreaded memory leak)?
What is the best way to work with groups of objects? How do you dispose of an
object? Although these questions are unlikely to keep a developer awake at night,
their consideration should lead to a keener insight into class design.
In an attempt to answer these questions, a variety of topics are presented. These
include how to create objects using established design patterns; how to implement the
System.Object methods on custom classes; how to implement exception handling;
how to persist objects using serialization; and how to use collection classes and inter-
faces to manage groups of objects. The chapter concludes with a look at how to design
an object so that it shuts down properly when subject to .NET Garbage Collection.
145
146 Chapter 4 ■ Working with Objects in C#
sole purpose is to create other objects—much like a real-world factory. Its advantage
is that it handles all the details of object creation; a client instructs the factory which
object to create and is generally unaffected by any implementation changes that may
occur.
There are a number of ways to implement the factory pattern. This section pre-
sents two logical approaches—illustrated in Figures 4-1 and 4-2.
X Factory X X
X
Client 1 Client 1
Factory Y Factory Y Y
Client 2 Client 2
Z Z Z
Factory Z
Product Product
Figure 4-1 Factory with one factory class Figure 4-2 Factory with multiple factory
classes
Figure 4-1 represents the case where one factory is used to produce all of the
related products (objects). In Figure 4-2, each product has its own factory, and the
client sends the request to the factory that produces the desired object. We’ll look at
examples of both, beginning with code for the single factory implementation (see
Listing 4-1).
The same effect could be achieved by replacing the interface with an abstract
class. This could yield better code reuse in situations where objects share common
behavior, but should be weighed against other factors that were discussed in Chapter
3, “Class Design in C#.”
With the factory and product classes defined, all the hard work has been done. It’s
a simple matter for clients to create objects:
148 Chapter 4 ■ Working with Objects in C#
If the application needs to add any more products, the factory is supplied with the
new code, but no changes are required on the client side. It only needs to be aware of
how to request all available products.
// abstract
public abstract class AppFactory
{
public abstract IApparel CreateApparel();
}
// Concrete factory classes
public class DressShirtFactory:AppFactory
{
public override IApparel CreateApparel( )
{ return new DressShirt(); }
}
public class SportShirtFactory : AppFactory
{
public override IApparel CreateApparel( )
{ return new SportsShirt(); }
}
We have created the abstract class so that its subclasses can be passed to a new
ApparelCollector class that serves as an intermediary between the clients and the
factories. Specifically, the client passes the factory to this class, and it is responsible
for calling the appropriate factory.
The code to use the new class is analogous to that in the first example:
For a simple example like this, the first approach using one factory is easier to
implement. However, there are cases where it’s preferable to have multiple factories.
The objects may be grouped into families of products (a shirt factory and a dress fac-
tory, for example), or you may have a distributed application where it makes sense for
different developers to provide their own factory classes.
System.Exception Class
As shown in Figure 4-3, System.Exception is the base class for two generic sub-
classes—SystemException and ApplicationException—from which all excep-
tion objects directly inherit. .NET Framework exceptions (such as IOException
and ArithmeticException) derive directly from IOException, whereas custom
application exceptions should inherit from ApplicationException. The sole pur-
pose of these classes is to categorize exceptions, because they do not add any proper-
ties or methods to the base System.Exception class.
System.Object
System.Exception
System.ApplicationException
System.SystemException
The System.Exception class contains relatively few members. Table 4-1 sum-
marizes the members discussed in this section.
InnerException Exception Is set to null unless the exception occurs while a pre-
vious exception is being handled. A GetBaseExcep-
tion method can be used to list a chain of previous
inner exceptions.
TargetSite MethodBase Provides details about the method that threw the
exception. The property is an object of type Method-
Base. It returns the name of the method in which the
exception occurred. It also has a DeclaringType
property that returns the name of the class containing
the method.
try {
// Code that may cause an exception.
// It may consist of multiple lines of code.
}
// May contain any number of catch blocks.
catch(exception name) {
// Place code here that handles the exception.
// Catch block may contain a throw statement.
}
catch(exception name) {
// Place code here that handles the exception.
}
finally {
// This code is always executed whether or not an
// exception occurs.
}
The exception filter identifies the exception it handles and also serves as a param-
eter when an exception is thrown to it. Consider the following statement:
This codes first looks for specific exceptions such as a division by zero or an index
out of range. The final exception filter, Exception, catches any exception derived
from System.Exception. When an exception is caught, the code in the block is
executed and all other catch blocks are skipped. Control then flows to the finally
block—if one exists.
Note that the catch block may include a throw statement to pass the exception
further up the call stack to the previous caller. The throw statement has an optional
4.2 Exception Handling 153
parameter placed in parentheses that can be used to identify the type of exception
being thrown. If throw is used without a parameter, it throws the exception caught
by the current block. You typically throw an exception when the calling method is
better suited to handle it.
Core Recommendation
StackTrace displays only those methods on the call stack to the level
where the exception is first handled—not where it occurs. Although you
may be tempted to catch exceptions at the point where they occur in
order to view the full call stack, this is discouraged. It may improve
diagnostics; however, it takes time and space to throw an exception and
its entire call stack. Usually, the lower on a call stack that an exception
occurs, the more likely it is that the conditions causing it can be avoided
by improved coding logic.
4.2 Exception Handling 155
To view the custom exception in action, let’s create two shape objects and pass
them via calls to the static ObjAreas.ShowAreas method.
{
ObjAreas.ShowAreas(myRect);
ObjAreas.ShowAreas(myCircle);
}
catch (NoDescException ex)
{
Console.WriteLine(ex.Message);
}
The ShowAreas method checks to ensure the object it has received implements
the two interfaces. If not, it throws an instance of NoDescException and control
passes to the calling code. In this example, the Circle object implements only one
interface, resulting in an exception.
Pay particular attention to the design of NoDescException. It is a useful model
that illustrates the rules to be followed in implementing a custom exception type:
Unhandled Exceptions
Unhandled exceptions occur when the CLR is unable to find a catch filter to handle
the exception. The default result is that the CLR will handle it with its own methods.
Although this provides a warning to a user or developer, it is not a recommended way
158 Chapter 4 ■ Working with Objects in C#
to deal with it. The solution is in the problem: Take advantage of .NET’s unhandled
exception event handlers to funnel all of the exceptions to your own custom excep-
tion handling class.
The custom class provides a convenient way to establish a policy for dealing with
unhandled exceptions. The code can be implemented to recognize whether it is
dealing with a debug or release version, and respond accordingly. For example, in
debug version, your main concern is to start the debugger; in a release version, you
should log the error and provide a meaningful screen that allows the user to end the
program.
Unfortunately, there is no single approach that applies to all C# programming
needs. Your actual solution depends on whether you are working with a Console,
Windows Form, Web Forms, or Web Services application. In this section, we will
look at how to implement a Windows Forms solution, which is conceptually the same
as for a Console application. Web Forms and Web Services are addressed in the Web
Applications chapters, Chapters 16–18.
Unhandled Exceptions in a
Windows Forms Application
Event handling was discussed in the previous chapter along with the important role
of delegates. We can now use those techniques to register our own callback method
that processes any unhandled exceptions thrown in the Windows application.
When an exception occurs in Windows, the application’s OnThreadException
method is ultimately called. It displays a dialog box describing the unhandled excep-
tion. You can override this by creating and registering your own method that matches
the signature of the System.Threading.ThreadExceptionEventHandler dele-
gate. Listing 4-4 shows one way this can be implemented.
MyUnhandledMethod is defined to handle the exception and must be registered
to receive the callback. The following code registers the method for the Thread-
Exception event using the ThreadExceptionEventHandler delegate and runs
the application:
For a Console application, the same approach is used except that the delegate and
event names are different. You would register the method with the following:
Thread.GetDomain().UnhandledException += new
UnhandledExceptionEventHandler(
UnForgiven.MyUnhandledMethodAp);
Only catch exceptions when you have a specific need to do the following:
• Perform a recovery
• Perform a cleanup, particularly to release resources
• Log information
• Provide extra debug information
ex.ToString() // Output:
// Attempted to divide by zero
// at TestExcep.Calc(Int32 j)
// at MyApp.Main()
The following statements create an instance of the object and set desc to the
more meaningful value returned by ToString():
This method compares an instance of the current object with the one passed to it.
The first step is to ensure that the received object is not null. Next, following the steps
in Figure 4-5, the types of the two objects are compared to make sure they match.
164 Chapter 4 ■ Working with Objects in C#
1 2 3 4 5 6
The heart of the method consists of comparing the field values of the two objects.
To compare reference fields, it uses the static Object.Equals method, which takes
two objects as arguments. It returns true if the two objects reference the same
instance, if both objects are null, or if the object’s Equals comparison returns true.
Value types are compared using the field’s Equals method:
if (!myID.Equals(otherObj.myID))
return false;
Here is an example that demonstrates the new Equals method. It creates two
Chair objects, sets their fields to the same value, and performs a comparison:
Although the two objects have identical field values, the comparison fails—which
is probably not what you want. The reason is that the objects point to two different
myUpholstery instances, causing their reference comparison to fail. The solution is
to override the Equals method in the Upholstery class, so that it performs a value
comparison of the Fabric fields. To do so, place this code inside its Equals method,
in addition to the other overhead code shown in Listing 4-6:
Overriding GetHashCode
The GetHashCode method generates an Int32 hash code value for any object. This
value serves as an identifier that can be used to place any object in a hash table col-
lection. The Framework designers decreed that any two objects that are equal must
have the same hash code. As a result, any new Equals method must be paired with a
GetHashCode method to ensure that identical objects generate the same hash code
value.
4.3 Implementing System.Object Methods in a Custom Class 165
Ideally, the hash code values generated over the range of objects represent a wide
distribution. This example used a simple algorithm that calls the base type’s Get-
HashCode method to return a value based on the item’s ID. This is a good choice
because the IDs are unique for each item, the ID is an instance field, and the ID
field value is immutable—being set only in the constructor.
The method returns true because chair1 and chair3 reference the same
instance.
Figure 4-6 depicts a shallow and deep copy for this instance of the Chair class:
In both cases, the clone of the myChair object contains its own copy of the value
type fields. However, the shallow copy points to the same instance of myUpholstery
as the original; in the deep copy, it references a duplicate object.
Let’s now look at how to implement shallow cloning on a custom object. Deep
cloning (not discussed) is specific to each class, and it essentially requires creating an
instance of the object to be cloned and copying all field values to the clone. Any ref-
erence objects must also be created and assigned values from the original referenced
objects.
The only requirements are that the class inherit the ICloneable interface and
implement the Clone method using MemberwiseClone. To demonstrate, let’s use
this code segment to create a shallow copy clone of myChair by calling its Clone
method:
The results confirm this is a shallow copy: The reference comparison of myChair
and its clone fails because chairClone is created as a copy of the original object; on
the other hand, the comparison of the reference field myUpholstery succeeds
because the original and clone objects point to the same instance of the myUphol-
stery class.
To best work with container classes such as the ArrayList and Hashtable, a
developer should be familiar with the interfaces they implement. Interfaces not only
provide a uniform way of managing and accessing the contents of a collection, but
they are the key to creating a custom collection. We’ll begin the section by looking at
the most useful interfaces and the behavior they impart to the collection classes.
Then, we’ll examine selected collection classes.
Collection Interfaces
Interfaces play a major role in the implementation of the concrete collection classes.
All collections inherit from the ICollection interface, and most inherit from the
IEnumerable interface. This means that a developer can use common code seman-
tics to work with the different collections. For example, the foreach statement is
used to traverse the elements of a collection whether it is a Hashtable,a Sorted-
List, or a custom collection. The only requirement is that the class implements the
IEnumerable interface.
Table 4-2 summarizes the most important interfaces inherited by the collection
classes. IComparer provides a uniform way of comparing elements for the purpose of
sorting; IDictionary defines the special members required by the Hashtable and
Dictionary objects; similarly, IList is the base interface for the ArrayList collection.
Interface Description
ICollection The base interface for the collection classes. It contains prop-
erties to provide the number of elements in the collection and
whether it is thread-safe. It also contains a method that copies
elements of the collection into an array.
IList The base interface of all lists. It controls whether the elements
in the list can be modified, added, or deleted.
4.4 Working with .NET Collection Classes and Interfaces 169
The UML-like diagram in Figure 4-7 shows how these interfaces are related.
Recall that interfaces may inherit from multiple interfaces. Here, for example, IDic-
tionary and IList inherit from IEnumerable and ICollection. Also of special
interest is the GetEnumerator method that is used by interfaces to return the
IEnumerator interface.
<<Interface>>
ICollection <<Interface>>
IDictionaryEnumerator
Count
IsSynchronized Entry
SyncRoot Key
CopyTo Value
<<Interface>> <<Interface>>
IList IDictionary
... ...
Of these interfaces, IDictionary and IList are the most important when con-
sidering the built-in collections provided by the FCL. For this reason, we’ll discuss
them later in the context of the collection classes.
ICollection Interface
This interface provides the minimal information that a collection must implement.
All classes in the System.Collections namespace inherit it (see Table 4-3).
The IsSynchronized and SyncRoot properties require explanation if you are
not yet familiar with threading (discussed in Chapter 13, “Asynchronous Program-
ming and Multithreading”). Briefly, their purpose is to ensure the integrity of data in
170 Chapter 4 ■ Working with Objects in C#
Member Description
int Count Property that returns the number of entries in the col-
lection.
void CopyTo( array, index) Method to copy the contents of the collection to an
array.
IHashCodeProvider Interface
This interface has one member—the GetHashCode method—that uses a custom
hash function to return a hash code value for an object. The Hashtable class uses
this interface as a parameter type in one of its overloaded constructors, but other
than that you will rarely see it.
Member Description
The compiler expands the foreach construct into a while loop that employs
IEnumerable and IEnumerator members:
Core Note
Iterators
The foreach construct provides a simple and uniform way to iterate across mem-
bers of a collection. For this reason, it is a good practice—almost a de facto require-
ment—that any custom collection support foreach. Because this requires that the
collection class support the IEnumerable and IEnumerator interfaces, the tradi-
tional approach is to explicitly implement each member of these interfaces inside the
collection class. Referencing Table 4-4, this means writing code to support the Get-
Enumerator method of the IEnumerable interface, and the MoveNext, Current,
and Reset members of IEnumerator. In essence, it is necessary to build a state
machine that keeps track of the most recently accessed item in a collection and
knows how to move to the next item.
C# 2.0 introduced a new syntax referred to as iterators that greatly simplifies the
task of implementing an iterator pattern. It includes a yield return (also a yield
break) statement that causes the compiler to automatically generate code to imple-
ment the IEnumerable and IEnumerator interfaces. To illustrate, let’s add iterators
to the GenStack collection class that was introduced in the generics discussion in
Chapter 3. (Note that iterators work identically in a non-generics collection.)
Listing 4-7 shows part of the original GenStack class with two new members that
implement iterators: a GetEnumerator method that returns an enumerator to
traverse the collection in the order items are stored, and a Reverse property that
returns an enumerator to traverse the collection in reverse order. Both use yield
return to generate the underlying code that supports foreach iteration.
4.4 Working with .NET Collection Classes and Interfaces 173
This code should raise some obvious questions about iterators: Where is the
implementation of IEnumerator? And how can a method with an IEnumerator
return type or a property with an IEnumerable return type seemingly return a
string value?
174 Chapter 4 ■ Working with Objects in C#
The answer to these questions is that the compiler generates the code to take care
of the details. If the member containing the yield return statement is an IEnu-
merable type, the compiler implements the necessary generics or non-generics ver-
sion of both IEnumerable and IEnumerator; if the member is an IEnumerator
type, it implements only the two enumerator interfaces. The developer’s responsibil-
ity is limited to providing the logic that defines how the collection is traversed and
what items are returned. The compiler uses this logic to implement the IEnumera-
tor.MoveNext method.
The client code to access the GenStack collection is straightforward. An instance
of the GenStack class is created to hold ten string elements. Three items are
added to the collection and are then displayed in original and reverse sequence.
The Reverse property demonstrates how easy it is to create multiple iterators for
a collection. You simply implement a property that traverses the collection in some
order and uses tbe yield return statement(s) to return an item in the collection.
Core Note
IComparable
Unlike the other interfaces in this section, IComparable is a member of the System
namespace. It has only one member, the method CompareTo:
The object in parentheses is compared to the current instance of the object imple-
menting CompareTo, and the returned value indicates the results of the comparison.
Let’s use this method to extend the Chair class so that it can be sorted on its
myPrice field. This requires adding the IComparable inheritance and implement-
ing the CompareTo method.
The code to sort an array of Chair objects is straightforward because all the work
is done inside the Chair class:
IComparer
The previous example allows you to sort items on one field. A more flexible and real-
istic approach is to permit sorting on multiple fields. This can be done using an over-
loaded form of Array.Sort that takes an object that implements IComparer as its
second parameter.
IComparer is similar to IComparable in that it exposes only one member, Com-
pare, that receives two objects. It returns a value of –1, 0, or 1 based on whether the
first object is less than, equal to, or greater than the second. The first object is usually
the array to be sorted, and the second object is a class implementing a custom Com-
pare method for a specific object field. This class can be implemented as a separate
helper class or as a nested class within the class you are trying to sort (Chair).
This code creates a helper class that sorts the Chair objects by the myVendor
field:
If you refer back to the Chair class definition (refer to Figure 4-6 on page 166),
you will notice that there is a problem with this code: myVendor is a private member
and not accessible in this outside class. To make the example work, change it to pub-
lic. A better solution, of course, is to add a property to expose the value of the field.
In order to sort, pass both the array to be sorted and an instance of the helper
class to Sort:
Array.Sort(chairsOrdered,new CompareByVen());
In summary, sorting by more than one field is accomplished by adding classes that
implement Compare for each sortable field.
System.Collections Namespace
The classes in this namespace provide a variety of data containers for managing col-
lections of data. As shown in Figure 4-8, it is useful to categorize them based on the
primary interface they implement: ICollection, IList, or IDictionary.
Collections
Stacks play a useful role for an application that needs to maintain state informa-
tion in order to hold tasks to be “performed later.” The call stack associated with
exception handling is a classic example; stacks are also used widely in text parsing
operations. Listing 4-8 provides an example of some of the basic stack operations.
Three objects are added to the stack. PrintValues enumerates the stack and
lists the objects in the reverse order they were added. The Pop method removes
“Lane” from the top of the stack. A new object is pushed onto the stack and the Peek
method lists it. Note that the foreach statement could also be used to list the con-
tents of the Stack.
ArrayList
The ArrayList includes all the features of the System.Array, but also extends it to
include dynamic sizing and insertion/deletion of items at a specific location in the list.
These additional features are defined by the IList interface from which ArrayList
inherits.
IList Interface
This interface, whose members are listed in Table 4-6, is used to retrieve the con-
tents of a collection via a zero-based numeric index. This permits insertion and
removal at random location within the list.
The most important thing to observe about this interface is that it operates on
object types. This means that an ArrayList—or any collection implementing
IList—may contain types of any kind. However, this flexibility comes at a cost:
Casting must be widely used in order to access the object’s contents, and value types
must be converted to objects (boxed) in order to be stored in the collection. As we
see shortly, C# 2.0 offers a new feature—called generics—that addresses both issues.
However, the basic functionality of the ArrayList, as illustrated in this code seg-
ment, remains the same.
180 Chapter 4 ■ Working with Objects in C#
Interface Description
int Add(object) Adds an item to the end of a list. It returns the value
of the index where the item was added.
void Insert (index, object) Methods to insert a value at a specific index; delete
void RemoveAt (index) the value at a specific index; and remove the first
void Remove (object) occurrence of an item having the specified value.
Hashtable
The Hashtable is a .NET version of a dictionary for storing key-value pairs. It asso-
ciates data with a key and uses the key (a transformation algorithm is applied) to
determine a location where the data is stored in the table. When data is requested,
the same steps are followed except that the calculated memory location is used to
retrieve data rather than store it.
Syntax:
As shown here, the Hashtable inherits from many interfaces; of these, IDic-
tionary is of the most interest because it provides the properties and methods used
to store and retrieve data.
IDictionary Interface
Collections implementing the IDictionary interface contain items that can be
retrieved by an associated key value. Table 4-7 summarizes the most important mem-
bers for working with such a collection.
Member Description
ICollection Keys Properties that return the keys and values of the collection.
ICollection Values
void Add (key, value) Methods to add a key-value pair to a collection, remove
void Clear () a specific key, and remove all items (clear) from the
void Remove (key) collection.
IDictionaryEnumerator Interface
As shown in Figure 4-7 on page 169, IDictionaryEnumerator inherits from
IEnumerator. It adds properties to enumerate through a dictionary by retrieving
keys, values, or both.
Member Description
DictionaryEntry Entry The variable Entry is used to retrieve both the key and
value when iterating through a collection.
object Key Properties that return the keys and values of the current
object Value collection entry.
All classes derived from IDictionary maintain two internal lists of data: one for
keys and one for the associated value, which may be an object. The values are stored
in a location based on the hash code of the key. This code is provided by the key’s
System.Object.GetHashCode method, although it is possible to override this with
your own hash algorithm.
This structure is efficient for searching, but less so for insertion. Because keys may
generate the same hash code, a collision occurs that requires the code be recalcu-
lated until a free bucket is found. For this reason, the Hashtable is recommended
for situations where a large amount of relatively static data is to be searched by key
values.
Create a Hashtable
A parameterless constructor creates a Hashtable with a default number of buckets
allocated and an implicit load factor of 1. The load factor is the ratio of values to
buckets the storage should maintain. For example, a load factor of .5 means that a
hash table should maintain twice as many buckets as there are values in the table.
The alternate syntax to specify the initial number of buckets and load factor is
The following code creates a hash table and adds objects to it:
// Create HashTable
Hashtable chairHash = new Hashtable();
// Add key - value pair to Hashtable
chairHash.Add ("88-00", new Chair(350.0, "Adams", "88-00");
chairHash.Add ("99-03", new Chair(380.0, "Lane", "99-03");
4.4 Working with .NET Collection Classes and Interfaces 183
// or this syntax
chairHash["89-01"] = new Chair(250.0, "Broyhill", "89-01");
There are many ways to add values to a Hashtable, including loading them from
another collection. The preceding example shows the most straightforward
approach. Note that a System.Argument exception is thrown if you attempt to add a
value using a key that already exists in the table. To check for a key, use the Con-
tainsKey method:
// List Keys
foreach (string invenKey in chairHash.Keys)
{ MessageBox.Show(invenKey); }
// List Values
foreach (Chair chairVal in chairHash.Values)
{ MessageBox.Show(chairVal.myVendor);}
Core Note
This section has given you a flavor of working with System.Collections inter-
faces and classes. The classes presented are designed to meet most general-purpose
programming needs. There are numerous other useful classes in the namespace as
well as in the System.Collections.Specialized namespace. You should have
little trouble working with either, because all of their classes inherit from the same
interfaces presented in this section.
System.Collections.Generic Namespace
Recall from Chapter 3 that generics are used to implement type-safe classes, struc-
tures, and interfaces. The declaration of a generic type includes one (or more) type
parameters in brackets (<>) that serve(s) as a placeholder for the actual type to be
used. When an instance of this type is created, the client uses these parameters to
pass the specific type of data to be used in the generic type. Thus, a single generic
class can handle multiple types of data in a type-specific manner.
No classes benefit more from generics than the collections classes, which stored
any type of data as an object in .NET 1.x. The effect of this was to place the burden
of casting and type verification on the developer. Without such verification, a single
ArrayList instance could be used to store a string, an integer, or a custom object.
Only at runtime would the error be detected.
The System.Collections.Generic namespace provides the generic versions
of the classes in the System.Collections namespace. If you are familiar with the
non-generic classes, switching to the generic type is straightforward. For example,
this code segment using the ArrayList:
4.4 Working with .NET Collection Classes and Interfaces 185
The declaration of List includes a type parameter that tells the compiler what
type of data the object may contain—int in this case. The compiler then generates
code that expects the specified type. For the developer, this eliminates the need for
casting and type verification at runtime. From a memory usage and efficiency stand-
point, it also eliminates boxing (conversion to objects) when primitives are stored in
the collection.
System.Collections System.Collections.Generic
Comparer Comparer<T>
Hashtable Dictionary<K,T>
ArrayList List<T>
Queue Queue<T>
SortedList SortedDictionary<K,T>
Stack Stack<T>
ICollection ICollection<T>
IComparable IComparable<T>
IComparer IComparer<T>
IDictionary IDictionary<K,T>
IEnumerable IEnumerable<T>
IEnumerator IEnumerator<T>
IKeyComparer IKeyComparer<T>
IList IList<T>
(not applicable) LinkedList<T>
186 Chapter 4 ■ Working with Objects in C#
The only other points to note regard IEnumerator. Unlike the original version,
the generics version inherits from IDisposable and does not support the Reset
method.
Observe how data is retrieved from the Hashtable. Because data is stored as an
object, verification is required to ensure that the object being retrieved is a Chair
4.5 Object Serialization 187
type; casting is then used to access the members of the object. These steps are unnec-
essary when the type-safe Dictionary class is used in place of the Hashtable.
The Dictionary<K,V> class accepts two type parameters that allow it to be
strongly typed: K is the key type and V is the type of the value stored in the collection.
In this example, the key is a string representing the unique product identifier, and
the value stored in the Dictionary is a Chair type.
Serialization is used primarily for two tasks: to implement Web Services and to
store (persist) collections of objects to a medium from which they can be later resur-
rected. Web Services and their use of XML serialization are discussed in Chapter 18,
“XML Web Services.” This section focuses on how to use binary serialization to store
and retrieve objects. The examples use File I/O (see Chapter 5) methods to read and
write to a file, which should be easily understood within the context of their usage.
Binary Serialization
The BinaryFormatter object that performs binary serialization is found in the
System.Runtime.Serialization.Formatters.Binary namespace. It performs
serialization and deserialization using the Serialize and Deserialize methods,
respectively, which it inherits from the IFormatter interface.
Listing 4-9 provides an example of binary serialization using simple class mem-
bers. A hash table is created and populated with two Chair objects. Next, a
FileStream object is instantiated that points to a file on the local drive where the
serialized output is stored. A BinaryFormatter is then created, and its Serialize
method is used to serialize the hash table’s contents to a file. To confirm the process,
the hash table is cleared and the BinaryFormatter object is used to deserialize the
contents of the file into the hash table. Finally, one of the members from a restored
object in the hash table is printed—verifying that the original contents have been
restored.
[NonSerialized]
public Upholstery myUpholstery;
The primary reason for marking a field NonSerialized is that it may have no
meaning where it is serialized. Because an object graph may be loaded onto a
machine different from the one on which it was stored, types that are tied to system
operations are the most likely candidates to be excluded. These include delegates,
events, file handles, and threads.
Core Note
An event handler for these events is implemented in the object being serialized
and must satisfy two requirements: the attribute associated with the event must be
attached to the method, and the method must have this signature:
To illustrate, here is a method called after all objects have been deserialized. The
binary formatter iterates the list of objects in the order they were deserialized and
calls each object’s OnDeserialized method. This example uses the event handler to
selectively update a field in the object. A more common use is to assign values to
fields that were not serialized.
Note that more than one method can have the same event attribute, and that
more than one attribute can be assigned to a method—although the latter is rarely
practical.
[OptionalField]
private string finish;
The presence of the attribute causes the formatter to assign a default null value
to the finish field, and no exception is thrown. The application may also take advan-
tage of the deserialized event to assign a value to the new field:
192 Chapter 4 ■ Working with Objects in C#
(garbage) and compact the heap memory. This is a complicated process because the
collector must deal with the twin tasks of updating all old references to the new
object addresses and ensuring that the state of the heap is not altered as Garbage
Collection takes place.
Object I Object D
Object H Object B Object B
Object G Object J
Object F Freachable Object I Freachable
Object E Queue Object G Queue
Object D Object F
Object C Object E
Object B Object D Object D
Object A Object B Object G
Before Garbage Collection After Garbage Collection
The details of Garbage Collection are not as important to the programmer as the
fact that it is a nondeterministic (occurs unpredictably) event that deals with man-
aged resources only. This leaves the programmer facing two problems: how to dis-
pose of unmanaged resources such as files or network connections, and how to
dispose of them in a timely manner. The solution to the first problem is to implement
a method named Finalize that manages object cleanup; the second is solved by
adding a Dispose method that can be called to release resources before Garbage
Collection occurs. As we will see, these two methods do not operate autonomously.
Proper object termination requires a solution that coordinates the actions of both
methods.
Core Note
Garbage Collection typically occurs when the CLR detects that some
memory threshold has been reached. However, there is a static method
GC.Collect that can be called to trigger Garbage Collection. It can be
useful under controlled conditions while debugging and testing, but
should not be used as part of an application.
194 Chapter 4 ■ Working with Objects in C#
Object Finalization
Objects that contain a Finalize method are treated differently during both object
creation and Garbage Collection than those that do not contain a Finalize method.
When an object implementing a Finalize method is created, space is allocated on
the heap in the usual manner. In addition, a pointer to the object is placed in the
finalization queue (see Figure 4-9). During Garbage Collection, the GC scans the
finalization queue searching for pointers to objects that are no longer reachable.
Those found are moved to the freachable queue. The objects referenced in this
queue remain alive, so that a special background thread can scan the freachable
queue and execute the Finalize method on each referenced object. The memory
for these objects is not released until Garbage Collection occurs again.
To implement Finalize correctly, you should be aware of several issues:
It turns out that you do not have to implement Finalize directly. Instead, you
can create a destructor and place the finalization code in it. The compiler converts
the destructor code into a Finalize method that provides exception handling,
includes a call to the base class Finalize, and contains the code entered into the
destructor:
Note that an attempt to code both a destructor and Finalize method results in a
compiler error.
As it stands, this finalization approach suffers from its dependency on the GC to
implement the Finalize method whenever it chooses. Performance and scalability
4.6 Object Life Cycle Management 195
are adversely affected when expensive resources cannot be released when they are
no longer needed. Fortunately, the CLR provides a way to notify an object to per-
form cleanup operations and make itself unavailable. This deterministic finalization
relies on a public Dispose method that a client is responsible for calling.
IDisposable.Dispose()
Although the Dispose method can be implemented independently, the recom-
mended convention is to use it as a member of the IDisposable interface. This
allows a client to take advantage of the fact that an object can be tested for the exist-
ence of an interface. Only if it detects IDisposable does it attempt to call the Dis-
pose method. Listing 4-10 presents a general pattern for calling the Dispose
method.
This code takes advantage of the finally block to ensure that Dispose is called
even if an exception occurs. Note that you can shorten this code by replacing the
try/finally block with a using construct that generates the equivalent code:
Using(connObj)
{ connObj.UseResources() }
CleanUp();
IsDisposed = true;
GC.SuppressFinalize(this);
}
}
protected virtual void CleanUp()
{
// cleanup code here
}
~MyConnections() // Destructor that creates Finalize()
{ CleanUp(); }
public void UseResources()
{
// code to perform actions
if(Disposed)
{
throw new ObjectDisposedException
("Object has been disposed of");
}
}
}
// Inheriting class that implements its own cleanup
public class DBConnections: MyConnections
{
protected override void CleanUp()
{
// implement cleanup here
base.CleanUp();
}
}
4.7 Summary
This chapter has discussed how to work with objects. We’ve seen how to create them,
manipulate them, clone them, group them in collections, and destroy them. The
chapter began with a description of how to use a factory design pattern to create
objects. It closed with a look at how object resources are released through automatic
Garbage Collection and how this process can be enhanced programmatically through
the use of the Dispose and Finalize methods. In between, the chapter examined
how to make applications more robust with the use of intelligent exception handling,
how to customize the System.Object methods such as Equals and ToString to
work with your own objects, how cloning can be used to make deep or shallow cop-
ies, and how to use the built-in classes available in the System.Collections and
System.Collections.Generic namespaces.
As a by-product of this chapter, you should now have a much greater appreciation
of the important role that interfaces play in application design. They represent the
base product when constructing a class factory, and they allow you to clone (IClone-
able), sort (IComparer), or enumerate (IEnumerable) custom classes. Knowing
that an object implements a particular interface gives you an immediate insight into
the capabilities of the object.
■ Chapter 6
Building Windows Forms Applications 266
■ Chapter 7
Windows Forms Controls 318
■ Chapter 8
.NET Graphics Using GDI+ 378
■ Chapter 9
Fonts, Text, and Printing 426
■ Chapter 10
Working with XML in .NET 460
■ Chapter 11
ADO.NET 496
■ Chapter 12
Data Binding with Windows Forms Controls 544
C# TEXT
MANIPULATION
AND FILE I/O
This chapter introduces the string handling capabilities provided by the .NET
classes. Topics include how to use the basic String methods for extracting and
manipulating string content; the use of the String.Format method to display num-
bers and dates in special formats; and the use of regular expressions (regexes) to per-
form advanced pattern matching. Also included is a look at the underlying features of
.NET that influence how an application works with text. Topics include how the
Just-In-Time (JIT) compiler optimizes the use of literal strings; the importance of
Unicode as the cornerstone of character and string representations in .NET; and the
built-in localization features that permit applications to automatically take into
account the culture-specific characteristics of languages and countries.
This chapter is divided into two major topics. The first topic focuses on how to
create, represent, and manipulate strings using the System.Char, System.String,
and Regex classes; the second takes up a related topic of how to store and retrieve
string data. It begins by looking at the Stream class and how to use it to process raw
bytes of data as streams that can be stored in files or transmitted across a network.
The discussion then moves to using the TextReader/TextWriter classes to read
and write strings as lines of text. The chapter concludes with examples of how mem-
bers of the System.IO namespace are used to access the Microsoft Windows direc-
tory and file structure.
203
204 Chapter 5 ■ C# Text Manipulation and File I/O
Unicode
NET fully supports the Unicode standard. Its internal representation of a character
is an unsigned 16-bit number that conforms to the Unicode encoding scheme. Two
bytes enable a character to represent up to 65,536 values. Figure 5-1 illustrates why
two bytes are needed.
The uppercase character on the left is a member of the Basic Latin character set
that consists of the original 128 ASCII characters. Its decimal value of 75 can be
depicted in 8 bits; the unneeded bits are set to zero. However, the other three char-
acters have values that range from 310 (0x0136) to 56,609 (0xDB05), which can be
represented by no less than two bytes.
1. Unicode Consortium—www.unicode.org.
5.1 Characters and Unicode 205
Core Note
Core Note
Char k = 'K';
int iCat = (int) char.GetUnicodeCategory(k); // 0
Console.WriteLine(char.GetUnicodeCategory(k)); // UppercaseLetter
char cr = (Char)13;
iCat = (int) char.GetUnicodeCategory(cr); // 14
Console.WriteLine(char.GetUnicodeCategory(cr)); // Control
Unicode
Method Category Description
IsLetter 0, 1, 2, 4 Letter.
Using these methods is straightforward. The main point of interest is that they
have overloads that accept a single char parameter, or two parameters specifying a
string and index to the character within the string.
Console.WriteLine(Char.IsSymbol('+')); // true
Console.WriteLine(Char.IsPunctuation('+')): // false
string str = "black magic";
Console.WriteLine(Char.IsWhiteSpace(str, 5)); // true
char p = '.';
Console.WriteLine(Char.IsPunctuation(p)); // true
Int iCat = (int) char.GetUnicodeCategory(p); // 24
Char p = '(';
Console.WriteLine(Char.IsPunctuation(p)); // true
int iCat = (int) char.GetUnicodeCategory(p); // 20
5.2 The String Class 209
Creating Strings
A string is created by declaring a variable as a string type and assigning a value to it.
The value may be a literal string or dynamically created using concatenation. This is
often a perfunctory process and not an area that most programmers consider when
trying to improve code efficiency. In .NET, however, an understanding of how literal
strings are handled can help a developer improve program performance.
String Interning
One of the points of emphasis in Chapter 1, “Introduction to .NET and C#,” was to
distinguish how value and reference types are stored in memory. Recall that value
types are stored on a stack, whereas reference types are placed on a managed heap.
It turns out that that the CLR also sets aside a third area in memory called the intern
pool, where it stores all the string literals during compilation. The purpose of this
pool is to eliminate duplicate string values from being stored.
210 Chapter 5 ■ C# Text Manipulation and File I/O
Figure 5-2 shows a simplified view of how the strings and their values are stored in
memory.
poem1 •
poem2 •
poem3 • Object3
poem4 • "Christabel"
Object2
Thread Stack "Kubla Khan"
Object1
"Kubla Khan"
Key Pointer
Managed Heap
"Christabel" •
"Kubla Khan" •
Intern Pool
The intern pool is implemented as a hash table. The hash table key is the actual
string and its pointer references the associated string object on the managed heap.
When the JITcompiler compiles the preceding code, it places the first instance of
"Kubla Khan" (poem1) in the pool and creates a reference to the string object on
the managed heap. When it encounters the second string reference to "Kubla
Khan" (poem2), the CLR sees that the string already exists in memory and, instead of
creating a new string, simply assigns poem2 to the same object as poem1. This pro-
cess is known as string interning. Continuing with the example, the String.Copy
method creates a new string poem3 and creates an object for it in the managed heap.
Finally, the string literal associated with poem4 is added to the pool.
To examine the practical effects of string interning, let’s extend the previous exam-
ple. We add code that uses the equivalence (==) operator to compare string values
and the Object.ReferenceEquals method to compare their addresses.
5.2 The String Class 211
The first two statements compare the value of the variables and—as expected—
return a true value. The third statement compares the memory location of the vari-
ables poem3 and poem2. Because they reference different objects in the heap, a
value of false is returned.
The .NET designers decided to exclude dynamically created values from the
intern pool because checking the intern pool each time a string was created would
hamper performance. However, they did include the String.Intern method as a
way to selectively add dynamically created strings to the literal pool.
The String.Intern method searches for the value of poem5 ("Kubla Khan")
in the intern pool; because it is already in the pool, there is no need to add it. The
method returns a reference to the already existing object (Object1) and assigns it to
poem5. Because poem5 and poem1 now point to the same object, the comparison in
the final statement is true. Note that the original object created for poem5 is
released and swept up during the next Garbage Collection.
Core Recommendation
This code segment demonstrates the static and reference forms of the Equals
method:
//
Console.WriteLine(String.Equals(poem1,poem2)); // true
Console.WriteLine(poem1.Equals(poem3)); // true
Console.WriteLine(poem1 == poem3); // equivalent to Equals
Console.WriteLine(poem1 == poem4); // false – case differs
Note that the == operator, which calls the Equals method underneath, is a more
convenient way of expressing the comparison.
Although the Equals method satisfies most comparison needs, it contains no
overloads that allow it to take case sensitivity and culture into account. To address
this shortcoming, the string class includes the Compare method.
Using String.Compare
String.Compare is a flexible comparison method that is used when culture or case
must be taken into account. Its many overloads accept culture and case-sensitive
parameters, as well as supporting substring comparisons.
Syntax:
Parameters:
str1 and str2 Specify strings to be compared.
IgnoreCase Set true to make comparison case-insensitive (default is false).
index1 and Starting position in str1 and str2.
index2
ci A CultureInfo object indicating the culture to be used.
Compare returns an integer value that indicates the results of the comparison. If
the two strings are equal, a value of 0 is returned; if the first string is less than the sec-
ond, a value less than zero is returned; if the first string is greater than the second, a
value greater than zero is returned.
The following segment shows how to use Compare to make case-insensitive and
case-sensitive comparisons:
int result;
string stringUpper = "AUTUMN";
214 Chapter 5 ■ C# Text Manipulation and File I/O
Perhaps even more important than case is the potential effect of culture informa-
tion on a comparison operation. .NET contains a list of comparison rules for each
culture that it supports. When the Compare method is executed, the CLR checks the
culture associated with it and applies the rules. The result is that two strings may
compare differently on a computer with a US culture vis-à-vis one with a Japanese
culture. There are cases where it may be important to override the current culture to
ensure that the program behaves the same for all users. For example, it may be cru-
cial that a sort operation order items exactly the same no matter where the applica-
tion is run.
By default, the Compare method uses culture information based on the
Thread.CurrentThread.CurrentCulture property. To override the default, sup-
ply a CultureInfo object as a parameter to the method. This statement shows how
to create an object to represent the German language and country:
The string values "circle" and "chair" are compared using the US culture, no
culture, and the Czech culture. The first two comparisons return a value indicating
that "circle" > "chair", which is what you expect. However, the result using the
Czech culture is the opposite of that obtained from the other comparisons. This is
because one of the rules of the Czech language specifies that "ch" is to be treated as
a single character that lexically appears after "c".
Core Recommendation
Using String.CompareOrdinal
To perform a comparison that is based strictly on the ordinal value of characters, use
String.CompareOrdinal. Its simple algorithm compares the Unicode value of two
strings and returns a value less than zero if the first string is less than the second; a
value of zero if the strings are equal; and a value greater than zero if the first string is
greater than the second. This code shows the difference between it and the Compare
method:
TextElementEnumerator tEnum =
StringInfo.GetTextElementEnumerator(poem) ;
while (tEnum.MoveNext()) // Step through the string
{
Console.WriteLine(tEnum.Current); // Print current char
}
String Transformations
Table 5-3 summarizes the most important string class methods for modifying a
string. Because the original string is immutable, any string constructed by these
methods is actually a new string with its own allocated memory.
Tag Description
Tag Description
Split( char[]) The char array contains delimiters that are used to break a
string into substrings that are returned as elements in a
string array.
string words = "red,blue orange ";
string [] split = words.Split(new Char []
{' ', ','});
Console.WriteLine(split[2]); // orange
Tag Description
Most of these methods have analogues in other languages and behave as you
would expect. Somewhat surprisingly, as we see in the next section, most of these
methods are not available in the StringBuilder class. Only Replace, Remove, and
Insert are included.
String Encoding
Encoding comes into play when you need to convert between strings and bytes for
operations such as writing a string to a file or streaming it across a network. Charac-
ter encoding and decoding offer two major benefits: efficiency and interoperability.
Most strings read in English consist of characters that can be represented by 8 bits.
Encoding can be used to strip an extra byte (from the 16-bit Unicode memory repre-
sentation) for transmission and storage. The flexibility of encoding is also important
in allowing an application to interoperate with legacy data or third-party data
encoded in different formats.
The .NET Framework supports many forms of character encoding and decoding.
The most frequently used include the following:
Encoding and decoding are performed using the Encoding class found in the
System.Text namespace. This abstract class has several static properties that return
an object used to implement a specific encoding technique. These properties include
ASCII, UTF8, and Unicode. The latter is used for UTF-16 encoding.
An encoding object offers several methods—each having several overloads—for
converting between characters and bytes. Here is an example that illustrates two of
the most useful methods: GetBytes, which converts a text string to bytes, and Get-
String, which reverses the process and converts a byte array to a string.
You can also instantiate the encoding objects directly. In this example, the UTF-8
object could be created with
With the exception of ASCIIEncoding, the constructor for these classes defines
parameters that allow more control over the encoding process. For example, you can
specify whether an exception is thrown when invalid encoding is detected.
5.5 StringBuilder
The primary drawback of strings is that memory must be allocated each time the
contents of a string variable are changed. Suppose we create a loop that iterates 100
times and concatenates one character to a string during each iteration. We could end
up with a hundred strings in memory, each differing from its preceding one by a sin-
gle character.
The StringBuilder class addresses this problem by allocating a work area
(buffer) where its methods can be applied to the string. These methods include ways
to append, insert, delete, remove, and replace characters. After the operations are
complete, the ToString method is called to convert the buffer to a string that can be
assigned to a string variable. Listing 5-1 introduces some of the StringBuilder
methods in an example that creates a comma delimited list.
5.5 StringBuilder 221
All operations occur in a single buffer and require no memory allocation until the
final assignment to csv. Let’s take a formal look at the class and its members.
// Stringbuilder(initial value)
StringBuilder sb1 = new StringBuilder("abc");
// StringBuilder(initial value, initial capacity)
StringBuilder sb2 = new StringBuilder("abc", 16);
// StringBuiler(Initial Capacity, maximum capacity)
StringBuilder sb3 = new StringBuilder(32,128);
int i = 4;
char[] ch = {'w','h','i','t','e'};
string myColor = " orange&