0% found this document useful (0 votes)
745 views1,006 pages

C# - Net

Uploaded by

shanmugasun489
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
745 views1,006 pages

C# - Net

Uploaded by

shanmugasun489
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 1006

CORE C#

AND .NET
PRENTICE HALL
CORE SERIES

Core J2EE Patterns, Second Edition, Alur/Malks/Crupi


Core PHP Programming,Third Edition, Atkinson/Suraski
Core Lego Mindstorms, Bagnall
Core JSTL, Geary
Core JavaServer Faces, Geary/Horstmann
Core Web Programming, Second Edition, Hall/Brown
Core Servlets and JavaServer Pages, Second Edition,
Hall/Brown
Core Java™ 2, Volume I—Fundamentals,
Horstmann/Cornell
Core Java™ 2, Volume II—Advanced Features,
Horstmann/Cornell
Core C# and .NET, Perry
Core CSS, Second Edition, Schengili-Roberts
Core Security Patterns, Steel/Nagappan/Lai
Core Java Data Objects, Tyagi/Vorburger/
McCammon/Bobzin
Core Web Application Development with PHP and
MySQL, Wandschneider
CORE C#
AND .NET

Stephen C. Perry

Prentice Hall Professional Technical Reference


Upper Saddle River, NJ • Boston • Indianapolis • San Francisco
New York • Toronto • Montreal • London • Munich • Paris • Madrid
Capetown • Sydney • Tokyo • Singapore • Mexico City
Many of the designations used by manufacturers and sellers to distinguish their products are
claimed as trademarks. Where those designations appear in this book, and the publisher was
aware of a trademark claim, the designations have been printed with initial capital letters or in
all capitals.
The author and publisher have taken care in the preparation of this book, but make no
expressed or implied warranty of any kind and assume no responsibility for errors or omissions.
No liability is assumed for incidental or consequential damages in connection with or arising out
of the use of the information or programs contained herein.
The publisher offers excellent discounts on this book when ordered in quantity for bulk
purchases or special sales, which may include electronic versions and/or custom covers and
content particular to your business, training goals, marketing focus, and branding interests. For
more information, please contact:
U. S. Corporate and Government Sales
(800) 382-3419
[email protected]
For sales outside the U. S., please contact:
International Sales
[email protected]
Visit us on the Web: www.phptr.com

Library of Congress Cataloging-in-Publication Data


Perry, Stephen (Stephen C.)
Core C# and .NET / Stephen Perry.
p. cm.
ISBN 0-13-147227-5
1. C++ (Computer program language) 2. Microsoft .NET. I. Title.
QA76.73.C153P468 2005
005.13'3--dc22
2005021301

Copyright © 2006 Pearson Education, Inc.


All rights reserved. Printed in the United States of America. This publication is protected by
copyright, and permission must be obtained from the publisher prior to any prohibited
reproduction, storage in a retrieval system, or transmission in any form or by any means,
electronic, mechanical, photocopying, recording, or likewise. For information regarding
permissions, write to:
Pearson Education, Inc.
Rights and Contracts Department
One Lake Street
Upper Saddle River, NJ 07458
ISBN 0-13-147227-5
Text printed in the United States on recycled paper at R. R. Donnelley in Crawfordsville,
Indiana.
First printing, September 2005
Chapter

ABOUT THE AUTHOR XXIII


FOREWORD XXV
PREFACE XXVII
ACKNOWLEDGMENTS XXX

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

1.4 Working with the .NET Framework and SDK 22


Updating the .NET Framework 23
.NET Framework Tools 23
Ildasm.exe 25
wincv.exe 28
Framework Configuration Tool 29
1.5 Understanding the C# Compiler 31
Locating the Compiler 31
Compiling from the Command Line 32
1.6 Summary 35
1.7 Test Your Understanding 36

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

Abstract, Sealed, and Static Modifiers 86


Class Identifier 86
Base Classes, Interfaces, and Inheritance 87
3.3 Overview of Class Members 88
Member Access Modifiers 89
3.4 Constants, Fields, and Properties 89
Constants 89
Fields 91
Properties 93
Indexers 95
3.5 Methods 97
Method Modifiers 98
Passing Parameters 103
3.6 Constructors 106
Instance Constructor 106
Private Constructor 110
Static Constructor 111
3.7 Delegates and Events 112
Delegates 113
Delegate-Based Event Handling 115
3.8 Operator Overloading 123
3.9 Interfaces 126
Creating and Using a Custom Interface 127
Working with Interfaces 129
3.10 Generics 131
3.11 Structures 134
Defining Structures 134
Using Methods and Properties with a Structure 136
3.12 Structure Versus Class 137
Structures Are Value Types and Classes Are Reference Types 138
Unlike a Class, a Structure Cannot Be Inherited 138
General Rules for Choosing Between a Structure and a Class 139
Contents ix

3.13 Summary 139


3.14 Test Your Understanding 140

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

Working with Groups 240


Examples of Using Regular Expressions 242
5.8 System.IO: Classes to Read and Write Streams of Data 244
The Stream Class 244
FileStreams 245
MemoryStreams 247
BufferedStreams 248
Using StreamReader and StreamWriter
to Read and Write Lines of Text 249
StringWriter and StringReader 251
Encryption with the CryptoStream Class 252
5.9 System.IO: Directories and Files 255
FileSystemInfo 256
Working with Directories Using the DirectoryInfo,
Directory, and Path Classes 256
Working with Files Using the FileInfo and File Classes 261
5.10 Summary 263
5.11 Test Your Understanding 264

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

Forms Interaction—A Sample Application 294


Owner and Owned Forms 298
Message and Dialog Boxes 299
Multiple Document Interface Forms 301
6.4 Working with Menus 306
MenuItem Properties 306
Context Menus 307
6.5 Adding Help to a Form 308
ToolTips 309
Responding to F1 and the Help Button 311
The HelpProvider Component 312
6.6 Forms Inheritance 313
Building and Using a Forms Library 313
Using the Inherited Form 314
6.7 Summary 315
6.8 Test Your Understanding 316

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

Other List Controls: the ComboBox and the CheckedListBox 341


7.5 The ListView and TreeView Classes 342
The ListView Class 342
The TreeView Class 349
7.6 The ProgressBar, Timer, and StatusStrip Classes 355
Building a StatusStrip 355
7.7 Building Custom Controls 358
Extending a Control 358
Building a Custom UserControl 359
A UserControl Example 359
Using the Custom User Control 361
Working with the User Control at Design Time 362
7.8 Using Drag and Drop with Controls 363
Overview of Drag and Drop 363
7.9 Using Resources 369
Working with Resource Files 369
Using Resource Files to Create Localized Forms 373
7.10 Summary 376
7.11 Test Your Understanding 376

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

8.3 Images 407


Loading and Storing Images 408
Manipulating Images 411
Sample Project: Working with Images 414
A Note on GDI and BitBlt for
the Microsoft Windows Platform 421
8.4 Summary 423
8.5 Test Your Understanding 423

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

The Command Object 511


DataReader Object 516
11.4 DataSets, DataTables, and the Disconnected Model 518
The DataSet Class 518
DataTables 519
Loading Data into a DataSet 523
Using the DataAdapter to Update a Database 525
Defining Relationships Between Tables in a DataSet 530
Choosing Between the Connected and Disconnected Model 532
11.5 XML and ADO.NET 533
Using a DataSet to Create XML Data and Schema Files 534
Creating a DataSet Schema from XML 536
Reading XML Data into a DataSet 537
11.6 Summary 540
11.7 Test Your Understanding 541

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

15.3 Security 692


Permissions and Permission Sets 693
Evidence 698
Security Policies 701
Configuring Security Policy 702
The .NET Framework Configuration Tool 704
Configuring Code Access Security with the
Configuration Tool—An Example 706
Requesting Permissions for an Assembly 711
Programmatic Security 715
15.4 Application Deployment Considerations 722
Microsoft Windows Deployment: XCOPY
Deployment Versus the Windows Installer 722
Deploying Assemblies in the Global Assembly Cache 723
Deploying Private Assemblies 724
Using CodeBase Configuration 725
Using a Configuration File to Manage
Multiple Versions of an Assembly 726
Assembly Version and Product Information 727
15.5 Summary 728
15.6 Test Your Understanding 728

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

The Code-Behind Model 749


Code-Behind with Partial Classes 753
Page Class 754
16.2 Web Forms Controls 758
Web Controls Overview 759
Specifying the Appearance of a Web Control 760
Simple Controls 761
List Controls 766
The DataList Control 768
16.3 Data Binding and Data Source Controls 772
Binding to a DataReader 772
Binding to a DataSet 774
DataSource Controls 776
16.4 Validation Controls 784
Using Validation Controls 786
16.5 Master and Content Pages 789
Creating a Master Page 790
Creating a Content Page 791
Accessing the Master Page from a Content Page 792
16.6 Building and Using Custom Web Controls 793
A Custom Control Example 794
Using a Custom Control 796
Control State Management 797
Composite Controls 798
16.7 Selecting a Web Control to Display Data 801
16.8 Summary 802
16.9 Test Your Understanding 803

Chapter 17
THE ASP.NET APPLICATION ENVIRONMENT 806
17.1 HTTP Request and Response Classes 808
HttpRequest Object 808
Contents xxi

HttpResponse Object 813


17.2 ASP.NET and Configuration Files 817
A Look Inside web.config 818
Adding a Custom Configuration Section 824
17.3 ASP.NET Application Security 827
Forms Authentication 827
An Example of Forms Authentication 830
17.4 Maintaining State 835
Application State 837
Session State 838
17.5 Caching 841
Page Output Caching 842
Data Caching 845
17.6 Creating a Web Client with WebRequest and WebResponse 848
WebRequest and WebResponse Classes 848
Web Client Example 848
17.7 HTTP Pipeline 851
Processing a Request in the Pipeline 851
HttpApplication Class 853
HTTP Modules 857
HTTP Handlers 862
17.8 Summary 866
17.9 Test Your Understanding 867

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

Extending the Web Service with the


WebService and WebMethod Attributes 880
18.3 Building an XML Web Service Client 884
Creating a Simple Client to Access the Web Service Class 884
Creating a Proxy with Visual Studio.NET 894
18.4 Understanding WSDL and SOAP 895
Web Services Description Language (WSDL) 895
Simple Object Access Protocol (SOAP) 898
18.5 Using Web Services with Complex Data Types 906
A Web Service to Return Images 907
Using Amazon Web Services 909
Creating a Proxy for the Amazon Web Services 911
Building a WinForms Web Service Client 913
18.6 Web Services Performance 916
Configuring the HTTP Connection 916
Working with Large Amounts of Data 917
18.7 Summary 918
18.8 Test Your Understanding 918

Appendix A
FEATURES SPECIFIC TO .NET 2.0 AND C# 2.0 920

Appendix B
DATAGRIDVIEW EVENTS AND DELEGATES 924

ANSWERS TO CHAPTER EXERCISES 938

INDEX 952
Chapter

Stephen Perry is a software architect specializing in the design and implementation


of .NET applications. For the past three years he has designed and developed signif-
icant .NET-based solutions for clients in the textile, furniture, legal, and medical pro-
fessions. Prior to that, he worked for more than 20 years in all phases of software
development. With the extra 25 hours a week now available from the completion of
the book, he’ll have more time for triathlon training and watching “Seinfeld” reruns.

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

“The process of preparing programs for a digital computer is especially attractive


because it not only can be economically and scientifically rewarding, it can
also be an aesthetic experience much like composing poetry or music.”
— Donald Knuth, Preface to Fundamental Algorithms (1968)

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

ming (OOP)—although it will re-enforce the principles of encapsulation, polymor-


phism, and inheritance through numerous examples. Finally, this book is not an
introduction to using Visual Studio.NET to develop C# programs. VS.NET is men-
tioned, but the emphasis is on developing and understanding C# and the .NET
classes—independent of any IDE.
This book is intended for the experienced programmer who is moving to .NET
and wants to get an overall feel for its capabilities. You may be a VB6 or C++ pro-
grammer seeking exposure to .NET; a VB.NET programmer expanding your reper-
toire into C#; or—and yes it does happen occasionally—a Java programmer
investigating life on the far side. Here’s what you’ll find if you choose to journey
through this book.

• 18 Chapters. The first four chapters should be read in order. They


provide an introduction to C# and a familiarity with using the .NET
class libraries. The remaining chapters can be read selectively based
on your interests. Chapters 6 and 7 describe how to develop Windows
Forms applications. Chapters 8 and 9 deal with GDI+—the .NET
graphics classes. Chapters 10 through 12 are about working with data.
Both XML and ADO.NET are discussed. Chapters 13, 14, and 15
tackle the more advanced topics of threading, remoting, and code
security, respectively. The final chapters form a Web trilogy: Chapter
16 discusses ASP.NET Web page development; Chapter 17 looks
behind the scenes at how to manage state information and manage
HTTP requests; the book closes with a look at Web Services in
Chapter 18.
• .NET 2.0. The manuscript went to publication after the release of
Beta 2.0. As such, it contains information based on that release. The
2.0 topics are integrated within the chapters, rather than placing them
in a special 2.0 section. However, as a convenience, Appendix A con-
tains a summary and separate index to the .NET 2.0 topics.
• Coding examples. Most of the code examples are short segments
that emphasize a single construct or technique. The objective is to
avoid filler code that does nothing but waste paper. Only when it is
essential does a code example flow beyond a page in length. Note that
all significant code examples are available as a download from
www.corecsharp.net or indirectly at www.phptr.com/title/
0131472275. To access the download area, enter the keyword
parsifal.
• Questions and answers. Each chapter ends with a section of ques-
tions to test your knowledge. The answers are available in a single sec-
tion at the end of the book.
Preface xxix

• Fact rather than opinion. This book is not based on my opinion; it is


based on the features inherent in .NET and C#. Core recommenda-
tions and notes are included with the intent of providing insight rather
than opinion.

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#

Topics in This Chapter

• Overview of the .NET Framework: Architecture and features.


• Common Language Runtime: An overview of the tasks performed
by the runtime portion of the .NET Framework: Just-in-Time (JIT)
compiler, loading assemblies, and code verification.
• Common Type System and Common Language Specifications:
Rules that govern Common Language Runtime (CLR)
compatibility and language interoperability.
• Assemblies: A look at the structure of an assembly, the philosophy
behind it, and the difference between private and shared
assemblies.
• Framework Class Library: The Framework Library supplies
hundreds of base classes grouped into logical namespaces.
• Development Tools: Several tools are provided with .NET to aid
code development. These include Ildasm for disassembling code,
WinCV to view the properties of a class, and the Framework
Configuration tool.
• Compiling and Running C# Programs: Using the C# compiler
from the command line and options for structuring an application.
1

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#

1.1 Overview of the .NET Framework


The .NET Framework is designed as an integrated environment for seamlessly
developing and running applications on the Internet, on the desktop as Windows
Forms, and even on mobile devices (with the Compact Framework). Its primary
objectives are as follows:

• To provide a consistent object-oriented environment across the range


of applications.
• To provide an environment that minimizes the versioning conflicts
(“DLL Hell”) that has bedeviled Windows (COM) programmers, and
to simplify the code distribution/installation process.
• To provide a portable environment, based on certified standards, that
can be hosted by any operating system. Already, C# and a major part
of the .NET runtime, the Common Language Infrastructure (CLI),
have been standardized by the ECMA.1
• To provide a managed environment in which code is easily verified for
safe execution.

To achieve these broad objectives, the .NET Framework designers settled on an


architecture that separates the framework into two parts: the Common Language
Runtime (CLR) and the Framework Class Library (FCL). Figure 1-1 provides a styl-
ized representation of this.

FRAMEWORK CLASS LIBRARY


Web Applications
Windows Forms
ASP.NET, Web Services

Data Classes
ADO.NET, XML, SQL
Base Classes
System.IO, System.Drawing, System.Threading

Common Language Runtime


CTS, Just-in-Time Compiler, Memory Management

Operating System

Figure 1-1 .NET Framework

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

The CLR—which is Microsoft’s implementation of the CLI standard—handles


code execution and all of the tasks associated with it: compilation, memory manage-
ment, security, thread management, and enforcement of type safety and use. Code
that runs under the CLR is referred to as managed code. This is to distinguish it from
unmanaged code that does not implement the requirements to run in the CLR—
such as COM or Windows API based components.
The other major component, the Framework Class Library, is a reusable code
library of types (classes, structures, and so on) available to applications running under
.NET. As the figure shows, these include classes for database access, graphics, inter-
operating with unmanaged code, security, and both Web and Windows forms. All
languages that target the .NET Framework use this common class library. Thus, after
you gain experience using these types, you can apply that knowledge to any .NET
language you may choose to program in.

Microsoft .NET and the CLI Standards


A natural concern for a developer that chooses to invest the time in learning C# and
.NET is whether the acquired skill set can be transferred to other platforms. Specifi-
cally, is .NET a Microsoft product tethered only to the Windows operating system?
Or is it a portable runtime and development platform that will be implemented on
multiple operating systems? To answer the question, it is necessary to understand the
relationship among Microsoft .NET, C#, and the Common Language Infrastructure
(CLI) standards.
The CLI defines a platform-independent virtual code execution environment. It
specifies no operating system, so it could just as easily be Linux as Windows. The
centerpiece of the standard is the definition for a Common Intermediate Language
(CIL) that must be produced by CLI compliant compilers and a type system that
defines the data types supported by any compliant language. As described in the next
section, this intermediate code is compiled into the native language of its host oper-
ating system.
The CLI also includes the standards for the C# language, which was developed
and promoted by Microsoft. As such, it is the de facto standard language for .NET.
However, other vendors have quickly adopted the CIL standard and produced—just
to name a few—Python, Pascal, Fortran, Cobol, and Eiffel .NET compilers.
The .NET Framework, as depicted in Figure 1-1, is Microsoft’s implementation of
the CLI standards. The most important thing to note about this implementation is
that it contains a great deal more features than are specified by the CLI architecture.
To illustrate this, compare it to the CLI standards architecture shown in Figure 1-2.
8 Chapter 1 ■ Introduction to .NET and C#

Network XML Reflection


Library Library Library

Runtime Infrastructure
Library

Base Class
Library

Kernel Profile

Compact Profile

Figure 1-2 Architecture defined by CLI specifications

Briefly, the CLI defines two implementations: a minimal implementation known


as a Kernel Profile and a more feature rich Compact Profile. The kernel contains the
types and classes required by a compiler that is CLI compliant. The Base Class
Library holds the basic data type classes, as well as classes that provide simple file
access, define security attributes, and implement one-dimensional arrays. The Com-
pact Profile adds three class libraries: an XML library that defines simple XML pars-
ing, a Network library that provides HTTP support and access to ports, and a
Reflection library that supports reflection (a way for a program to examine itself
through metacode).
This book, which describes the Microsoft implementation, would be considerably
shorter if it described only the CLI recommendations. There would be no chapters
on ADO.NET (database classes), ASP.NET (Web classes), or Windows Forms—and
the XML chapters would be greatly reduced. As you may guess, these libraries
depend on the underlying Windows API for functionality. In addition, .NET permits
a program to invoke the Win32 API using an Interop feature. This means that a
.NET developer has access not only to the Win32 API but also legacy applications
and components (COM).
By keeping this rather wide bridge to Windows, Microsoft’s .NET implementation
becomes more of a transparent than virtual environment—not that there’s anything
wrong with that. It gives developers making the transition to .NET the ability to cre-
ate hybrid applications that combine .NET components with preexisting code. It also
means that the .NET implementation code is not going to be ported to another oper-
ating system.
1.2 Common Language Runtime 9

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.

1.2 Common Language Runtime


The Common Language Runtime manages the entire life cycle of an application: it
locates code, compiles it, loads associated classes, manages its execution, and ensures
automatic memory management. Moreover, it supports cross-language integration to
permit code generated by different languages to interact seamlessly. This section
peers into the inner workings of the Common Language Runtime to see how it
accomplishes this. It is not an in-depth discussion, but is intended to make you com-
fortable with the terminology, appreciate the language-neutral architecture, and
understand what’s actually happening when you create and execute a program.

Pascal VB.NET C# J# C++ Perl

Base Class
IL + Metadata Libraries

Common Language Runtime


Execution Class Loader
Support
Just-in-Time
Security Compiler
Memory
Management Native Code

CPU

Figure 1-3 Common Language Runtime functions

2. http://www.mono-project.com/Main_Page
10 Chapter 1 ■ Introduction to .NET and C#

Compiling .NET Code


Compilers that are compliant with the CLR generate code that is targeted for the
runtime, as opposed to a specific CPU. This code, known variously as Common
Intermediate Language (CIL), Intermediate Language (IL), or Microsoft Intermedi-
ate Language (MSIL), is an assembler-type language that is packaged in an EXE or
DLL file. Note that these are not standard executable files and require that the run-
time’s Just-in-Time (JIT) compiler convert the IL in them to a machine-specific code
when an application actually runs. Because the Common Language Runtime is
responsible for managing this IL, the code is known as managed code.
This intermediate code is one of the keys to meeting the .NET Framework’s for-
mal objective of language compatibility. As Figure 1-3 illustrates, the Common Lan-
guage Runtime neither knows—nor needs to know—which language an application
is created in. Its interaction is with the language-independent IL. Because applica-
tions communicate through their IL, output from one compiler can be integrated
with code produced by a different compiler.
Another .NET goal, platform portability, is addressed by localizing the creation of
machine code in the JIT compiler. This means that IL produced on one platform can
be run on any other platform that has its own framework and a JIT compiler that
emits its own machine code.
In addition to producing IL, compilers that target the CLR must emit metadata
into every code module. The metadata is a set of tables that allows each code module
to be self-descriptive. The tables contain information about the assembly containing
the code, as well as a full description of the code itself. This information includes
what types are available, the name of each type, type members, the scope or visibility
of the type, and any other type features. Metadata has many uses:

• 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

IL and metadata are crucial to providing language interoperability, but its


real-world success hinges on all .NET compilers supporting a common set of data
types and language specifications. For example, two languages cannot be compatible
at the IL level if one language supports a 32-bit signed integer and the other does not.
They may differ syntactically (for example, C# int versus a Visual Basic Integer),
but there must be agreement of what base types each will support.
As discussed earlier, the CLI defines a formal specification, called the Common
Type System (CTS), which is an integral part of the Common Language Runtime. It
describes how types are defined and how they must behave in order to be supported
by the Common Language Runtime.

Common Type System


The CTS provides a base set of data types for each language that runs on the .NET
platform. In addition, it specifies how to declare and create custom types, and how to
manage the lifetime of instances of these types. Figure 1-4 shows how .NET orga-
nizes the Common Type System.

Object

Class Primitives

Interface Structures

Array Enums

Reference Types Value Types

Figure 1-4 Base types defined by Common Type System

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

In .NET, “type” is a generic term that refers to a class, structure,


enumeration, delegate, or interface.

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).

Table 1-1 Selected Common Language Specification Features and Rules

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).

Enumerations The underlying type of an enumeration (enum) must be of the


type Byte, Int16, Int32, or Int64.

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

public class Conversion


{
public double Metric( double inches)
{ return (2.54 * inches); }
public double metric( double miles)
{ return (miles / 0.62); }
}

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

Figure 1-5 Single file assembly

An assembly is created when a .NET compatible compiler converts a file contain-


ing source code into a DLL or EXE file. As shown in Figure 1-5, an assembly con-
tains a manifest, metadata, and the compiler-generated Intermediate Language (IL).
Let’s take a closer look at these:
14 Chapter 1 ■ Introduction to .NET and C#

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.

Metadata. In addition to the manifest tables just described, the C# compiler


produces definition and reference tables. The definition tables provide a com-
plete description of the types contained in the IL. For instance, there are tables
defining types, methods, fields, parameters, and properties. The reference
tables contain information on all references to types and other assemblies. The
JIT compiler relies on these tables to convert the IL to native machine code.

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:

• All managed code, whether it is a stand-alone program, a control, or a


DLL library containing reusable types, is packaged in an assembly. It
is the most atomic unit that can be deployed on a system. When an
application begins, only those assemblies required for initialization
must be present. Other assemblies are loaded on demand. A judicious
developer can take advantage of this to partition an application into
assemblies based on their frequency of use.
• In .NET jargon, an assembly forms a version boundary. The version
field in the manifest applies to all types and resources in the assembly.
Thus, all the files comprising the assembly are treated as a single unit
with the same version. By decoupling the physical package from the
logical, .NET can share a logical attribute among several physical files.
This is the fundamental characteristic that separates an assembly from
a system based on the traditional DLLs.
• An assembly also forms a security boundary on which access permis-
sions are based. C# uses access modifiers to control how types and
type members in an assembly can be accessed. Two of these use the
assembly as a boundary: public permits unrestricted access from any
assembly; internal restricts access to types and members within the
assembly.
1.2 Common Language Runtime 15

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

Figure 1-6 Multi-file assembly

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:

• They allow you to combine modules created in different programming


languages. A programming shop may rely on Visual Basic.NET for its
Rapid Application Development (RAD) and C# for component or
enterprise development. Code from both can coexist and interact in
the .NET assembly.
• Code modules can be partitioned to optimize how code is loaded into
the CLR. Related and frequently used code should be placed in one
module; infrequently used code in another. The CLR does not load
the modules until they are needed. If creating a class library, go a step
further and group components with common life cycle, version, and
security needs into separate assemblies.
• Resource files can be placed in their own module separate from IL
modules. This makes it easier for multiple applications to share
common resources.
16 Chapter 1 ■ Introduction to .NET and C#

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.

Private and Shared Assemblies


Assemblies may be deployed in two ways: privately or globally. Assemblies that are
located in an application’s base directory or a subdirectory are called privately
deployed assemblies. The installation and updating of a private assembly could not be
simpler. It only requires copying the assembly into the directory, called the AppBase,
where the application is located. No registry settings are needed. In addition, an
application configuration file can be added to override settings in an application’s
manifest and permit an assembly’s files to be moved within the AppBase.
A shared assembly is one installed in a global location, called the Global Assembly
Cache (GAC), where it is accessible by multiple applications. The most significant
feature of the GAC is that it permits multiple versions of an assembly to execute
side-by-side. To support this, .NET overcomes the name conflict problem that
plagues DLLs by using four attributes to identify an assembly: the file name, a cul-
ture identity, a version number, and a public key token.

Figure 1-7 Partial listing of Global Assembly Directory

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.

Culture Setting. The contents of an assembly may be associated with a particu-


lar culture or language. This is designated by a two-letter code such as “en” for
English or “fr” for French, and can be assigned with an AssemblyCulture
attribute placed in source code:
[assembly: AssemblyCulture ("fr-CA")]

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

An assembly that is signed with a public/private key is referred to as a


strongly named assembly. All shared assemblies must have a strong
name.

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

As a rule, a dynamically compiled assembly provides performance


equal to, or better than, that of a precompiled executable created using
Ngen.

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

By default, code produced by the C# compiler is verifiably type-safe.


However, there is an unsafe keyword that can be used to relax memory
access restrictions within a C# program (such as referencing beyond an
array boundary).

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.

1.3 Framework Class Library


The Framework Class Library (FCL) is a collection of classes and other types (enu-
merations, structures, and interfaces) that are available to managed code written in
1.3 Framework Class Library 19

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.

Figure 1-8 Output from Ildasm shows the namespaces


and types that comprise an assembly

Figure 1-8 displays a portion of the output generated by using Ildasm.exe to


examine the contents of the mscorlib assembly. Although this only a partial listing,
you can see that mscorlib contains System, the preeminent namespace in .NET,
which serves as a repository for the types that give .NET its basic functionality. The
assembly is also home to the System.Collections namespace, which includes
classes and interfaces used for manipulating collections of data.
20 Chapter 1 ■ Introduction to .NET and C#

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.

Table 1-2 Selected FCL Namespaces

Namespace Use Chapter

System Contains the basic data types used 3, 18


by all applications. It also contains
exception classes, predefined
attributes, a Math library, and
classes for managing the applica-
tion environment.

System.Collections Interfaces and classes used to man- 4


System.Collections.Specialized age collections of objects. These
System.Collections.Generic collections include the ArrayList,
Hashtable, and Stack.

System.Data Classes used for database opera- 11, 12


System.Data.OracleClient tions (ADO.NET). The client
System.Data.SqlClient namespaces support Oracle and
System.Data.OleDb SQL Server, respectively; OledDb
System.Data.Odbc
and Odbc define the data connec-
tion used.

System.Diagnostics Contains classes that can be used to 13


trace program execution, debug,
and work with system logs and per-
formance counters.

System.Drawing Provides graphics functionality for 8, 9


System.Drawing.Drawing2D GDI+. These namespaces contain a
System.Drawing.Printing class used for drawing as well as
System.Drawing.Text pens, brushes, geometric shapes,
and fonts.

System.Globalization Contains classes that define 5


culture-related information that
affects the way dates, currency,
and symbols are represented.

System.IO Provides file and data stream I/O. 5


These classes provide a way to
access the underlying file systems
of the host operating system.
1.3 Framework Class Library 21

Table 1-2 Selected FCL Namespaces (continued)

Namespace Use Chapter

System.Net Classes that support network proto- 17


cols and operations. Examples
include WebRequest and Web-
Response that request and fetch a
Web page.

System.Reflection Contains types that permit the 7, 15,


System.Reflection.Emit runtime inspection of metadata. App. B
The Emit namespace allows a com-
piler or tool to generate metadata
and IL dynamically.

System.Runtime.InterOpServices Provides interoperability between 8


managed and unmanaged code
such as legacy DLLs or COM.

System.Security Classes used to manage .NET secu- 5, 15


System.Security.Permissions rity. Defines classes that control
System.Security.Cryptography access to operations and resources.

System.Text.RegularExpressions Classes that support .NET’s regular 5


expression engine.

System.Threading Manages threading activites: thread 13


System.Threading.Thread creation, synchronization, and
thread pool access.

System.Web The Internet-related classes 16, 17, 18


System.Web.Services referred to as ASP.NET. They man-
System.Web.UI age browser-server communication
System.Web.UI.WebControls requirements, manipulate cookies,
System.Web.Security
and contain the controls that adorn
a Web page.
Web.Services includes those
classes required for SOAP-based
XML messaging.
Web.UI includes classes and inter-
faces used for creating controls and
pages that comprise Web forms.
22 Chapter 1 ■ Introduction to .NET and C#

Table 1-2 Selected FCL Namespaces (continued)

Namespace Use Chapter

System.Windows.Forms Classes used to build Windows 6, 7


desktop GUI applications. Controls
including the ListBox, TextBox,
DataGrid, and buttons are found
here.

System.Xml Types for processing XML. 10

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.

1.4 Working with the .NET


Framework and SDK
The .NET Framework Software Development Kit (SDK) contains the tools, compil-
ers, and documentation required to create software that will run on any machine that
has the .NET Framework installed. It is available as a free download (100 megabytes)
from Microsoft that can be installed on Windows XP, Windows 2000, Windows
Server 2003, and subsequent Windows operating systems. If you have Visual Stu-
dio.NET installed, there is no need to download it because VS.NET automatically
does it for you.
Clients using software developed with the SDK do not require the SDK on their
machine; however, they do require a compatible version of the .NET Framework.
This .NET Framework Redistributable is available as a free download3 (20+ mega-
bytes) and should be distributed to clients along with applications that require it.
This redistributable can be installed on Windows 98 and ME, in addition to the ones
listed for the SDK. With minor exceptions, .NET applications will run identically on
all operating system platforms, because they are targeted for the Common Language
Runtime and not the operating system. There are some system requirements such as
a minimum Internet Explorer version of 5.01. These are listed at the download site.

3. http://msdn.microsoft.com/netframework/downloads/updates/
default.aspx
1.4 Working with the .NET Framework and SDK 23

Updating the .NET Framework


Unlike many development environments, installing a new version of the framework
is almost effortless. The installation process places the updated version in a new
directory having the name of the version. Most importantly, there is no file depen-
dency between the new and older versions. Thus, all versions are functional on your
system. Although it varies by operating system, the versions are usually in the path

\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.

.NET Framework Tools


The .NET Framework automates as many tasks as possible and usually hides the
details from the developer. However, there are times when manual intervention is
required. These may be a need to better understand the details of an assembly or
perform the housekeeping required to prepare an application for deployment. We
have encountered several examples of such tasks throughout the chapter. These
include the need to

• Add a file to an assembly


• View the contents of an assembly
• View the details of a specific class
• Generate a public/private key pair in order to create a strongly named
assembly
• Edit configuration files

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.

Table 1-3 Selected .NET Framework Tools

Tool Description

Al.exe Can be used for creating an assembly composed


Assembly Linker of modules from different compilers. It is also
used to build resource-only (satellite) assemblies.

Fuslogvw.exe Used to troubleshoot the assembly loading pro-


Assembly Binding Log Viewer cess. It traces the steps followed while attempt-
ing to load an assembly.

Gacutil.exe Is used to install or delete an assembly in the


Global Assembly Cache tool Global Assembly Cache. It can also be used for
listing the GAC’s contents.

Ildasm.exe A tool for exploring an assembly, its IL, and


MSIL Disassembler metadata.

Mscorcfg.msc A Microsoft Management Console (MMC)


.NET Framework Configuration tool snap-in used to configure an assembly while
avoiding direct manual changes to an applica-
tion’s configuration file. Designed primarily for
administrators, a subset, Framework Wizards.
Available for individual programmers.

Ngen.exe Compiles an assembly’s IL into native machine


Native Image Generator code. This image is then placed in the native
image cache.

Sn.exe Generates the keys that are used to create a


Strong Name tool strong—or signed—assembly.

wincv.exe A visual interface to display searchable informa-


Windows Forms Class Viewer tion about a class.

Wsdl.exe Generates descriptive information about a Web


Web Services Description Language tool Service that is used by a client to access the
service.
1.4 Working with the .NET Framework and SDK 25

Many of these tools are located in an SDK subdirectory:

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:

1. Right click on the My Computer icon and select Properties.


2. Select Advanced – Environment Variables.
3. Choose the Path variable and add the SDK subdirectory path to it.

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

at a command-line prompt (the optional /adv switch makes advanced viewing


options available). This invokes the GUI that provides a File menu you use to select
the assembly to view. Note that it does not open files in the Global Assembly Cache.
Figure 1-9 shows an example of the output created when an assembly is opened in
Ildasm. The contents are displayed in a readable, hierarchical format that contains
the assembly name, corecsharp1, and all of its members.
This hierarchy can then be used to drill down to the underlying IL (or CIL)
instructions for a specific member. As an example, let’s consider the Conversion
class. The figure shows that it consists of three methods: Metric, conversion, and
metric. The original source code confirms this:

public class Conversion


{
public double Metric( double inches)
{ return (2.54 * inches); }
[CLSCompliantAttribute(false)]
public double metric( double miles)
26 Chapter 1 ■ Introduction to .NET and C#

{ return (miles / 0.62); }


public double conversion( double pounds)
{ return (pounds * 454);}
}

Figure 1-9 View assembly contents with Ildasm.exe

Double clicking on the Metric method brings up a screen that displays its IL
(Figure 1-10).

Figure 1-10 View of the IL


1.4 Working with the .NET Framework and SDK 27

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.

Ildasm and Obfuscation


One of the natural concerns facing .NET developers is how to protect their code
when a tool such as Ildasm—and other commercially available disassemblers—can
be used to expose it. One solution is to use obfuscation—a technique that uses
renaming and code manipulation sleight of hand to make the contents of an assembly
unreadable by humans.
It is important to understand that obfuscation is not encryption. Encryption
requires a decryption step so that the JIT compiler can process the code. Obfusca-
tion transforms the IL code into a form that can be compiled using the tools of your
development environment (see Figure 1-11).

Source
Compile MSIL CLR
Code

Obfuscate Obfuscated CLR


MSIL

Figure 1-11 Obfuscation conceals the original Intermediate Language

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

1. System.Array is the class that is being explored.


2. This class is located in the mscorlib.dll assembly. We have already men-
tioned that this assembly contains the .NET managed types.
3. This list contains the class, object, and interfaces that the Array class
inherits from.
4. The definition of each method in the class is included. This definition,
which includes accessibility, type, and parameters, is called the
method’s signature.

Framework Configuration Tool


This tool provides an easy way to manage and configure assemblies as well as set
security policies for accessing code. This tool is packaged as a Microsoft Manage-
ment Console (MMC) snap-in. To access it, select Administrative Tools from the
Control Panel; then select the Microsoft .NET Framework Configuration tool. This
tool is designed for administrators who need to do the following:

• Manage assemblies. Assemblies can be added to the GAC or


deleted.
• Configure assemblies. When an assembly is updated, the publisher
of the assembly is responsible for updating the binding policy of the
assembly. This policy tells the CLR which version of an assembly to
load when an application references an assembly. For example, if
assembly version 1.1 replaces 1.0, the policy redirects version 1.0 to
1.1 so that it is loaded. This redirection information is contained in a
configuration file.
• View .NET Framework security and modify an assembly’s
security. .NET security allows an assembly to be assigned certain
permissions or rights. In addition, an assembly can require that other
assemblies accessing it have certain permissions.
• Manage how individual applications interact with an assembly
or set of assemblies. You can view a list of all assemblies an
application uses and set the version that your application uses.

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.

Figure 1-13 Using application configuration tool to select assembly version

This configuration tool is clearly targeted for administrators. Individual develop-


ers should rely on a subset of this tool that is packaged as three wizards: Adjust .NET
Security, Trust An Assembly, and Fix An Application. Access these by selecting
Framework Wizards from Administrative Tools.
1.5 Understanding the C# Compiler 31

1.5 Understanding the C# Compiler


Many developers writing nontrivial .NET applications rely on Visual Studio or some
other Integrated Development Environment (IDE) to enter source code, link exter-
nal assemblies, perform debugging, and create the final compiled output. If you fall
into this category, it is not essential that you understand how to use the .NET SDK
and raw C# compiler; however, it will increase your understanding of the .NET com-
pilation process and give you a better feel for working with assemblies. As a byprod-
uct, it will also acquaint you with the command line as a way to work with SDK
programs. Many of the utilities presented in the previous section are invoked from
the command line, and you will occasionally find it useful to perform compilation in
that environment rather than firing up your IDE.

Visual Studio
IL + Metacode
SRC1
Third-Party IDE
SharpDevelop CSC.EXE APP.EXE
SRC2
Text Editor .cs
External
LIB.DLL Assembly

Figure 1-14 Compilation process

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.

Locating the Compiler


The C# compiler, csc.exe, is located in the path where the .NET Framework is
installed:

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

Compiling from the Command Line


To compile the C# console application client.cs into the executable client.exe,
enter either of the following statements at the command prompt:

C:\> csc client.cs


C:\> csc /t:exe client.cs

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.

Table 1-4 Selected Options for the C# Command-Line Compiler

Option Description

/addmodule Specifies a module that is to be included in the assembly created.


This is an easy way to create a multi-file assembly.

/debug Causes debug information to be produced.

/define Preprocessor directive can be passed to compiler:


/define:DEBUG.

/delaysign Builds an assembly using delayed signing of the strong name. This
is discussed in Chapter 15.

/doc Used to specify that an output file containing XML documenta-


tion is to be produced.

/keyfile Specifies the path to the .snk file containing the key pair used for
strong signing (see Chapter 15).

/lib Specifies where assemblies included in the /reference option


are located.

/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

Table 1-4 Selected Options for the C# Command-Line Compiler (continued)

Option Description

/reference (/r) References an external assembly.

/resource Used to embed resource files into the assembly that is created.

/target (/t) Specifies the type of output file created:


/t:exe builds a *.exe console application. This is the default
output.
/t:library builds a *.dll assembly.
/t:module builds a module (Portable Executable file) that
does not contain a manifest.
/t:winexe builds a *.exe Windows Forms assembly.

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#

Example 1: Compiling Multiple Files


The C# compiler accepts any number of input source files. It combines their output
into a single file assembly:

csc /out:client.exe client.cs clientlib.cs

Example 2: Creating and Using a Code Library


The code in clientlib can be placed in a separate library that can be accessed by
any client:

csc /t:library clientlib.cs

The output is an assembly named clientlib.dll. Now, compile the client code
and reference this external assembly:

csc /r:clientlib.dll client.cs

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.

Example 3: Creating an Assembly with Multiple Files


Rather than existing as a separate assembly, clientlib can also be packaged as a
separate file inside the client.exe assembly. Because only one file in an assembly
may contain a manifest, it is first necessary to complile clientlib.cs into a Porta-
ble Executable4 (PE) module. This is done by selecting module as the target output:

csc /t:module clientlib.cs

The output file is clientfile.netmodule. Now, it can be placed in the cli-


ent.exe assembly by using the compiler’s addmodule switch:

csc /addmodule:clientlib.netmodule client.cs

The resultant assembly consists of two files: client.exe and clientlib.net-


module.

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

Manifest Manifest Manifest Manifest

Metadata Metadata Metadata Metadata Metadata

IL
IL IL IL IL
clientlib.
client.exe client.exe clientlib.dll client.exe netmodule

Figure 1-15 Options for deploying an application

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#

1.7 Test Your Understanding


1. What portable environment must be installed on a client’s machine to
enable it to run a .NET application?

2. What is managed code? What is unmanaged code?

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?

5. What is the role of the Global Assembly Cache?

6. What four components make up the identity of a strongly named


assembly?

7. What is the relationship between a namespace and an assembly?

8. Describe what these commonly used acronyms stand for: CLR, GAC,
FCL, IL.
This page intentionally left blank
C# LANGUAGE
FUNDAMENTALS

Topics in This Chapter

• Overview of a C# Program: In addition to the basic elements that


comprise a C# program, a developer needs to be aware of other
.NET features such as commenting options and recommended
naming conventions.
• Primitives: Primitives are the basic data types defined by the FCL
to represent numbers, characters, and dates.
• Operators: C# uses traditional operator syntax to perform
arithmetic and conditional operations.
• Program Flow Statements: Program flow can be controlled using
if and switch statements for selection; and while, do, for,
and foreach clauses for iteration.
• String: The string class supports the expected string operations:
concatenation, extracting substrings, searching for instances of a
character pattern, and both case sensitive and insensitive
comparisons.
• Enums: An enumeration is a convenient way to assign
descriptions that can be used to reference an underlying set of
values.
• Using Arrays: Single- or multi-dimensional arrays of any type can
be created in C#. After an array is created, the System.Array
class can be used to sort and copy the array.
• Reference and Value Types: All types in .NET are either a value or
reference type. It is important to understand the differences and
how they can affect a program’s performance.
2

In September 2000, an ECMA1 (international standardization group for information


and communication systems) task group was established to define a Microsoft pro-
posed standard for the C# programming language. Its stated design goal was to pro-
duce “a simple, modern, general-purpose, object-oriented programming language.”
The result, defined in a standard known as ECMA-334, is a satisfyingly clean lan-
guage with a syntax that resembles Java, and clearly borrows from C++ and C. It’s a
language designed to promote software robustness with array bounds checking,
strong type checking, and the prohibition of uninitialized variables.
This chapter introduces you to the fundamentals of the language: It illustrates the
basic parts of a C# program; compares value and reference types; and describes the
syntax for operators and statements used for looping and controlling program flow.
As an experienced programmer, this should be familiar terrain through which you
can move quickly. However, the section on value and reference types may demand a
bit more attention. Understanding the differences in how .NET handles value and
reference types can influence program design choices.

1. ECMA International was formerly known as European Computer Manufacturers Association


and is referred to herein simply as ECMA.

39
40 Chapter 2 ■ C# Language Fundamentals

2.1 The Layout of a C# Program


Figure 2-1 illustrates some of the basic features of a C# program.

// (1) using simplifies references to namespaces


using using System;
// (2) A Class Declaration
class Apparel
Class {
///
public double Price = 250.0;
///
public string FabType = "Synthetic";
}
XML /// <remarks> Entry point to program </remarks>
Comment public class MyApp
{
// (3) Main() is required in each C# program
static void Main()
{
Main() Apparel myApparel = new Apparel();
string myType = myApparel.FabType;
Console.WriteLine(myApparel.Price, myType);
}
}

Figure 2-1 Basic elements of a C# program

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:

1. The using statement specifies the namespace System. Recall


from Chapter 1, “Introduction to .NET and C#,” that the .NET class
libraries are organized into namespaces and that the System namespace
contains all of the simple data types. The using statement tells the com-
piler to search this namespace when resolving references, making it
unnecessary to use fully qualified names. For example, you can refer to
label rather than System.Web.UI.WebControls.Label.
2. All programming logic and data must be contained within a
type definition. All program logic and data must be embedded in a
class, structure, enum, interface, or delegate. Unlike Visual Basic, for
2.1 The Layout of a C# Program 41

instance, C# has no global variable that exists outside the scope of a


type. Access to types and type members is strictly controlled by access
modifiers. In this example, the access modifier public permits exter-
nal classes—such as MyApp—to access the two members of the
Apparel class.
3. A Main() method is required for every executable C#
application. This method serves as the entry point to the application;
it must always have the static modifier and the M must be capital-
ized. Overloaded forms of Main()define a return type and accept a
parameter list as input.

Return an integer value:

static int Main()


{
return 0; // must return an integer value
}

Receive a list of command-line arguments as a parameter and return


an integer value:

static int Main(string[] args)


{
// loop through arguments
foreach(string myArg in args)
Console.WriteLine(myArg);
return 0;
}

The parameter is a string array containing the contents of the com-


mand line used to invoke the program. For example, this command
line executes the program MyApparel and passes it two parameter
values:

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

General C# Programming Notes


Case Sensitivity
All variable and keywords are distinguished by case sensitivity. Replace class with
Class in Figure 2-1 and the code will not compile.

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:

1. Pascal. The first character of each word is capitalized (for example,


MyClassAdder).
2. Camel. The first character of each word, except the first, is capital-
ized (for example, myClassAdder).

Table 2-1 C# Naming Conventions

Type Case Notes and Examples

Class Pascal • Use noun or noun phrases.


• Try to avoid starting with I because this is reserved for
interfaces.
• Do not use underscores.

Constant Pascal public const double GramToPound = 454.0 ;

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.

Exception Pascal • Has suffix Exception.

Interface Pascal • Has prefix of I.


IDisposable

Local Variable Camel • Variables with public access modifier use Pascal.
int myIndex
2.1 The Layout of a C# Program 43

Table 2-1 C# Naming Conventions (continued)

Type Case Notes and Examples

Method Pascal • Use verb or verb phrases for name.

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

Property Pascal • Use noun or noun phrase.

Parameter Camel • Use meaningful names that describe the parameter’s


purpose.

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:

// for a single line


/* for one or more lines
*/
/// <remarks> XML comment describing a class </remarks>

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

Table 2-2 XML Documentation Tags

Tag Description

<example> Text illustrating an example of using a particular program


feature goes between the beginning and ending tags.

<exception cref="Excep"> cref attribute contains name of exception.


/// <exception cref="NoParmException">
</exception>

<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.

<param name="parm1"> name attribute contains the name of the parameter.

<permission cref= ""> Most of the time this is set to the following:
///<permission cref="System.Security.Permis-
sionSet"> </permission>

<remarks> Provides additional information about a type not found in


the <summary> section.

<returns> Place a textual description of what is returned from a


method or property between the beginning and ending
tags.

<seealso cref="price"> The cref attribute is set to the name of an associated


type, field, method, or other type member.

<summary> Contains a class description; is used by IntelliSense in


VisualStudio.NET.

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:

C:\> csc consoleapp.cs /doc:consoleXML.xml

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

To suppress this, add the /nowarn:1591 option to the compile-line command.


The option accepts multiple warning codes separated with a comma.

Core Note

Many documentation tools are available to transform and extend the C#


XML documentation output. One of the most advanced is NDoc
(ndoc.sourceforge.net), an open source tool that not only formats
the XML but uses reflection to glean further information about an
assembly.

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.

Table 2-3 C# Primitive Data Types

C# Primitive Type FCL Data Type Description

object System.Object Ultimate base type of all other types.

string System.String A sequence of Unicode characters.

decimal System.Decimal Precise decimal with 28 significant digits.

bool System.Boolean A value represented as true or false.

char System.Char A 16-bit Unicode character.

byte System.Byte 8-bit unsigned integral type.

sbyte System.SByte 8-bit signed integral type.

short System.Int16 16-bit signed integral type.

int System.Int32 32-bit signed integral type.


46 Chapter 2 ■ C# Language Fundamentals

Table 2-3 C# Primitive Data Types (continued)

C# Primitive Type FCL Data Type Description

long System.Int64 64-bit signed integral type.

ushort System.UInt16 16-bit unsigned integral type.

uint System.UInt32 32-bit unsigned integral type.

ulong System.UIint64 64-bit unsigned integral type.

single (float) System.Single Single-precision floating-point type.

double System.Double Double-precision floating-point type.

As the table shows, primitives map directly to types in the base class library and
can be used interchangeably. Consider these statements:

System.Int32 age = new System.Int32(17);


int age = 17;
System.Int32 age = 17;

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:

int iMax = int.MaxValue; // Return largest integer


int pVal = int.Parse("100"); // converts string to int

The C# compiler supports implicit conversions if the conversion is a


“safe” conversion that results in no loss of data. This occurs when the
target of the conversion has a greater precision than the object being
converted, and is called a widening conversion. In the case of a
narrowing conversion, where the target has less precision, the
conversion must have explicit casting. Casting is used to coerce, or
convert, a value of one type into that of another. This is done
2.2 Primitives 47

syntactically by placing the target data type in parentheses in front of


the value being converted: int i = (int)y;.

short i16 = 50; // 16-bit integer


int i32 = i16; // Okay: int has greater precision
i16 = i32; // Fails: short is 16 bit, int is 32
i16 = (short) i32; // Okay since casting used

• Literal values assigned to the types float, double, and decimal


require that their value include a trailing letter: float requires F or f;
double has an optional D or d; and decimal requires M or m.

decimal pct = .15M; // M is required for literal 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.

decimal iRate = 3.9834M; // decimal requires M


iRate = decimal.Round(iRate,2); // Returns 3.98
decimal dividend = 512.0M;
decimal divisor = 51.0M;
decimal p = decimal.Parse("100.05");
// Next statement returns remainder = 2
decimal rem = decimal.Remainder(dividend,divisor);

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:

myChar = 'B'; // 'B' has an ASCII value of 66


myChar = (char) 66; // Equivalent to 'B'
myChar = '\u0042'; // Unicode escape sequence
myChar = '\x0042'; // Hex escape sequence
myChar = '\t'; // Simple esc sequence:horizontal tab
bool bt;
string pattern = "123abcd?";
myChar = pattern[0]; // '1'
bt = char.IsLetter(pattern,3); // true ('a')
bt = char.IsNumber(pattern,3); // false
bt = char.IsLower(pattern,0); // false ('1')
bt = char.IsPunctuation(pattern,7); // true ('?')
bt = char.IsLetterOrDigit(pattern,1); // true
bt = char.IsNumber(pattern,2); // true ('3')
string kstr="K";
char k = char.Parse(kstr);

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.

byte[] b = {0x00, 0x12, 0x34, 0x56, 0xAA, 0x55, 0xFF};


string s = b[4].ToString(); // returns 170
char myChar = (char) b[3];

short, int, long


These represent 16-, 32-, and 64-bit signed integer values, respectively. The
unsigned versions are also available (ushort, uint, ulong).

short i16 = 200;


i16 = 0xC8 ; // hex value for 200
int i32 = i16; // no casting required
2.2 Primitives 49

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.

float xFloat = 24567.66F;


int xInt = Convert.ToInt32(xFloat); // returns 24567
int xInt2 = (int) xFloat;
if(xInt == xInt2) { } // False
string xStr = Convert.ToString(xFloat);
single zero = 0;
if (Single.IsNaN(0 / zero)) { } // True
double xDouble = 124.56D;

Note that the F suffix is used when assigning a literal value to a single type, and
D is optional for a double type.

Using Parse and TryParse to


Convert a Numeric String
The primitive numeric types include Parse and TryParse methods that are used to
convert a string of numbers to the specified numeric type. This code illustrates:

short shParse = Int16.Parse("100");


int iParse = Int32.Parse("100");
long lparse = Int64.Parse("100");
decimal dParse = decimal.Parse("99.99");
float sParse = float.Parse("99.99");
double dbParse = double.Parse("99.99");

TryParse, introduced in .NET 2.0, provides conditional parsing. It returns a


boolean value indicating whether the parse is successful, which provides a way to
50 Chapter 2 ■ C# Language Fundamentals

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.

2.3 Operators: Arithmetic, Logical,


and Conditional
The C# operators used for arithmetic operations, bit manipulation, and conditional
program flow should be familiar to all programmers. This section presents an over-
view of these operators that is meant to serve as a syntactical reference.

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.

Table 2-4 Numerical Operators

Operator Description Example

+ (3) Addition int x = y + 10;


- Subtraction

* (2) Multiplication int x = 60;


/ Division, int y = 15;
% Modulo int z = x * y / 2; // 450
y = x % 29 ; // remainder is 2

++ (1) Prefix/postfix x = 5;
-- Increment/decrement Console.WriteLine(x++) // x = 5
Console.WriteLine(++x) // x = 6

~ (1) Bitwise complement int x = ~127; // returns -128


2.3 Operators: Arithmetic, Logical, and Conditional 51

Table 2-4 Numerical Operators (continued)

Operator Description Example

>> (4) Shift right byte x = 10; // binary 10 is 01010


<< Shift left int result = x << 1; // 20 = 10100
result = x >> 2; // 5 = 00101
Works with byte, char, short, int, and
long

& (5-6-7) Bitwise AND byte x = 12; // 001100


| Bitwise OR byte y = 11; // 001011
^ Bitwise XOR int result = x & y; //8 = 001000
result = x ^ y; //7 = 000111

Core Note

C# does not provide an exponentiation operator. Instead, use the


Math.Pow() method to raise a number to a power, and Math.Exp() to
raise e to a power.

Conditional and Relational Operators


Relational operators are used to compare two values and determine their relation-
ship. They are generally used in conjunction with conditional operators to form more
complex decision constructs. Table 2-5 provides a summary of C# relational and con-
ditional operators.

Table 2-5 Relational and Conditional Boolean Operators

Statement Description Example

== Equality if (x == y) {...}
!= Inequality

< Numeric less than if (x <= y) {...}


<= Less than or equal to
> Greater than
>= Greater than or equal to

&& Logical AND if (x == y && y < 30) {...}


|| Logical OR If first expression is false, second is not evaluated
52 Chapter 2 ■ C# Language Fundamentals

Table 2-5 Relational and Conditional Boolean Operators (continued)

Statement Description Example

& Logical AND if (x== y | y < 30) {...}


| Logical OR Always evaluates second expression

! Logical negation if !(x ==y && y < 30) {...}

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.

Control Flow Statements


The C# language provides if and switch conditional constructs that should be
quite familiar to C++ and Java programmers. Table 2-6 provides a summary of these
statements.

Table 2-6 Control Flow Statements

Conditional Statement Example

if (boolean expression) { if (bmi < 24.9) {


// statements weight = "normal";
} else { riskFactor = 2;
// statements } else {
} weight = "over";
riskFactor=6;
}
2.3 Operators: Arithmetic, Logical, and Conditional 53

Table 2-6 Control Flow Statements (continued)

Conditional Statement Example

switch (expression) switch (ndx)


{ {
case constant expression: case 1:
// statements; fabric = "cotton";
// break/goto/return() blend = "100%";
case constant expression: break;
// statements; case 2: // combine 2 & 3
// break/goto/return() case 3:
default: fabric = "cotton";
// statements; blend = "60%";
// break/goto/return() break;
} default: // optional
fabric = "cotton";
• Constant expression may be an integer, enum blend = "50%";
value, or string. break;
• No “fall through” is permitted. Each case }
block must end with a statement that transfers
control.

if-else
Syntax:

if ( boolean expression ) statement


if ( boolean expression ) statement1 else statement2

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:

switch( expression ) {switch block}

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:

// switch with string expression


using System;
public class MyApp
{
static void Main(String[] args)
{
switch (args[0])
{
case "COTTON": // is case sensitive
case "cotton":
Console.WriteLine("A good natural fiber.");
goto case "natural";
case "polyester":
Console.WriteLine("A no-iron synthetic fiber.");
break;
case "natural":
Console.WriteLine("A Natural Fiber. ");
break;
default:
Console.WriteLine("Fiber is unknown.");
break;
}
}
}
2.4 Loops 55

The most important things to observe in this example are as follows:

• 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:

while ( boolean expression ) { body }

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:

byte[] r = {0x00, 0x12, 0x34, 0x56, 0xAA, 0x55, 0xFF};


int ndx=0;
int totVal = 0;
while (ndx <=6)
{
totVal += r[ndx];
ndx += 1;
}
56 Chapter 2 ■ C# Language Fundamentals

do loop
Syntax:

do { do-body } while ( boolean expression );

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:

byte[] r = {0x00, 0x12, 0x34, 0x56, 0xAA, 0x55, 0xFF};


int ndx=0;
int totVal = 0;
do
{
totVal += r[ndx];
ndx += 1;
}
while (ndx <= 6);

for loop
Syntax:

for ( [initialization]; [termination condition]; [iteration] )


{ for-body }

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:

int[] r = {80, 88, 90, 72, 68, 94, 83};


int totVal = 0;
for (int ndx = 0; ndx <= 6; ndx++) {
totVal += r[ndx];
}
2.4 Loops 57

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:

for (ndx = 0; ndx < 6; )


{
totVal += r[ndx];
ndx++; // increment here
}

You can also leave out all of the for clauses:

for (;;) { body } // equivalent to while(true) { body }

A return, goto, or break statement is required to exit this loop.

foreach loop
Syntax:

foreach ( type identifier in collection ) { body }

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;
}

In a one-dimensional array, iteration begins with index 0 and moves in ascending


order. In a multi-dimensional array, iteration occurs through the rightmost index
first. For example, in a two-dimensional array, iteration begins in the first column
and moves across the row. When it reaches the end, it moves to the next row of the
first column and iterates that row.
58 Chapter 2 ■ C# Language Fundamentals

Transferring Control Within a Loop


It is often necessary to terminate a loop, or redirect the flow of statements within the
loop body, based on conditions that arise during an iteration. For example, a while
(true) loop obviously requires that the loop termination logic exists in the body.
Table 2-7 summarizes the principal statements used to redirect the program flow.

Table 2-7 Statements to Exit a Loop or Redirect the Iteration

Statement Description Example

break Redirects program while (true) {


control to the end ndx+=1;
point of a containing if (ndx >10) break;
loop construct. }

continue Starts a new iteration while (ndx <10) {


of enclosing loop ndx +=1;
without executing if(ndx %2 =1) continue;
remaining state- totVal += ndx;
}
ments in loop.

goto Directs program con- public int FindMatch(string myColor)


identifier; trol to a label, a case {
statement within a string[] colorsAvail("blueaqua",
goto case exp; switch block, or the "red", "green","navyblue");
int loc;
default statement
goto default; int matches=0;
within a switch foreach (colorType in colorsAvail)
block. {
loc = colortype.IndexOf(myColor);
The goto may not if (loc >=0) goto Found;
transfer control into a continue;
nested scope—for Found:
example, a loop. matches += 1;
}
return(matches);
}

return Returns program public double Area(double w, double l)


[expression] ; control to the method {
that called the cur- return w * l;
rent method. Returns }
no argument if the
enclosing method has
a void return type.
2.5 C# Preprocessing Directives 59

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.

2.5 C# Preprocessing Directives


Preprocessing directives are statements read by the C# compiler during its lexical
analysis phase. They can instruct the compiler to include/exclude code or even abort
compilation based on the value of preprocessing directives.
A preprocessor directive is identified by the # character that must be the first non-
blank character in the line. Blank spaces are permitted before and after the # symbol.
Table 2-8 lists the directives that C# recognizes.

Table 2-8 Preprocessing Directives

C# Preprocessing
Symbol Description

#define Used to define and undefine a symbol. Defining a symbol makes it


#undef evaluate to true when used in a #if directive.

#if Analogues to the C# if, else if, and else statements.


#elif
#else
#endif

#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.

#error #error causes the compiler to report a fatal error.


#warning #warning causes the compiler to report a warning and continue
processing.

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:

csc /Define:DEBUG myproject.cs

Compiling code with this statement is equivalent to including a #Define DEBUG


statement in the source code.

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.

Table 2-9 String Escape Characters

Escape Character Description

\' Inserts a single quote into a string

\" Inserts a double quote

\\ Inserts a backslash; useful for file paths

\a System alert

\b Backspace

\f Form feed

\n Inserts a new line

\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:

string myQuote, path;


myQuote = @"The solution is in the problem.";
myQuote = "The solution\nis in the problem.";
myQuote = "The Unicode representation of f is \u0066";
// The next two statements assign the same value to myQuote.
myQuote = @"""The solution is in the problem. """;
myQuote = "\"The solution is in the problem. "";
// The next two statements assign the same value to path.
path = @"c:\my documents\notes.txt";
path = "c:\\my documents\\notes.txt";
path = "c:\my documents\notes.txt"; // Fails
2.6 Strings 63

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.

Indexing Individual Characters in a String


The foreach and while loops offer the easiest way to iterate through the characters
in a string. In both cases, the operations are read-only.

// Example 1 - using foreach statement


string myQuote = "The solution is in the problem.";
foreach (char cc in myQuote)
{
Console.Write(cc.ToString());
}

// Example 2 - using while loop


int ndx = 0;
while (ndx < myQuote.Length)
{
Console.Write(myQuote[ndx].ToString());
ndx += 1;
}

Note that before an individual character can be displayed or assigned to a string, it


must be converted to a string type.

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.

string s1 = "My age = ";


int myAge = 28;
string cat = s1 + myAge; // My age = 28
MyClass clStr = new MyClass;
Cat = "Class Name = " + clStr; // Class Name = MyClass
64 Chapter 2 ■ C# Language Fundamentals

The concatenation operation is simple to use, but it is important to understand


what is going on behind the scenes: During concatenation, the strings being joined
are copied and a new combined string is allocated space. Each concatenation results
in the allocation of more memory equal to the length of the new string. This is an
acceptable use of resources as long as the number of concatenations is minimal.
However, if concatenation occurs inside a long loop, an application’s performance
can suffer.
Consider an example where an HTML document is constructed by inserting the
<br> tag between names in a list.

// assume names is an array containing 1000 names


string nameList = "";
foreach (string custName in names)
{
// This is inefficient and should be avoided.
nameList = nameList + custName+"<br>";
}

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.

Extracting and Locating Substrings


The Substring method extracts selected portions of a string. Its two overloads are
illustrated here:

string poem = "In Xanadu did Kubla Khan";


string poemSeg;
poemSeg = poem.Substring(10); // did Kubla Khan
// second argument specifies length
poemSeg = poem.Substring(0,9); // In Xanadu

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

Listing 2-1 Locating Text Occurrences in a String


// Method to count the occurrences of text in a given string
public static int CharCount(String strSource,String strToFind)
{
int iCount=0; // string type has index of 0
int iPos=strSource.IndexOf(strToFind);
while(iPos!=-1)
{
iCount++;
iPos=strSource.IndexOf(strToFind, iPos+1);
}
return iCount;
}
public class MyApp
{
static void Main()
{
string txt = "In Xanadu did Kubla Khan";
int ct = CharCount(txt, "a"); // ct = 4
}
}

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

isMatch = (title.ToUpper() == "ANCIENT MARINER"); // true


isMatch = (title == "Ancient"+" Mariner"); // true
isMatch = title.Equals("Ancient Mariner"); // true

Note that the == operator is just a syntactical shortcut for calling the Equals
method; it is actually faster to call Equals()directly.

2.7 Enumerated Types


An enumerated type, or enum as it’s called in C#, offers a convenient way to create a
structured set of symbols to represent constant values.

Syntax:

[access modifiers]enum <identifier> [:enum-base]{enum body}

Example:

enum Fabric :short {


Cotton = 1,
Silk = 2,
Wool = 4,
Rayon = 8,
Other = 128
}

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.

Working with Enumerations


Enumerated types not only improve program readability, but also minimize code
changes when the underlying value changes. In such cases, all references to the value
2.7 Enumerated Types 67

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.

Listing 2-2 Using an Enumerated Type


static double GetPrice(Fabric fab)
{
switch(fab)
{
case Fabric.Cotton: return(3.55);
case Fabric.Silk: return(5.65);
case Fabric.Wool: return(4.05);
case Fabric.Rayon: return(3.20);
case Fabric.Other: return(2.50);
default: return(0.0);
}
}
static void Main()
{
Fabric fab = Fabric.Cotton;
int fabNum = (int) fab; // 1
string fabType = fab.ToString(); // "Cotton"
string fabVal = fab.ToString("D"); // "1"
double cost = GetPrice(fab); // 3.55
}

Things to note:

• Casting is required to set the value of an enum to an integer variable:


fabNum = (int) fab;
• The character value of the underlying constant value can be obtained
using the ToString() method with the parameter "D". "D" is a
format character that converts a value to its decimal form.
• Passing an instance of the Fabric enum to GetPrice requires that
the corresponding parameter in the GetPrice method is declared as
the same type.
68 Chapter 2 ■ C# Language Fundamentals

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.

string fabStr = "Cotton";


// Determine if symbol Cotton exists in Fabric enum
if (Enum.IsDefined(typeof(Fabric),fabStr))
{
// Create enum instance
Fabric fab = (Fabric)Enum.Parse(
typeof(Fabric) , fabStr);
// Output from the following statement is: "Silk"
Console.WriteLine("Second value of Fabric Enum is: " +
Enum.GetName(typeof(Fabric), 2));
}

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

Enums and Bit Flags


It was not by accident that the values of the Fabric enum were set to powers of 2.
Enum members are often used in logical operations where such values have obvious
advantages in mapping to unique bit values. You may have code to identify a combi-
nation of values:

Fabric cotWool = Fabric.Cotton | Fabric.Wool;


Console.WriteLine(cotWool.ToString()); // Output: 5

It would be more meaningful if the output identified the variable as a combination


of wool and cotton. This can be accomplished by adding the [Flags] attribute to the
enum declaration:

[Flags]
enum Fabric :short {

The ToString() method checks an enum declaration to see if this attribute is


present. If so, it treats the enum as a set of bitmapped flag elements. In this example,
it cannot find a symbolic value equal to 5, so it uses the bit pattern “101” and prints
the symbols having the bit patterns “001” and “100”. The new output is a comma-
delimited list: “Cotton, Wool”.

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:

• Each element in the array is of the type specified in the array


declaration. Besides the usual primitive types, an array may contain
structures, objects, enums, and even other arrays.
70 Chapter 2 ■ C# Language Fundamentals

• Common Language Specification (CLS) compliance requires that all


arrays be zero-based to ensure language interoperability (for example,
an array reference in C# can be passed to code written in VB.NET).
Although it is possible to create a non-zero–based array in C#, it is
discouraged because array operations are optimized for zero-based
dimensions.
• When an array is created, it is allocated space for a fixed number of
elements. Its capacity cannot be dynamically increased. The
ArrayList/List is usually a better choice when the number of
elements the array must hold is unknown.

Declaring and Creating an Array


Syntax:

<type> identifier [ ] = new <type> [n] [{ initializer list}]

Example:

int[] myRating; // declares an array


myRating = new int[5]; // creates array and allocates memory
int[] myRating = new int[5] {3,4,7,2,8};
int[] myRating = {3,4,7,2,8}; // shorthand version

// Create array containing instances of an Apparel class.


Apparel myApparel = {new Apparel(), new Apparel(),
new Apparel());

// Set to an enum
Fabric[] enumArray = new Fabric[2];
enumArray[0] = Fabric.Cotton;

// Create a 2-dimensional array with 3 rows and 2 columns


int[ , ] myRatings = {{3 , 7}, {4 , 9}, {2, 6}};

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

Using System.Array Methods and Properties


The System.Array class defines a number of properties and methods that are used
to query an array and manipulate its contents. Array operations include sorting, copy-
ing, searching for specific values, and clearing the contents. Table 2-10 summarizes
some of the more useful members of the System.Array class.

Table 2-10 Selected Members of System.Array

Member Type Description

Length Instance Total number of elements in the array.


property

Rank Instance Number of dimensions of the array.


property

CreateInstance Static Creates an Array object.


method To create a single-dimensional array:
int[] rank1 =
(int[]) Array.CreateInstance(typeof(int),4);

GetUpperBound(n) Instance The upper bound of a specified dimension n. Returned value is


method Length – 1.
d0 = myArray.GetUpperBound(0);
d1= myArray.GetUpperBound(1);

Sort Static Sorts the elements in an array or a section of an array.


method

Reverse Static Reverses the elements in a one-dimensional array.


method

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.

Copy Static Copies a selected portion of one array to another.


method

Clear Static Sets a specified range of elements in an array to zero or null.


method
72 Chapter 2 ■ C# Language Fundamentals

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.

Listing 2-3 Working with Arrays Using System.Array Members


class MyApp
{
static void Main()
{
string[] artists = {"Rembrandt", "Velazquez",
"Botticelli", "Goya", "Manet","El Greco"};
// ..Sort array in ascending order
Array.Sort(artists);
// ..Invert the array
Array.Reverse(artists);
PrintArray(artists); // call method to list array
int ndx = Array.IndexOf(artists,"Goya"); // ndx = 3
// ..Clone the array
string[] artClone = (string[]) artists.Clone();
// Do arrays point to same address?
bool eq = Object.ReferenceEquals(
artClone[0],artists[0]); // true
Array.Clear(artClone,0,artClone.Length);
// ..Copy selected members of artists to artClone
Array.Copy(artists,1,artClone,0,4);
eq = Object.ReferenceEquals(
artClone[0],artists[1]); // true
}
// List contents of Array
public static void PrintArray(string[] strArray)
{
for ( int i = 0; i<= strArray.GetUpperBound(0); i++ )
{
Console.WriteLine(strArray[i]);
}
}
}
2.9 Reference and Value Types 73

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:

(source, source index, target, target index, # to copy)

2.9 Reference and Value Types


The Common Language Runtime (CLR) supports two kinds of types: reference types
and value types (see Figure 2-2 on the following page). Reference types include
classes, arrays, interfaces, and delegates. Value types include the primitive data types
such as int, char, and byte as well as struct and enum types. Value and reference
types are distinguished by their location in the .NET class hierarchy and the way in
which .NET allocates memory for each. We’ll look at both, beginning with the class
inheritance hierarchy.

System.Object and System.ValueType


Both reference and value types inherit from the System.Object class. The differ-
ence is that almost all reference types inherit directly from it, whereas value types
inherit further down the hierarchy—directly from the System.ValueType class.
As the base for all types, System.Object provides a set of methods that you can
expect to find on all types. This set includes the ToString method used throughout
this chapter, as well as methods to clone a type, create a unique hash code for a type,
and compare type instances for equality. Chapter 4 discusses these methods in detail
and describes how to implement them on custom classes.
74 Chapter 2 ■ C# Language Fundamentals

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

Figure 2-2 Hierarchy of common reference and value types

System.ValueType inherits from System.Object. It does not add any mem-


bers, but does override some of the inherited methods to make them more suitable
for value types. For example, Equals() is overridden to return true if the value of
two objects’ fields match. By definition, all value types implicitly inherit from the
ValueType class.

Memory Allocation for Reference


and Value Types
The primary difference between value and reference types is the way the CLR han-
dles their memory requirements. Value types are allocated on a runtime stack, and
reference types are placed on a managed heap that is referenced from the stack.
Figure 2-3 illustrates how the value and reference types from our example (refer
to Figure 2-1) are represented in memory. Let’s step through what happens when an
instance of a reference type is created and is then assigned to a second variable:

Apparel myApparel = new Apparel();


Apparel myApparel2 = myApparel;
2.9 Reference and Value Types 75

appStatus 0

myApparel •
myApparel2 •
fabType • "Synthetic"

price 250
Overhead

Thread Stack Managed Heap

Figure 2-3 Memory layout for value and reference types

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:

int age = 17;


object refAge = age;
76 Chapter 2 ■ C# Language Fundamentals

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:

int newAge = (int) refAge;


string newAge = (string) refAge; // Fails. InvalidCastException

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.

// Create array with four values


Int[] ages = {1,2,3,4};

// Place four values in ArrayList


ArrayList ages = new ArrayList();
For (int i=0; i<4; i++) {
ages.add(i); // expects object parameter
}

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

Managed Heap Managed Heap

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

Summary of Value and Reference


Type Differences
Memory Allocation
We have seen that memory allocation is the most significant difference between
value and reference types. Reference types are allocated on the heap and value types
on the thread or call stack. When a reference type is created, it is initialized to null,
indicating it does not point to anything. A value type is initialized to zero (0).

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.11 Test Your Understanding


1. Which method is required in each C# program, and what parameters
does it take?

2. What are the three types of inline comments available in C#? Which
can be exported?

3. What is a primitive?

4. What is printed by the following statements?


int grade=78;
bool pass = (grade >=70) ? true : false;
Console.WriteLine(pass);

5. Which loop construct is executed at least once?

6. How do a break and a continue statement differ?

7. Given the following variables


char c= 'c';
double d= 120.0D;
int i=10;
string s="Rodin";

which of the following fails to compile?


2.11 Test Your Understanding 79

a. c = c+ i;
b. s += i;
c. c += s;
d. d += i;

8. Describe a potential drawback to using the + operator for concatena-


tion. What is the recommended alternative?

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#

Topics in This Chapter

• Defining a Class: The attributes and modifiers included in a class


definition influence the behavior and accessibility of the class.
• Constants: A const type defines fixed values at compilation time.
• Fields: A field is typically used to maintain data inside a class.
• Properties: A property is the recommended way to expose a
class’s data.
• Methods: The functionality of a class is defined by its methods.
• Inheritance: By using inheritance, a class can take advantage of
preexisting types.
• Constructors: This special purpose method is used to initialize a
class.
• Events and Delegates: A program’s actions are often triggered by
events; delegates have the role of invoking methods to handle an
event.
• Operator Overloading: Operators can be used to manipulate
classes.
• Interfaces: An inherited interface defines the methods and
properties that a struct or class must implement.
• Generics: By creating a generic class to store data, it is possible to
eliminate casting, boxing, and ensure type safety.
• Structures: In some situations, a struct is a better choice than a
class.
3

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#

3.1 Introduction to a C# Class


Figure 3-1 displays a class declaration followed by a body containing typical class
members: a constant, fields, a constructor containing initialization code, a property,
and a method. Its purpose is to familiarize you with the syntax common to most C#
classes and serve as a reference for later examples.

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; }
}

Figure 3-1 Class declaration and body

3.2 Defining a Class


A class definition consists of an optional attributes list, optional modifiers, the word
class followed by the class identifier (name), and an optional list containing a base
class or interfaces to be used for inheritance. Following this class declaration is the
class body, consisting of the code and class members such as methods and properties.

Syntax for class definition:

[attributes] [modifiers] class identifier [:baselist]


{class body} [;]

Classes—as do all .NET types—inherit from the System.Object class. This


inheritance is implicit and is thus not specified as part of the class definition. As we’ll
3.2 Defining a Class 83

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]

Single attribute with named argument and positional argument (0):

[ClassDesc(Author="Knuth", 0)]

Multiple attributes can be defined within brackets.:

[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:

• An attribute is an instance of a public class. As such, it has fields and


properties that can be used to provide rich descriptive information
about a target or targets.
• All compilers that target the Common Language Runtime (CLR) rec-
ognize attributes and store information about them in the module’s
metadata. This is an elegant way to attach information to a program
entity that can affect its behavior without modifying its implementa-
tion code. An application can then use reflection (a set of types for
reading metadata) to read the metadata at runtime and make deci-
sions based on its value.
• Hundreds of predefined attributes are included in the .NET Frame-
work Class Library (FCL). They are used heavily in dealing with
84 Chapter 3 ■ Class Design in C#

interoperability issues such as accessing the Win32API or allowing


.NET applications and COM objects to communicate. They also are
used to control compiler operations. The[assembly:CLSCompliant-
true)] attribute in Figure 3-1 tells the C# compiler to check the code
for CLS compliance.

Core Note

Attributes provide a way to extend the metadata generated by the C#


compiler with custom descriptive information about a class or class
member.

.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:

File: attribs.cs (attribs.dll)

#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

File: attribclient.cs (attribclient.exe)

#define TRACE
using System;
public class MyApp {
static void Main()
{
Console.WriteLine("Testing Method Calls");
AttributeTest.ListTrace();
AttributeTest.ListDebug();
}
}

Executing attribclient yields the following output:

Testing Method Calls


Trace is On

When attribclient is compiled, the compiler detects the existence of the


TRACE symbol, so the call to ListTrace is included. Because DEBUG is not defined,
the call to ListDebug is excluded. The compiler ignores the fact that DEBUG is
defined in attribs; its action is based on the symbols defined in the file containing
the method calls. Note that a conditional attribute can be used only with methods
having a return type of void.

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.

public A class can be accessed from any assembly.


protected Applies only to a nested class (class defined within another
class). Access is limited to the container class or classes derived
from the container class.
internal Access is limited to classes in the same assembly. This is the
default access.
private Applies only to a nested class. Access is limited to the container
class.
protected The only case where multiple modifiers may be used.
internal Access is limited to the current assembly or types derived from
the containing class.
86 Chapter 3 ■ Class Design in C#

Core Note

A base class must be at least as accessible as its derived class. The


following raises an error:

class Furniture { } // default access is internal


public class Sofa : Furniture { } // error

The error occurs because the Furniture class (internal by default) is


less accessible than the derived Sofa class. Errors such as this occur
most frequently when a developer relies on a default modifer. This is
one reason that modifiers should be included in a declaration.

Abstract, Sealed, and Static Modifiers


In addition to the access modifiers, C# provides a dozen or so other modifiers for use
with types and type members. Of these, three can be used with classes: abstract,
sealed, and static.
abstract Indicates that a class is to be used only as a base class for other
classes. This means that you cannot create an instance of the
class directly. Any class derived from it must implement all of its
abstract methods and accessors. Despite its name, an abstract
class can possess nonabstract methods and properties.
sealed Specifies that a class cannot be inherited (used as a base class).
Note that .NET does not permit a class to be both abstract and
sealed.
static Specifies that a class contains only static members (.NET 2.0).

Class Identifier
This is the name assigned to the class. The ECMA standard recommends the follow-
ing guidelines for naming the identifier:

• Use a noun or noun phrase.


• Use the Pascal case capitalization style: The first letter in the name
and the first letter of each subsequent concatenated word are capital-
ized—for example, BinaryTree.
• Use abbreviations sparingly.
• Do not use a type prefix, such as C, to designate all classes—for exam-
ple, BinaryTree, not CBinaryTree.
• Do not use the underscore character.
3.2 Defining a Class 87

• By convention, interface names always begin with I; therefore, do not


use I as the first character of a class name unless I is the first letter in
an entire word—for example, IntegralCalculator.

Base Classes, Interfaces, and Inheritance


This optional list contains a previously defined class or interface(s) from which a class
may derive its behavior and capabilities. The new class is referred to as the derived
class, and the class or interface from which it inherits is the base class or interface. A
base class must be listed before any interface(s).

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

• Inheritance from a base class is referred to as implementation


inheritance. The derived class inherits all of the members of the base
class. However, the base class can prevent access to a member by
defining it with the private modifier.
• Inheritance from an interface is referred to as interface inheritance
because the interface does not provide implementation code. The
derived class must provide the logic to implement any functions
defined in the base interface(s).
88 Chapter 3 ■ Class Design in C#

3.3 Overview of Class Members


Table 3-1 provides a summary of the types that comprise a .NET class. They can be
classified broadly as members that hold data—constants, fields, and properties—and
members that provide functionality—the constructor, method, and event. We’ll look
at each individually.

Table 3-1 Class Members

Member Type Valid In Description

Constant Class, Structure A symbol that represents an unchanging value.


The compiler associates it with the class—not an
instance of the class.

Field Class, Structure A variable that holds a data value. It may be


read-only or read/write.

Property Class, Structure Provides access to a value in a class. It uses an


accessor that specifies the code to be executed in
order to read or write the value. The code to read
or write to a property is implemented implicitly
by .NET as two separate methods.

Constructor Class, Structure C# has three types of constructors:


Instance. Initializes fields when an instance of
a class is created.
Private. Commonly used to prevent instances
of a class from being created.
Static. Initializes class before any instance is
created.

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.

Types Class, Structure, Classes, interfaces, structs, delegates.


Interface
3.4 Constants, Fields, and Properties 89

Member Access Modifiers


The access modifiers used for a class declaration can also be applied to class mem-
bers. They determine the classes and assemblies that have access to the class. Table
3-2 summarizes the scope of accessibility.

Table 3-2 Summary of Accessibility Provided by Access Modifiers

Access Modifiers

Class can be accessed


public protected Internal private
by classes in:

Another assembly Yes * No *

Same assembly Yes * Yes *

Containing class Yes Yes Yes Yes

Class derived from containing class Yes Yes Yes No

* Not applicable

3.4 Constants, Fields, and Properties


Constants, fields, and properties are the members of a class that maintain the con-
tent or state of the class. As a rule of thumb, use constants for values that will
never change; use fields to maintain private data within a class; and use properties
to control access to data in a class. Let’s now look at the details of these three class
members.

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#

Listing 3-1 Constants


using System;
class Conversions
{
public const double Cm = 2.54;
public const double Grams = 454.0 , km = .62 ;
public const string ProjectName = "Metrics";
}
class ShowConversions
{
static void Main()
{
double pounds, gramWeight;
gramWeight = 1362;
pounds = gramWeight / Conversions.Grams;
Console.WriteLine(
"{0} Grams= {1} Pounds", gramWeight,pounds);
Console.WriteLine("Cm per inch {0}", Conversions.Cm);
Conversions c= new Conversions(); // Create class
// instance
// This fails to compile. Cannot access const from object
Console.WriteLine("Cm per inch {0}", c.Cm);
}
}

• The const keyword can be used to declare multiple constants in one


statement.
• A constant must be defined as a primitive type, such as string or
double shown here (see Chapter 2 for a list of all C# primitives).
• Constants cannot be accessed from an instance of the class. Note that
the ShowConversion class accesses the constants without instantiat-
ing the class.

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

It is often desirable to store a group of constants in a special utility or


helper class. Declare them public to make them available to all classes.
Because there is no need to create instances of the class, declare the
class as abstract.

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).

Table 3-3 Field Modifiers

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:

const double salesTax = .065;

can be replaced with a field

public static readonly double SalesTax = .065;

A method then references this field as Furniture.SalesTax, and the readonly


modifier ensures the value cannot be changed. Note that if you create an instance of
this class, you cannot access salesTax as a member of that instance. An attempt to
do so results in the compile error “static member cannot be accessed with an
instance reference”.

Furniture chair = new Furniture("Broyhill","12422",225.00);


double itemTax = chair.SalesTax; // Raises an error

Using Static Read-Only Fields


to Reference Class Instances
Static readonly fields can be used to represent groups of related constant data by
declaring them a reference type such as a class instance or array. This is of use when
the data can be represented by a limited number of objects with unchanging values.
This example presents the interesting concept of fields defined as instances of their
containing class. The static modifier makes this possible, because it designates the
field as part of the class and not its instances. Note that the private constructor pre-
vents clients outside the scope of the class from creating new class instances. Thus,
only those objects exposed by the fields are available outside the class.
The class also contains two instance fields, yardPrice and deliveryWeeks, that
are declared as private. Access to them is controlled though a public method and
property:

Upholstery.silk.FabCost(10); // Value from method


Upholstery.silk.DeliveryTime; // Value from property
3.4 Constants, Fields, and Properties 93

Listing 3-2 Using Static Read-Only Fields as Reference Types


public class Upholstery
{
// fields to contain price and delivery time for fabrics
public static readonly Upholstery silk =
new Upholstery(15.00, 8);
public static readonly Upholstery wool =
new Upholstery(12.00, 6);
public static readonly Upholstery cotton =
new Upholstery(9.00, 6);
private double yardPrice;
private int deliveryWeeks;
// constructor - set price per yard and delivery time
// private modifier prevents external class instantiation
private Upholstery ( double yrPrice, int delWeeks)
{
yardPrice = yrPrice;
deliveryWeeks = delWeeks;
}
// method to return total cost of fabric
public double FabCost(double yards)
{
return yards * this.yardPrice;
}
// property to return delivery time
public int DeliveryTime
{get { return deliveryWeeks;}}
// property to return price per yard
public double PricePerYard
{get {return yardPrice;}}
}

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:

1. In addition to the four access modifiers, the property modifier may be


static, abstract, new, virtual, or override. Abstract is used
only in an abstract class; virtual is used in a base class and per-
mits a subclass to override the property.
2. value is an implicit parameter containing the value passed when a
property is called.
3. The get and set accessors may have different access modifiers.

Listing 3-3 Creating and Accessing a Property


public class Upholstery
{
private double yardPrice;
// Property to return or set the price
public double PricePerYard
{
get {return yardPrice;} // Returns a property value
set { // Sets a property value
if ( value <= 0 )
throw new ArgumentOutOfRangeException(
"Price must be greater than 0.");
yardPrice = value
}
}
...
}

The syntax for accessing the property of a class instance is the same as for a field:

// fabObj is instance of Upholstery class


double fabricPrice = fabObj.PricePerYard;
fabObj.PricePerYard = 12.50D;
3.4 Constants, Fields, and Properties 95

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:

[attributes] <modifier><return type> this[parameter(s)] {

Example:

public int this [int ndx] {

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#

Listing 3-4 Using Indexer to Expose an Array of Objects


using System;
using System.Collections; // Namespace containing ArrayList
public class Upholstery
{
// Class to represent upholstery fabric
private double yardPrice;
private int deliveryWeeks;
private string fabName;
// Constructor
public Upholstery (double price, int delivery, string fabric)
{
this.yardPrice = price;
this.deliveryWeeks = delivery;
this.fabName = fabric;
}
// Three readonly properties to return Fabric information
public int DeliveryTime
{get {return deliveryWeeks;}}
public double PricePerYard
{get {return yardPrice;}}
public string FabricName
{get {return fabName;}}
}
public class Fabrics
{
// Array to hold list of objects
private ArrayList fabricArray = new ArrayList();
// Indexer to add or return an object from the array
public Upholstery this[int ndx]
{
get {
if(!(ndx<0 || ndx > fabricArray.Count-1))
return (Upholstery)fabricArray[ndx];
// Return empty object
else return(new Upholstery(0,0,""));
}
set { fabricArray.Insert(ndx, value);}
}
}
public class IndexerApp
{
3.5 Methods 97

Listing 3-4 Using Indexer to Expose an Array of Objects (continued)


public static void Main()
{
Fabrics sofaFabric = new Fabrics();
// Use Indexer to create array of Objects
sofaFabric[0] = new Upholstery(15.00, 8, "Silk");
sofaFabric[1] = new Upholstery(12.00, 6, "Wool");
sofaFabric[2] = new Upholstery(9.00, 6, "Cotton");
// Next statement prints "Fabric: Silk"
Console.WriteLine("Fabric: {0} ",
sofaFabric[0].FabricName);
}
}

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:

Fabrics sofaFabric = new Fabrics();


// Use Indexer to create array of Objects
sofaFabric[0] = new Upholstery(15.00, 8, "Silk");
sofaFabric[1] = new Upholstery(12.00, 6, "Wool");

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:

Fabrics curtainFabric = new Fabrics();


curtainFabric[0] = new Upholstery(11.00, 4, "Cotton");
curtainFabric[1] = new Upholstery(7.00, 5, "Rayon");

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.

Table 3-4 Method Modifiers

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.

new Permits a method in an inherited class to “hide” a non-virtual method with


a same name in the base class. It replaces the original method rather than
overriding it.
3.5 Methods 99

Table 3-4 Method Modifiers (continued)

Modifier Description

sealed Prevents a derived class from overriding this method.


• Is used in a derived class that will serve as the base for its own subclasses.
• Must be used with the override modifier.

abstract The method contains no implementation details and must be implemented


by any subclass. Can only be used as a member of an abstract class.

extern Indicates that the method is implemented externally. It is generally used


with the DLLImport attribute that specifies a DLL to provide the imple-
mentation.

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.

Listing 3-5 Static Method


using System;
class Conversions
{
// class contains functions to provide metric conversions
private static double cmPerInch = 2.54;
private static double gmPerPound = 455;
public static double inchesToMetric(double inches) {
return(inches * cmPerInch);
}
public static double poundsToGrams(double pounds) {
return(pounds * gmPerPound);
}
}
class Test
{
static void Main() {
double cm, grams;
cm = Conversions.inchesToMetric(28.5);
grams = Conversions.poundsToGrams(984.4);
}
}
100 Chapter 3 ■ Class Design in C#

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).

Method Inheritance with Virtual and Override Modifiers


Inheritance enables a program to create a new class that takes the form and function-
ality of an existing (base) class. The new class then adds code to distinguish its behav-
ior from that of its base class. The capability of the subclass and base class to respond
differently to the same message is classical polymorphism. In practical terms, this
most often means that a base and derived class(es) provide different code for meth-
ods having the same signature.
By default, methods in the base class cannot be changed in the derived class. To
overcome this, .NET provides the virtual modifier as a cue to the compiler that a
method can be redefined in any class that inherits it. Similarly, the compiler requires
that any derived class that alters a virtual method preface the method with the over-
ride modifier. Figure 3-2 and Listing 3-6 provide a simple illustration of this.

Class Fiber

Class Natural

Class Cotton

Figure 3-2 Relationship between base class and subclasses for Listing 3-6

Listing 3-6 Virtual Methods


using System;
class Fiber
{
public virtual string ShowMe() { return("Base");}
}
class Natural:Fiber
{
public override string ShowMe() { return("Natural");}
}
class Cotton:Natural
{
public override string ShowMe() { return("Cotton");}
}
3.5 Methods 101

Listing 3-6 Virtual Methods (continued)


class Test
{
static void Main ()
{
Fiber fib1 = new Natural(); // Instance of Natural
Fiber fib2 = new Cotton(); // Instance of Cotton
string fibVal;
fibVal = fib1.ShowMe(); // Returns "Natural"
fibVal = fib2.ShowMe(); // Returns "Cotton"
}
}

In this example, Cotton is a subclass of Natural, which is itself a subclass of


Fiber. Each subclass implements its own overriding code for the virtual method
ShowMe.

fib1.ShowMe(); //returns "Natural"


fib2.ShowMe(); //returns "Cotton"

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”.

New Modifier and Versioning


There are situations where it is useful to hide inherited members of a base class. For
example, your class may contain a method with the same signature as one in its base
class. To notify the compiler that your subclass is creating its own version of the
method, it should use the new modifier in the declaration.
ShowMe() is no longer virtual and cannot be overridden. For Natural to create
its own version, it must use the new modifier in the method declaration to hide the
inherited version. Observe the following:

• An instance of the Natural class that calls ShowMe()invokes the new


method:
Natural myFiber = new Natural();
string fibTtype = myFiber.ShowMe(); // returns "Natural"
• Cotton inherits the new method from Natural:
Cotton myFiber = new Cotton();
string fibType = myFiber.ShowMe(); // returns "Natural"
102 Chapter 3 ■ Class Design in C#

• If ShowMe were declared as private rather than public in Natural,


Cotton would inherit ShowMe from Fiber, because it cannot inherit a
method that is out of scope.

Listing 3-7 Using the New Modifier for Versioning


public class Fiber
{
public string ShowMe() {return("Base");}
public virtual string GetID() {return("BaseID");}
}
public class Natural:Fiber
{
// Hide Inherited version of ShowMe
new public string ShowMe() {return("Natural");}
public override string GetID() {return("NaturalID");}
}
public class Cotton:Natural
{ // Inherits two methods: ShowMe() and GetID()
}

Sealed and Abstract Modifiers


A sealed modifier indicates that a method cannot be overridden in an inheriting
class; an abstract modifier requires that the inheriting class implement it. In the
latter case, the base class provides the method declaration but no implementation.
This code sample illustrates how an inheriting class uses the sealed modifier to
prevent a method from being overridden further down the inheritance chain. Note
that sealed is always paired with the override modifier.

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

An abstract method represents a function with a signature—but no implementa-


tion code—that must be defined by any non-abstract class inheriting it. This differs
from a virtual method, which has implementation code, but may be redefined by an
inheriting class. The following rules govern the use abstract methods:

• Abstract methods can only be declared in abstract classes; however,


abstract classes may have non-abstract methods.
• The method body consists of a semicolon:
public abstract void myMethod();
• Although implicitly virtual, abstract methods cannot have the
virtual modifier.
• A virtual method can be overridden by an abstract method.

When facing the decision of whether to create an abstract class, a developer


should also consider using an interface. The two are similar in that both create a
blueprint for methods and properties without providing the implementation details.
There are differences between the two, and a developer needs to be aware of these
in order to make the better choice. The section on interfaces in this chapter offers a
critical comparison.

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#

Listing 3-8 Using the ref and out Parameter Modifiers


class TestParms
{
public static void FillArray(out double[] prices)
{
prices = new double[4] {50.00,80.00,120.00,200.00};
}
public static void UpdateArray(ref double[] prices)
{
prices[0] = prices[0] * 1.50;
prices[1] = prices[1] * 2.0;
}
public static double TaxVal(double ourPrice,
out double taxAmt)
{
double totVal = 1.10 * ourPrice;
taxAmt = totVal – ourPrice;
ourPrice = 0.0; // Does not affect calling parameter
return totVal;
}
}
class MyApp
{
public static void Main()
{
double[] priceArray;
double taxAmt;
// (1) Call method to initialize array
TestParms.FillArray(out priceArray);
Console.WriteLine( priceArray[1].ToString()); // 80
// (2) Call method to update array
TestParms.UpdateArray(ref priceArray);
Console.WriteLine( priceArray[1].ToString()); // 160
// (3) Call method to calculate amount of tax.
double ourPrice = 150.00;
double newtax = TestParms.TaxVal(ourPrice, out taxAmt);
Console.WriteLine( taxAmt.ToString()); // 15
Console.WriteLine( ourPrice); // 150.00
}
}
3.5 Methods 105

In this example, the class MyApp is used to invoke three methods in the
TestParms class:

1. FillArray is invoked to initialize the array. This requires passing the


array as a parameter with the out modifier.
2. The returned array is now passed to a second method that modifies
two elements in the array. The ref modifier indicates the array can be
modified.
3. ourPrice is passed to a method that calculates the amount of tax and
assigns it to the parameter taxAmt. Although ourPrice is set to 0
within the method, its value remains unchanged in the calling method
because it is passed by value.

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:

// Calculate average of variable number of arguments


public static double GetAvg(params double[] list)
{
double tot = 0.0;
for (int i = 0 ; i < list.Length; i++)
tot += list[i];
return tot / list.Length;
}

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:

[attributes] [modifiers] <identifier> ( [parameter-list])


[:initializer] { constructor-body}

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:

• The identifier (constructor name) must be the same as the class


name.
• The initializer provides a way to invoke code prior to entering the
constructor-body. It takes two forms:

base(argument list)—Calls a base class


this(argument list)—Calls another constructor in the same class
• If no explicit constructor is provided for a class, the compiler creates a
default parameterless constructor.

The instance constructor is called as part of creating a class instance. Because a


class may contain multiple constructors, the compiler calls the constructor whose sig-
nature matches that of the call:

Fiber fib1 = new Cotton();


Fiber fib1 = new Cotton("Egyptian");
3.6 Constructors 107

.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.

Inheritance and Constructors


Constructors, unlike methods, are not inherited. It is up to each class to define its
own constructor(s) or use the default parameterless constructor. Each constructor
must call a constructor in its base class before the first line of the calling constructor
is executed. Because C# generates a call to the base class’s default constructor auto-
matically, the programmer typically does not bother with this. But there are excep-
tions. Consider this code in which the base class Apparel defines a constructor:

public class Apparel


{
private string color;
private decimal price;
// constructor
public Apparel(string c, decimal p, string b)
{
color = c;
price = p;
}
}
// class inheriting from Apparel
class Coat: Apparel
{
private string length;
public Coat(string c, decimal p, string b, string l)
{
length = l;
// ... other code
}
}

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.

Listing 3-9 A Constructor with a Base Initializer


using System;
public class Apparel
{
private string color;
private decimal price;
private string brand;
// Constructor
public Apparel(string c,decimal p, string b)
{
color = c;
price = p;
brand = b;
}

public string ItemColor


{
get {return color;}
}
// other properties and members go here
}

public class Shirt: Apparel


{
private decimal mySleeve;
private decimal myCollar;
public Shirt (string c, decimal p, string b,
decimal sleeve, decimal collar) : base(c,p,b)
{
mySleeve = sleeve;
myCollar = collar;
}
}
3.6 Constructors 109

Listing 3-9 A Constructor with a Base Initializer (continued)


public class TestClass
{
static void Main()
{
Shirt shirtClass = new Shirt("white", 15.00m, "Arrow",
32.0m, 15.5m);
Console.WriteLine(shirtClass.ItemColor); // "white"
}
}

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.

public class Natural


{
string fiberType = "Generic";
string color = "white";
// Constructors
public Natural() { ... }
public Natural(string cotton_type) { ... }
public Natural(string cotton_type, string color) { ... }
}

For more efficient code, perform the field initialization in a single constructor and
have the other constructors invoke it using the this initializer.

public Natural() { // constructor initializes fields


fiberType="Generic";
color = "white";
}
// Following constructors use this() to call default
// constructor before constructor body is executed.
110 Chapter 3 ■ Class Design in C#

public Natural(string cotton_type): this() { ... }


public Natural(string cotton_type, string color):
this() { ... }

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.

Private Constructor Used with Class


Listing 3-10
Containing Static Methods
using System;
class Conversions
{
// class contains functions to provide metric conversions
// static method can only work with static field.
static cmPerInch = 2.54;
private static double gmPerPound = 455;
public static double inchesToMetric(double inches) {
return(inches * cmPerInch);
}
public static double poundsToGrams(double pounds) {
return(pounds*gmPerPound);
}
// Private constructor prevents creating class instance
private Conversions()
{ ... }
}
3.6 Constructors 111

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:

• It cannot have parameters and cannot be overloaded. Consequently, a


class may have only one static constructor.
• It must have private access, which C# assigns automatically.
• It cannot call other constructors.
• It can only access static members.

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
}

This class contains a static initializer, a static constructor, and an instance


constructor. Let’s look at the sequence of events that occur when the class is instanti-
ated:

BaseClass myClass1 = new BaseClass();


BaseClass myClass2 = new BaseClass();
BaseClass myClass2 = new BaseClass();

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.

3.7 Delegates and Events


Clicking a submit button, moving the mouse across a Form, pushing an Enter key, a
character being received on an I/O port—each of these is an event that usually trig-
gers a call to one or more special event handling routines within a program.
In the .NET world, events are bona fide class members—equal in status to prop-
erties and methods. Just about every class in the Framework Class Library has event
members. A prime example is the Control class, which serves as a base class for all
GUI components. Its events—including Click, DoubleClick, KeyUp, and Got-
Focus—are designed to recognize the most common actions that occur when a user
interacts with a program. But an event is only one side of the coin. On the other side
is a method that responds to, or handles, the event. Thus, if you look at the Control
class methods, you’ll find OnClick, OnDoubleClick, OnKeyUp, and the other meth-
ods that correspond to their events.
3.7 Delegates and Events 113

object1
OnClick
Click
object2
OnClick
delegate object

Figure 3-3 Event handling relationships

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:

public delegate void MyString (string msg);

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.

Listing 3-11 Multicasting Delegate


// file: delegate.cs
using System;
using System.Threading;
class DelegateSample
{
public delegate void MyString(string s);
public static void PrintLower(string s){
Console.WriteLine(s.ToLower());
}

public static void PrintUpper(string s){


Console.WriteLine(s.ToUpper());
}

public static void Main()


{
MyString myDel;
// register method to be called by delegate
myDel = new MyString(PrintLower);
// register second method
myDel += new MyString(PrintUpper);
// call delegate
myDel("My Name is Violetta.");
// Output: my name is violetta.
// MY NAME IS VIOLETTA.
}
}

Note that the += operator is used to add a method to the invocation list. Con-
versely, a method can be removed using the -= operator:

myDel += new MyString(PrintUpper); // register for callback


myDel -= new MyString(PrintUpper); // remove method from list

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.”

Delegate-Based Event Handling


In abstract terms, the .NET event model is based on the Observer Design Pattern.
This pattern is defined as “a one-to-many dependency between objects so that when
one object changes state, all its dependents are notified and updated automatically.”1
We can modify this definition to describe the .NET event handling model depicted
in Figure 3-3: “when an event occurs, all the delegate’s registered methods are noti-
fied and executed automatically.” An understanding of how events and delegates
work together is the key to handling events properly in .NET.
To illustrate, let’s look at two examples. We’ll begin with built-in events that have a
predefined delegate. Then, we’ll examine how to create events and delegates for a
custom class.

Working with Built-In Events


The example in Listing 3-12 displays a form and permits a user to draw a line on the
form by pushing a mouse key down, dragging the mouse, and then raising the mouse
key. To get the endpoints of the line, it is necessary to recognize the MouseDown and
MouseUp events. When a MouseUp occurs, the line is drawn.
The delegate, MouseEventHandler, and the event, MouseDown, are predefined
in the Framework Class Library. The developer’s task is reduced to implementing the
event handler code and registering it with the delegate. The += operator is used to
register methods associated with an event.

this.MouseDown += new MouseEventHandler(OnMouseDown);

The underlying construct of this statement is

this.event += new delegate(event handler method);

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:

public delegate void MouseEventHandler(


object sender,
MouseEventArgs e)

Listing 3-12 Event Handler Example


using System;
using System.Windows.Forms;
using System.Drawing;
class DrawingForm:Form
{
private int lastX;
private int lastY;
private Pen myPen= Pens.Black; // defines color of drawn line
public DrawingForm() {
this.Text = "Drawing Pad";
// Create delegates to call MouseUp and MouseDown
this.MouseDown += new MouseEventHandler(OnMouseDown);
this.MouseUp += new MouseEventHandler(OnMouseUp);
}
private void OnMouseDown(object sender, MouseEventArgs e)
{
lastX = e.X;
lastY = e.Y;
}
private void OnMouseUp(object sender, MouseEventArgs e)
{
// The next two statements draw a line on the form
Graphics g = this.CreateGraphics();
if (lastX >0 ){
g.DrawLine(myPen, lastX,lastY,e.X,e.Y);
}
lastX = e.X;
lastY = e.Y;
}
static void Main() {
Application.Run(new DrawingForm());
}
}
3.7 Delegates and Events 117

Using Anonymous Methods with Delegates


.NET 2.0 introduced a language construct known as anonymous methods that elimi-
nates the need for a separate event handler method; instead, the event handling code
is encapsulated within the delegate. For example, we can replace the following state-
ment from Listing 3-12:

this.MouseDown += new MouseEventHandler(OnMouseDown);

with this code that creates a delegate and includes the code to be executed when the
delegate is invoked:

this.MouseDown += delegate(object sender, EventArgs e)


{
lastX = e.X;
lastY = e.Y;
}

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:

delegate [(parameter-list)] {anonymous-method-block}

• The delegate keyword is placed in front of the code that is executed


when the delegate is invoked.
• An optional parameter list may be used to pass data to the code block.
These parameters should match those declared by the delegate. In
this example, the parameters correspond to those required by the pre-
defined delegate MouseEventHandler.
• When the C# compiler encounters the anonymous code block, it cre-
ates a new class and constructs a method inside it to contain the code
block. This method is called when the delegate is invoked.

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);

// Register two anonymous methods with the delegate


MyString myDel;
myDel = delegate(string s) { Console.WriteLine(s.ToLower()); };
118 Chapter 3 ■ Class Design in C#

myDel += delegate(string s) { Console.WriteLine(s.ToUpper()); };


// invoke delegate
myDel("My name is Violetta");

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.

Defining Custom Events


When writing your own classes, it is often necessary to define custom events that sig-
nal when some change of state has occurred. For example, you may have a compo-
nent running that monitors an I/O port and notifies another program about the status
of data being received. You could use raw delegates to manage the event notification;
but allowing direct access to a delegate means that any method can fire the event by
simply invoking the delegate. A better approach—and that used by classes in the
Framework Class Library—is to use the event keyword to specify a delegate that
will be called when the event occurs.
The syntax for declaring an event is

public event <delegate name> <event name>

Let’s look at a simple example that illustrates the interaction of an event and
delegate:

public class IOMonitor


{
// Declare delegate
public delegate void IODelegate(String s);
// Define event variable
public event IODelegate DataReceived ;
// Fire the event
public void FireReceivedEvent (string msg)
{
if (DataReceived != null) // Always check for null
{
DataReceived(msg); // Invoke callbacks
}
}
}

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:

IOMonitor monitor = new IOMonitor();


// You must provide a method that handles the callback
monitor.DataReceived += new IODelegate(callback method);
monitor.FireReceivedEvent("Buffer Full"); // Fire event

Defining a Delegate to Work with Events


In the preceding example, the event delegate defines a method signature that takes a
single string parameter. This helps simplify the example, but in practice, the signa-
ture should conform to that used by all built-in .NET delegates. The EventHandler
delegate provides an example of the signature that should be used:

public delegate void EventHandler(object sender,


EventArgs eventArgs);

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.

public class IOEventArgs: EventArgs


{
public IOEventArgs(string msg){
this.eventMsg = msg;
}
public string Msg{
get {return eventMsg;}
}
private string eventMsg;
}
120 Chapter 3 ■ Class Design in C#

IOEventArgs illustrates the guidelines to follow when defining an EventArgs


class:

• It must inherit from the EventArgs class.


• Its name should end with EventArgs.
• Define the arguments as readonly fields or properties.
• Use a constructor to initialize the values.

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

If your delegate uses the EventHandler signature, you can use


EventHandler as your delegate instead of creating your own. Because it
is part of the .NET Framework Class Library, there is no need to declare it.

An Event Handling Example


Let’s bring these aforementioned ideas into play with an event-based stock trading
example. For brevity, the code in Listing 3-13 includes only an event to indicate when
shares of a stock are sold. A stock purchase event can be added using similar logic.

Implementing a Custom Event-Based


Listing 3-13
Application
//File: stocktrader.cs
using System;
// (1) Declare delegate
public delegate void TraderDelegate(object sender,
EventArgs e);
// (2) A class to define the arguments passed to the delegate
public class TraderEventArgs: EventArgs
{
public TraderEventArgs(int shs, decimal prc, string msg,
string sym){
this.tradeMsg = msg;
this.tradeprice = prc;
this.tradeshs = shs;
this.tradesym = sym;
}
3.7 Delegates and Events 121

Implementing a Custom Event-Based


Listing 3-13
Application (continued)
public string Desc{
get {return tradeMsg;}
}
public decimal SalesPrice{
get {return tradeprice;}
}
public int Shares{
get {return tradeshs;}
}
public string Symbol{
get {return tradesym;}
}

private string tradeMsg;


private decimal tradeprice;
private int tradeshs;
private string tradesym;
}
// (3) class defining event handling methods
public class EventHandlerClass
{
public void HandleStockSale(object sender,EventArgs e)
{
// do housekeeping for stock purchase
TraderEventArgs ev = (TraderEventArgs) e;
decimal totSale = (decimal)(ev.Shares * ev.SalesPrice);
Console.WriteLine(ev.Desc);
}
public void LogTransaction(object sender,EventArgs e)
{
TraderEventArgs ev = (TraderEventArgs) e;
Console.WriteLine(ev.Symbol+" "+ev.Shares.ToString()
+" "+ev.SalesPrice.ToString("###.##"));
}
}
// (4) Class to sell stock and publish stock sold event
public class Seller
{
// Define event indicating a stock sale
public event TraderDelegate StockSold;
public void StartUp(string sym, int shs, decimal curr)
122 Chapter 3 ■ Class Design in C#

Implementing a Custom Event-Based


Listing 3-13
Application (continued)
{
decimal salePrice= GetSalePrice(curr);
TraderEventArgs t = new TraderEventArgs(shs,salePrice,
sym+" Sold at "+salePrice.ToString("###.##"), sym);
FireSellEvent(t); // Fire event
}
// method to return price at which stock is sold
// this simulates a random price movement from current price
private decimal GetSalePrice(decimal curr)
{
Random rNum = new Random();
// returns random number between 0 and 1
decimal rndSale = (decimal)rNum.NextDouble() * 4;
decimal salePrice= curr - 2 + rndSale;
return salePrice;
}
private void FireSellEvent(EventArgs e)
{
if (StockSold != null) // Publish defensively
{
StockSold(this, e); // Invoke callbacks by delegate
}
}
}

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.

3.8 Operator Overloading


Built-in operators such as + and - are used so instinctively that one rarely thinks of
them as a predefined implementation for manipulating intrinsic types. In C#, for
example, the + operator is used for addition or concatenation depending on the data
types involved. Clearly, each must be supported by different underlying code.
Because the compiler knows what a numeric and string type represent, it is quite
capable of doing this. It makes sense then that these operators cannot be applied to
custom classes or structures of which the compiler has no knowledge.
It turns out that C# provides a mechanism referred to as operator overloading that
enables a class to implement code that determines how the class responds to the
operator. The code for overloading an operator is syntactically similar to that of a
method:

public static <return type> operator <op> ( parameter list)


{ implementation code}

Several rules govern its usage:

• The public and static modifiers are required.


• The return type is the class type when working with classes. It can
never be void.
• op is a binary, unary, or relational operator. Both equals (==) and not
equals (!=) must be implemented in a relational pair.
• Binary operators require two arguments; unary operators require one
argument.

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#

Listing 3-14 Operator Overloading for Classes


using System;
class Portfolio
{
public decimal risk;
public decimal totValue;
// Overloaded operator to add stock to Portfolio
public static Portfolio operator + (Portfolio p,Stock s)
{
decimal currVal = p.totValue;
decimal currRisk = p.risk;
p.totValue = p.totValue + s.StockVal;
p.risk = (currVal/p.totValue)*p.risk +
(s.StockVal/p.totValue)* s.BetaVal;
return p;
}
// Overloaded operator to remove stock from Portfolio
public static Portfolio operator - (Portfolio p,Stock s)
{
p.totValue = p.totValue - s.StockVal;
p.risk = p.risk - ((s.BetaVal-p.risk)
*(s.StockVal/p.totValue));
return p;
}
}
class Stock
{
private decimal value;
private decimal beta; // risk increases with value
public Stock(decimal myBeta, decimal myValue,
int shares)
{
value = (decimal) myValue * shares;
beta = myBeta;
}
public decimal StockVal
{ get {return value; } }

public decimal BetaVal


{ get {return beta; } }
}
class MyApp
{
3.8 Operator Overloading 125

Listing 3-14 Operator Overloading for Classes (continued)


public static void Main()
{
Portfolio p = new Portfolio();
// 200 shs of HPQ at $25, 100 shs of IBM @ $95
Stock hpq = new Stock(1.1M, 25M, 200);
Stock ibm = new Stock(1.05M, 95.0M, 100);
p += hpq; // Add hpq
p += ibm; // Add ibm
Console.Write("value:{0} ",p.totValue.ToString());
Console.WriteLine(" risk: {0}",
p.risk.ToString("#.00"));
// value = 14,500 and risk = 1.07
p -= ibm; // Remove ibm from portfolio
Console.Write("value:{0} ",p.totValue.ToString());
Console.Write(" risk: {0}",p.risk.ToString("#.00"));
// value = 5000 and risk = 1.10
}
}

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:

public static Portfolio AddStocks ( Portfolio p, Stock s)


{ return p + s; }

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:

[attributes] [modifiers] interface identifier [:baselist]


{interface body} [;]

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:

• An interface cannot inherit from a class.


• An interface can inherit from multiple interfaces.
• A class can inherit from multiple interfaces, but only one class.
• Interface members must be methods, properties, events, or indexers.
• All interface members must have public access (the default).
• By convention, an interface name should begin with an uppercase I.
3.9 Interfaces 127

Creating and Using a Custom Interface


Listing 3-15 demonstrates how to define, implement, and program against a simple
custom interface:

Listing 3-15 Interface Basics


public interface IShapeFunction
{
double GetArea(); // public abstract method
}

class Circle : IShapeFunction // Inherit Interface


{
private double radius;
public circle( double rad)
{
radius = rad;
}
public double GetArea()
{
return ( Math.PI*radius * radius);
}
public string ShowMe()
{
return ("Circle");
}
}

class Rectangle: IShapeFunction // Inherit interface


{
private double width, height;
public rectangle( double myWidth, double myHeight)
{
width= myWidth;
height= myHeight;
}
public double GetArea()
{
return (width * height);
}
}
128 Chapter 3 ■ Class Design in C#

Listing 3-15 Interface Basics (continued)


class MyApp
{
public static void Main()
{
Circle myCircle = new Circle(4);
// Interface variable that references a circle object.
IShapeFunction myICircle = myCircle;
Rectangle myRectangle = new Rectangle(4,8);
// Place shape instances in an array
IShapeFunction[] myShapes = {myCircle, myRectangle};
// The next two statements print the same results
MessageBox.Show(myCircle.GetArea().ToString());
MessageBox.Show(myShapes[0].GetArea().ToString());
MessageBox.Show(myCircle.ShowMe()); // class method
MessageBox.Show(myICircle.GetArea().ToString());
// The following will not compile because myICircle can
// access only members of the IShapeFunction interface.
MessageBox.Show(myICircle.ShowMe());
}
}

The declaration of the interface and implementation of the classes is straightfor-


ward: A Circle and Rectangle class inherit the IShapeFunction interface and
implement its GetArea method.
Conceptually, it is useful to think of a class that implements one or more inter-
faces as having multiple types. It has its own innate type, but it also can be viewed as
having the type of each interface it implements. Consider this code in the MyApp
class:

Circle myCircle = new Circle(4);


IShapeFunction myICircle = myCircle;

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

IShapeFunction[] myShapes = {myCircle, myRectangle};


MessageBox.Show(myShapes[0].GetArea().ToString());

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.

public class ShapeUtil


{
public static double SumAreas
(IShapeFunction[] funArray)
{
double tot = 0.0;
for (int i = 0; i < funArray.Length; i++)
{ tot += funArray[i].GetArea(); }
return tot;
}
}
// Access this method with ShapeUtil.SumAreas(myShapes);

The code in this class can be used with any concrete class that implements the
IShapeFunction interface.

Core Approach

For maximum program flexibility, consider using interface types, rather


than class types, when defining method parameters. This ensures there is
no limit on the types of classes that can be passed to the method, as long
as they implement the required interface.

Working with Interfaces


Determining Which Interface Members Are Available
You may not always know at compile time whether a class implements a specific
interface. This is often the case when working with a collection that contains a num-
ber of types. To perform this check at runtime, use the as or is keyword in your
code.
130 Chapter 3 ■ Class Design in C#

// (1) as keyword to determine if interface is implemented


Circle myCircle = new Circle(5.0);
IShapeFunction myICircle;
myICircle = myCircle as IShapeFunction;
If (myICircle !=null) //interface is implemented

// (2) is keyword to determine if interface is implemented


Circle myCircle = new Circle(5.0);
If (myCircle is IShapeFunction) // True if interface implemented

Accessing Interface Methods


Because a class may inherit methods from a base class and/or multiple interfaces,
there is the possibility that inherited methods will have the same name. To avoid this
ambiguity, specify an interface method declaration in the derived class with the inter-
face and method name:

double IShapeFunction.GetArea() { // <interface>.<method>

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:

Circle myCircle = new Circle(5.0);


// cannot reference explicit method
double myArea = myCircle.GetArea();

Interfaces Versus Abstract Classes


In the overall design of a system, the real issue is not whether to use a class or inter-
face, but how to best mix the two. The C# restriction on multiple implementation
inheritance assures an expanded role for interfaces. It is just too burdensome to try to
load all the needed functionality into one base class or a hierarchy of base classes.
A better solution is to define a base class and then expand its capabilities by add-
ing corresponding interfaces. This permits developers to use the class directly with-
out regard to whether the methods are interface implementations. Yet, it also permits
the developer to make use of the interface and ignore the other class members.
Of course, you must consider the drawbacks to using interfaces. A well-designed
base class reduces the burden on the inherited class to implement everything by pro-
moting code reuse. Also, a non-abstract class can add a member without breaking
any existing derived class; adding a member to an interface would break any class
implementing the interface.
3.10 Generics 131

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.

Object[] myStack = new object[50];


myStack[0] = new Circle(5.0); // place Circle object in array
myStack[1] = "Circle"; // place string in array
Circle c1 = myStack[0] as Circle;
if( c1!=null) { // circle object obtained
Circle c2 = (Circle) myStack[1]; // invalid case exception

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:

public class myCollection<T>


{
T[] myStack = new T[50];
}

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:

myCollection <string> = new myCollection<string>;

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:

public class myCollection<T> where


T:ISerializable,
T:IComparable

A parameter may have multiple interface constraints and a single class restraint.
In addition, there are three special constraints to be aware of:

class—Parameter must be reference type.


struct—Parameter must be value type.
new()—Type parameter must have a parameterless constructor.

An example of a generic class is provided in Listing 3-16. The class implements an


array that manages objects of the type specified in the type parameter. It includes
methods to add items to the array, compare items, and return an item count. It
includes one constraint that restricts the type parameter to a reference type.

Listing 3-16 A Generic Class to Hold Data


using System.Collections.Generic
public class GenStack<T>
where T:class // constraint restricts access to ref types
{
private T[] stackCollection;
private int count = 0;
// Constructor
public GenStack(int size)
{
stackCollection = new T[size];
}
public void Add(T item)
{
stackCollection[count] = item;
count += 1;
}
3.10 Generics 133

Listing 3-16 A Generic Class to Hold Data (continued)


// Indexer to expose elements in internal array
public T this[int ndx]
{
get
{
if (!(ndx < 0 || ndx > count - 1))
return stackCollection[ndx];
// Return empty object
else return (default(T));
}
}
public int ItemCount
{
get {return count;}
}
public int Compare<C>(T value1, T value2)
{
// Case-sensitive comparison: -1, 0(match), 1
return Comparer<T>.Default.Compare(value1, value2);
}
}

The following code demonstrates how a client could access the generic class
described in Listing 3-16:

// Create instance to hold 10 items of type string


GenStack<string> myStack = new GenStack<string>(10);
myStack.Add("Leslie");
myStack.Add("Joanna");
Console.WriteLine(myStack.ItemCount); // 2
Console.WriteLine(myStack[1]); // Joanna
int rel = myStack.Compare<string>("Joanna", "joanna"); // -1
Console.WriteLine(rel.ToString());

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:

• A struct is a value type. It implicitly inherits from the


System.ValueType.
• A struct cannot inherit from classes, nor can it be inherited.
• An explicitly declared constructor must have at least one parameter.
• struct members cannot have initializers. The field members must be
initialized by the constructor or the client code that creates the con-
structor.

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:

[attribute][modifier] struct identifier [:interfaces]


{struct-body}

Example:

public struct DressShirt


{
public float CollarSz;
public int SleeveLn;
3.11 Structures 135

// 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

It is possible to specify the layout of a struct’s fields in memory using


the StructLayout attribute. This is most commonly used when the
struct is to be accessed by unmanaged code that expects a specific
physical layout of the data. By default, the fields in a struct are stored
in sequential memory locations.

An instance of the structure can be created in two ways:

DressShirt dShirt = new DressShirt(16, 33);


DressShirt myShirt = new DressShirt();
myShirt.CollarSz = 16.5F;
myShirt.SleeveLn = 33;

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#

Using Methods and Properties with a Structure


Methods and properties are usually associated with classes, but they play an equally
important role in the use of structures. In fact, a client accessing a method has no
syntactical clue as to whether the method or property is associated with a class or
struct. Listing 3-17 extends the original example to add two properties and a
method.

Listing 3-17 Basic Elements of a Struct


public struct DressShirt
{
private float CollarSz;
private int SleeveLn;
public DressShirt(float collar, int sleeve)
{
this.CollarSz = collar;
this.SleeveLn = sleeve;
}

// Properties to return sleeve and collar values


public int Sleeve
{
get {return (SleeveLn);}
set {SleeveLn = value;}
}
public float Collar
{
get {return (CollarSz); }
// "value" is an implicit parameter
set {CollarSz = value; }
}

// Method to convert size to different scale


public string ShirtSize()
{
string mySize = "S";
if (CollarSz > 14.5) mySize = "M";
if (CollarSz > 15.5) mySize = "L";
if (CollarSz > 16.5) mySize = "XL";
return (mySize);
}
}
3.12 Structure Versus Class 137

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.

3.12 Structure Versus Class


Many developers instinctively select a class to represent data and the operations per-
formed on it. However, there are cases where a struct is a better choice, as evi-
denced by its use in the Framework Class Library to represent simple data types.
This section compares the two and offers general guidelines to consider when choos-
ing between the two.

Table 3-5 Comparison of Structure and Class

Structure Class

Default access level of the type Internal Internal

Default access level for data members Public Private

Default access level for properties and Private Private


methods

Value or reference type Value Reference

Can be a base for new types No Yes

Implement interfaces Yes Yes

Raise and handle events Yes Yes

Scope of members Structure Class

Instance initialization Constructor—with or Constructor—with or


without parameters. A without parameters.
struct cannot contain
a custom parameterless
constructor.

Can be nested Yes Yes

Has a destructor No Yes (Finalizer)


138 Chapter 3 ■ Class Design in C#

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.

Structures Are Value Types


and Classes Are Reference Types
As mentioned, classes are allocated space from the managed heap when the object or
class instance is created. The address of the object (on the heap) is returned to the
variable representing the object. In contrast, a variable set to a struct type contains
the structure’s actual data—not a pointer. The ramifications of this are most pro-
nounced when passing arguments to functions. Reference types simply require that a
pointer be passed, whereas structures require that a copy of all fields be made and
passed to the function.
The structure does have some advantages with regard to memory allocation and
Garbage Collection. Structure instances are allocated in a thread’s stack rather than
the managed heap, thus avoiding the associated overhead of managing pointers.
Memory management is also simpler. When a copy of a structure is no longer reach-
able, its memory is collected and made available. In contrast, classes often require
special code to handle unmanaged resources or invoke the system’s garbage collec-
tion routine. Garbage Collection is covered in the next chapter.

Unlike a Class, a Structure


Cannot Be Inherited
This is a departure from C++ conventions that permit a class to use an existing struc-
ture as a base class and permit a structure to use a class as a base. Is the .NET lack of
support for this a step backward? Not really. It better delineates the role of the struc-
ture versus the class—making the structure less of a pseudo-class and more of a data
structure. In addition, it provides for a more efficient implementation of the struc-
ture. The restriction enables the compiler to minimize the amount of administrative
code that would be required to support inheritance. For example, because the com-
piler knows that a structure’s methods cannot be overridden by subclasses, it can
optimize the method invocation by expanding the method code inline rather than
executing a method call.
3.13 Summary 139

General Rules for Choosing


Between a Structure and a Class
The easiest way to make the choice is to compare the features of the type that you are
designing with the following checklist. If any of these are true, you should use a class.

• The type needs to serve as a base for deriving other types.


• The type needs to inherit from another class. Note that although
a structure cannot explicitly specify a base class, it does implicitly
inherit from the System.ValueType and may override the inherited
methods.
• The type is frequently passed as a method parameter. Performance
degrades as copies of the structure are created with each call. An
exception to this is when the structure exists inside an array. Because
an array is a reference type, only a pointer to the array is passed.
• The type is used as the return type of methods. In the case where the
return type is a structure, the system must copy the structure from the
called function to the calling program.

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#

3.14 Test Your Understanding


1. What type of class cannot be inherited?

2. How many classes can a class directly inherit? How many interfaces?

3. Referring to the following class, answer the questions below:


public class ShowName {
public static void ShowMe(string MyName)
{ Console.WriteLine(MyName); }
}
a. Can the method ShowName be referenced from s?
ShowName s = new ShowName();
s.ShowMe("Giacomo");
b. Write a code sample that will print “My Name is Ishmael”.

4. Can an abstract class have non-abstract methods?

5. What keyword must a derived class use to replace a non-virtual inher-


ited method?

6. What are the results from running the following code?


public class ParmTest
{
public static void GetCoordinates(ref int x, int y)
{
x= x+y;
y= y+x;
}
}
// calling method
int x=20;
int y=40;
ParmTest.GetCoordinates(ref x, y);
Console.WriteLine("x="+x+" y="+y);

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.

9. Your application contains a class StoreSales that fires an event when


an item is sold. The event provides the saleprice (decimal), date
(DateTime), and the itemnum (int) to its subscribers. Create an
event handler that processes the ItemSold event by extracting and
printing the sales data.
public delegate void SaleDelegate(object sender,
SaleEvArgs e);
public event SaleDelegate ItemSold;
StoreSales mysale= new StoreSale();
mySale.ItemSold += new SaleDelegate(PrintSale);

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");
}
}

public class Child: Base


{
string parm="Hello";
public static void Main(String[] argv)
{
Child c = new Child();
c.aMethod();
}
void aMethod(int i, string Parm)
{
Console.WriteLine(Parm);
}
public void aMethod()
{ }
}
142 Chapter 3 ■ Class Design in C#

a. Error during compilation.


b. “Base Constructor” is printed.
c. “Base Constructor” and “Base Method” are printed.
d. “Base Constructor” and “Hello” are printed.
This page intentionally left blank
WORKING WITH
OBJECTS IN C#

Topics in This Chapter

• Creating Objects: Learn how to use a factory design pattern to


create objects.
• Exception Handling: Effective exception handling requires an
understanding of how exceptions are thrown and caught in .NET.
Along with an overview of exception handling, this section looks
at how to create custom exception objects.
• Using System.Object Methods: Familiarity with the
System.Object methods is necessary if you want to create
custom collection classes that implement the standard features
found in classes contained in the Framework Class Library.
• Collection Classes and Interfaces: .NET offers a variety of
collection classes for managing multiple objects. This section
looks at arrays, hash tables, and stacks, among others. Of
particular interest are the 2.0 classes that support generics.
• Object Serialization: Objects can be converted (serialized) into a
binary stream or an XML formatted stream. An example using the
binary serializer is presented.
• Object Life Cycle Management: .NET Garbage Collection
automatically removes unreferenced objects. This can produce
unwanted results unless measures are taken to ensure that objects
are destroyed properly.
4

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.

4.1 Object Creation


The subject of creating objects typically receives less attention than that of designing
classes; but it is important enough that a formal body of techniques known as cre-
ational patterns has been developed to provide object creation models. A popular
approach—used throughout the Framework Class Library (FCL)—is to implement
the factory creational pattern. As the name implies, the factory is an object whose

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).

Using a Class Factory to Create Objects—


Listing 4-1
Single Factory
public interface IApparel // Interface representing product
{
string ShowMe();
bool Knit // Property to indicate if Knit
{ get; }
}
public class SportsShirt : IApparel
{
public string ShowMe()
{
return("Sports Shirt");
}
public bool Knit
{ get {return true;} }
4.1 Object Creation 147

Using a Class Factory to Create Objects—


Listing 4-1
Single Factory (continued)
}
public class DressShirt : IApparel
{
public string ShowMe()
{
return("Dress Shirt");
}
public bool Knit
{ get {return false;} }
}
// Factory to return instances of apparel classes
public class ApparelFactory
{
public IApparel CreateApparel( string apptype)
{
switch (apptype)
{
case "MDRSHIRT":
return new DressShirt();
case "MSPSHIRT":
return new SportsShirt();
}
return null;
}
}

In this example, the class factory implements a method named CreateApparel


that returns objects. The most important thing to note is that this method is declared
with IApparel as its return type, which enables it to return an instance of any class
that implements that interface.

public IApparel CreateApparel( string apptype)

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#

ApparelFactory factory= new ApparelFactory();


IApparel ob1 = factory.CreateApparel("MDRSHIRT");
IApparel ob2 = factory.CreateApparel("MSPSHIRT");
string shirtType = ob1.ShowMe(); // Returns "Dress Shirt"

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.

Example: Creating Objects with


Multiple Factories
This solution adds an abstract class (we could use an interface) for the factory and
two concrete subclasses that implement it to produce specific apparel objects:

// 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.

public class ApparelCollector


{
public void CollectApparel(AppFactory factory)
{
IApparel apparel = factory.CreateApparel();
}
}
4.2 Exception Handling 149

The code to use the new class is analogous to that in the first example:

AppFactory factory = new DressShirtFactory();


IApparel obj2 = new ApparelCollector().CollectApparel(factory);

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.

4.2 Exception Handling


One of the most important aspects of managing an object is to ensure that its behav-
ior and interaction with the system does not result in a program terminating in error.
This means that an application must deal gracefully with any runtime errors that
occur, whether they originate from faulty application code, the Framework Class
Library, or hardware faults.
.NET provides developers with a technique called structured exception handling
(SEH) to deal with error conditions. The basic idea is that when an exception occurs,
an exception object is created and passed along with program control to a specially
designated section of code. In .NET terms, the exception object is thrown from one
section of code to another section that catches it.
Compared to error handling techniques that rely on error codes and setting bit
values, SEH offers significant advantages:

• The exception is passed to the application as an object whose proper-


ties include a description of the exception, the assembly that threw the
exception, and a stack trace that shows the sequence of calls leading to
the exception.
• If an exception is thrown and an application does not catch it, the
Common Language Runtime (CLR) terminates the application. This
forces the developer to take error handling seriously.
• The exception handling and detection code does not have to be
located where the errors occur. This means, for example, that excep-
tion handling code could be placed in a special class devoted to that
purpose.
• Exceptions are used exclusively and consistently at both the applica-
tion and system level. All methods in the .NET Framework throw
exceptions when an error occurs.
150 Chapter 4 ■ Working with Objects in C#

Before looking at the actual mechanics of implementing exception handling, let’s


examine the exception itself. As previously mentioned, an exception is a class
instance. All .NET exceptions derive from the System.Exception class. So, an
understanding of this class is essential to working with exceptions.

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

Custom Application Exceptions

System.SystemException

.NET FCL Exceptions

Figure 4-3 .NET exception classes hierarchy

The System.Exception class contains relatively few members. Table 4-1 sum-
marizes the members discussed in this section.

Table 4-1 System.Exception Class Properties

Property Type Description

HelpLink string Contains a URL that points to help documentation.

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.

Message string The text describing the exception.


4.2 Exception Handling 151

Table 4-1 System.Exception Class Properties (continued)

Property Type Description

Source string Name of the assembly that generated the exception.

StackTrace string Contains the sequence of method names and signa-


tures that were called prior to the exception. It is
invaluable for debugging.

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.

HResult Int32 This is a protected property used when interoperating


with COM code. When an exception is thrown to a
COM client, this value is converted to an HRESULT in
the COM world of unmanaged code.

Writing Code to Handle Exceptions


C# uses a try/catch/finally construct to implement exception handling (see Fig-
ure 4-4). When an exception occurs, the system searches for a catch block that can
handle the current type of exception. It begins its search in the current method,
working down the list of catch blocks sequentially. If none is found, it then searches
the catch blocks in the calling method associated with the relevant try block. If the
search yields no matching catch block, an unhandled exception occurs. As discussed
later, the application is responsible for defining a policy to deal with this. Let’s look at
the details of using these three blocks.

The try Block


The code inside the try block is referred to as a guarded region because it has asso-
ciated catch or finally blocks to handle possible exceptions or cleanup duties.
Each try block must have at least one accompanying catch or finally block.

The catch Block


A catch block consists of the keyword catch followed by an expression in parenthe-
ses called the exception filter that indicates the type of exception to which it
responds. Following this is the code body that implements the response.
152 Chapter 4 ■ Working with Objects in C#

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.
}

Figure 4-4 Code blocks used for exception handling

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:

catch (DivideByZeroException ex) { ... }

The filter will be invoked if a System.DivideByZeroException occurs. The


variable ex references the exception and provides access to its properties, such as
ex.Message and ex.StackTrace .
When using multiple catch blocks, ordering is important. They should be listed
hierarchically, beginning with the most specific exceptions and ending with the more
general ones. In fact, the compiler generates an error if you do not order them cor-
rectly.

catch (DivideByZeroException ex) { ... }


catch (IndexOutOfRangeException ex) { ... }
catch (Exception ex) { ... }

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.

The finally Block


Regarded as the “cleanup” block, the finally block is executed whether or not an
exception occurs and is a convenient place to perform any cleanup operations such as
closing files or database connections. This block must be included if there are no
catch blocks; otherwise, it is optional.

Example: Handling Common


SystemException Exceptions
Listing 4-2 illustrates the use of the try/catch/finally blocks in dealing with an
exception generated by the CLR.

Listing 4-2 Handling Exceptions Generated by the CLR


using System;
// Class to illustrate results of division by zero
public class TestExcep
{
public static int Calc(int j)
{
return (100 / j);
}
}
class MyApp
{
public static void Main()
{
TestExcep exTest = new TestExcep();
try
{
// Create divide by zero in called method
int dZero = TestExcep.Calc(0);
// This statement is not executed
Console.WriteLine("Result: {0}",dZero);
}
154 Chapter 4 ■ Working with Objects in C#

Listing 4-2 Handling Exceptions Generated by the CLR (continued)


catch(DivideByZeroException ex)
{
Console.WriteLine("{0}\n{1}\n", ex.Message, ex.Source);
Console.WriteLine(ex.TargetSite.ToString());
Console.WriteLine(ex.StackTrace);
}
catch (Exception ex)
{
Console.WriteLine("General "+ex.Message);
}
finally
{
Console.WriteLine("Cleanup occurs here.");
}
}
}

In this example, TestExcep.Calc throws a division by zero exception when


MyApp calls it with a zero value. Because Calc has no code to handle the exception,
the exception is thrown back automatically to MyApp at the point where Calc was
called. From there, control passes to the block provided to handle DivideByZe-
roException exceptions. For demonstration purposes, the statements in the catch
block display the following information provided by the exception object:

Property Value Printed


ex.Message Attempted to divide by zero
ex.Source zeroexcept (assembly name)
ex.TargetSite Void Main()
ex.StackTrace at MyApp.Main()

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

How to Create a Custom Exception Class


Custom exception classes are useful when you need to describe errors in terms of the
class that issues the error. For example, you may want the exception to describe the
specific behavior causing the error or to indicate a problem with a parameter that
does not meet some required criteria. In general, first look to the most specific sys-
tem exception available; if that is inadequate, consider creating your own.
In Listing 4-3, a method throws a custom exception if the object it receives
does not implement the two required interfaces. The exception, NoDescException,
returns a message describing the error and the name of the object causing the
failure.

Listing 4-3 Building a Custom Exception Class


// Custom Exception Class
[Serializable]
public class NoDescException : ApplicationException
{ // Three constructors should be implemented
public NoDescException(){}
public NoDescException(string message):base(message){}
public NoDescException(string message, Exception innerEx)
:base(message, innerEx){ }
}

// Interfaces that shape objects are to implement


public interface IShapeFunction
{ double GetArea(); }
public interface IShapeDescription
{ string ShowMe();}

// Circle and Rectangle classes are defined


class Circle : IShapeFunction
{
private double radius;
public Circle (double rad)
{
radius= rad;
}
// Methods to implement both interfaces
public double GetArea()
{ return (3.14*radius*radius); }
}
156 Chapter 4 ■ Working with Objects in C#

Listing 4-3 Building a Custom Exception Class (continued)


class Rectangle : IShapeFunction, IShapeDescription
{
private int width, height;
public Rectangle(int w, int h)
{
width= w;
height=h;
}
// Methods to implement both interfaces
public double GetArea()
{ return (height*width); }
public string ShowMe()
{ return("rectangle"); }
}

public class ObjAreas


{
public static void ShowAreas(object ObjShape)
{
// Check to see if interfaces are implemented
if (!(ObjShape is IShapeDescription &&
ObjShape is IShapeFunction) )
{
// Throw custom exception
string objName = ObjShape.ToString();
throw new NoDescException
("Interface not implemented for "+objName);
}
// Continue processing since interfaces exist
IShapeFunction myShape = (IShapeFunction)ObjShape;
IShapeDescription myDesc = (IShapeDescription) ObjShape;
string desc = myDesc.ShowMe();
Console.WriteLine(desc+" Area= "+
myShape.GetArea().ToString());
}
}

To view the custom exception in action, let’s create two shape objects and pass
them via calls to the static ObjAreas.ShowAreas method.

Circle myCircle = new Circle(4.0);


Rectangle myRect = new Rectangle(5,2);
try
4.2 Exception Handling 157

{
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:

• The class should be derived from ApplicationException.


• By convention, the exception name should end in Exception.
The Exception base class defines three public constructors that
should be included:
1. A parameterless constructor to serve as the default.
2. A constructor with one string parameter—usually the message.
3. A constructor with a string parameter and an Exception object
parameter that is used when an exception occurs while a previous
exception is being handled.
• Use the base initializer to call the base class to take care of the actual
object creation. If you choose to add fields or properties, add a new
constructor to initialize these values.
• The Serializable attribute specifies that the exception can be seri-
alized, which means it can be represented as XML for purposes of
storing it or transmitting it. Typically, you can ignore this attribute,
because it’s only required if an exception object is being thrown from
code in one application domain to another. Application domains are
discussed Chapter 15, “Code Refinement, Security, and Deployment”;
for now, think of them as logical partitions that .NET uses to isolate
code segments.

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:

static void Main()


{
Application.ThreadException += new
ThreadExceptionEventHandler(UnForgiven.MyUnhandledMethod);
Application.Run(new Form1());
}

The implementation code in the method is straightforward. The most interesting


aspect is the use of preprocessor directives (#if DEBUG) to execute code based on
whether the application is in release or debug mode.
4.2 Exception Handling 159

Listing 4-4 Unhandled Exceptions in a Windows Form


// Class to receive callback to process unhandled exceptions
using System.Diagnostics;
using System.Windows.Forms;
using System.Threading;
public class UnForgiven
{
// Class signature matches ThreadExceptionEventHandler
public static void MyUnhandledMethod
(object sender, ThreadExceptionEventArgs e)
{
#if DEBUG
// Statements for debug mode
// Display trace and start the Debugger
MessageBox.Show("Debug: "+e.ToString());
#else
// Statements for release mode
// Provide output to user and log errors
MessageBox.Show("Release: "+e.ToString());
#endif
}
}

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);

Also, the method’s EventArgs parameter changes to UnhandledException-


EventArgs.

Exception Handling Guidelines


The use of exception handling is the key to implementing code that performs in a sta-
ble manner when unexpected conditions occur. But successful exception handling
requires more than surrounding code with try and catch blocks. Thought must be
given to which exceptions should be caught and what to do with a caught exception.
Included here are some general rules to consider. For a more extensive list, consult
online “best practices” information.
160 Chapter 4 ■ Working with Objects in C#

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

Use exceptions to handle unexpected events, not as a way to implement logic.


Distinguish between error handling and exception handling. Incorrectly formatted
credit card numbers or incorrect passwords, for example, are all occurrences that
should be anticipated and handled by the logic of your code. An unavailable database
table or hardware failure requires an exception.

Don’t catch or throw base exception types.


The base exception type System.Exception is a catch-all exception that can be
used to catch any exception this is not specifically identified and caught. Much like
“fools gold,” its intuitive appeal is deceptive. The objective of exception handling is to
identify specific exceptions and handle with code appropriate for them; catching
each exception prevents the unexpected ones from being identified. As a rule, catch
specific exceptions and allow unidentified ones to propagate up the call stack.

Make liberal use of finally blocks.


The purpose of a finally block is to perform any housekeeping chores before a
method is exited. It is implemented as part of both try/catch/finally and try/
finally constructs. Because code should be more liberal in its use of throws than
catches, an application should contain more try/finally constructs than try/
catch constructs.
The inclusion of thorough, intelligent exception handling code in an application is
one of the hallmarks of well-written code. Although you’ll find code segments in this
book where exception handling is ignored or used sparingly, understand that this is
done only in the interest of simplifying code examples. In real-world programming,
there are no excuses for leaving it out.

4.3 Implementing System.Object


Methods in a Custom Class
When creating a custom class, particularly one that will be available to other develop-
ers, it is important to ensure that it observes the proper rules of object etiquette. It
should be CLS compliant, provide adequate exception handling, and adhere to OOP
4.3 Implementing System.Object Methods in a Custom Class 161

principles of encapsulation and inheritance when providing member accessibility. A


class should also implement the features that .NET developers are accustomed to
when working with Framework classes. These features include a custom implemen-
tation of the System.Object methods that all classes inherit:

• ToString(). By default, this method returns the name of the class. It


should be overridden to display contents of the object that distinguish
it from other instances of the class.
• Equals(). This is used to compare instances of a class to determine if
they are equal. It’s up to the class to define what equality means. This
could mean that two objects have matching field values or that two
objects reference the same memory location.
• MemberwiseClone(). This method returns a copy of an object that
contains the object’s value fields only—referred to as a shallow copy. A
custom class can use this method to return a copy of itself or imple-
ment its own clone method to return a deep copy that also includes
reference type values.

The remainder of this section provides examples of overriding or using these


methods in custom classes. Note that System.Object.Finalize—a method to
perform cleanup duties before an object is claimed by garbage collection—is dis-
cussed in Section 4.6, “Object Life Cycle Management.”

ToString() to Describe an Object


This method is most commonly used with primitive types to convert numbers to a
string format that can be displayed or printed. When used with objects, the default is
to return the fully qualified name of the object: <namespace>.<classname>. It’s
common to override this and return a string containing selected values from mem-
bers of the object. A good example of this is the exception object from the previous
section (refer to Figure 4-3) that returns the Message and StackTrace values:

ex.ToString() // Output:
// Attempted to divide by zero
// at TestExcep.Calc(Int32 j)
// at MyApp.Main()

The code shown in Listing 4-5 demonstrates how easy it is to implement


ToString in your own class.
The StringBuilder class is used to create the text string returned by the
method. It provides an efficient way of handling strings and is described in Chapter
5, “C# Text Manipulation and File I/O.”
162 Chapter 4 ■ Working with Objects in C#

Listing 4-5 Overriding ToString()


using System.Text;
using System;
public class Chair
{
private double myPrice;
private string myVendor, myID;
public Upholstery myUpholstery;
public Chair(double price, string vendor, string sku)
{ myPrice = price;
myVendor = vendor;
myID = sku;
}
// Override System.Object ToString()
public override string ToString()
{
StringBuilder chairSB = new StringBuilder();
chairSB.AppendFormat("ITEM = Chair");
chairSB.AppendFormat(" VENDOR = {0}", this.myVendor);
chairSB.AppendFormat(" PRICE = {0}",
this.myPrice.ToString());
return chairSB.ToString();
}
public string MyVen
{
get {return myVendor;}
}
//... other properties to expose myPrice, myID
}
public class Upholstery
{
public string Fabric ;
public Upholstery( string fab)
{ Fabric = fab; }
}

The following statements create an instance of the object and set desc to the
more meaningful value returned by ToString():

Chair myChair = new Chair(120.0, "Broyhill", "60-1222");


string desc = myChair.ToString());
// Returns ITEM = Chair VENDOR = Broyhill PRICE = 120.0
4.3 Implementing System.Object Methods in a Custom Class 163

Equals() to Compare Objects


When comparing two reference types, Equals returns true only if they point to the
same object in memory. To compare objects based on their value (value equality
rather than referential equality), you must override the default method. An example
of this is the String class—a reference type that implements Equals to perform a
value comparison based on the characters in the strings.
The code in Listing 4-6 illustrates how to override Equals in the Chair class to
compare objects using value semantics. It also overrides the GetHashCode
method—always recommended when overriding Equals.

Listing 4-6 Overriding Equals()


public override bool Equals(Object obj)
{
// Include the following two statements if this class
// derives from a class that overrides Equals()
//if (!base.Equals(obj))
// return false;
// (1) Null objects cannot be compared
if (obj == null) return false;
// (2) Check object types
if (this.GetType() != obj.GetType()) return false;
// (3) Cast object so we can access its fields
Chair otherObj = (Chair) obj;
// (4) Compare reference fields
if (!Object.Equals(myUpholstery,
otherObj.myUpholstery)) return false;
// (5) Compare Value Type members
if (!myVendor.Equals(otherObj.myVendor)) return false;
if (!myPrice.Equals(otherObj.myPrice)) return false;
if (!myID.Equals(otherObj.myID)) return false;
return true;
}
// Override GetHashCode – Required if Equals overridden
public override int GetHashCode()
{
return myID.GetHashCode();
}

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

Override Check for Compare Compare Compare Override


Equals null object object types Ref fields Value field GetHashCode

Figure 4-5 Steps in overriding Equals()

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:

Chair myChair = new Chair(150.0, "Lane", "78-0988");


myChair.myUpholstery = new Upholstery("Silk");
Chair newChair = new Chair(150.0, "Lane", "78-0988");
newChair.myUpholstery= new Upholstery("Silk");
// Next statement returns false. Why?
bool eq = ( myChair.Equals(newChair));

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:

Upholstery otherObj = (Upholstery) obj;


if (!Fabric.Equals(otherObj.Fabric)) return false;

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.

Determining If References Point to the Same Object


After you override the original Equals method to compare object values, your appli-
cation may still need to determine if reference variables refer to the same object.
System.Object has a static method called ReferenceEquals for this purpose. It is
used here with our Chair class:

Chair chair1 = new Chair(120.0, "Broyhill", "66-9888") )


Chair chair2 = new Chair(120.0, "Broyhill", "66-9933") )
Chair chair3 = chair1;
if (Object.ReferenceEquals(chair1, chair3) )
{ MessageBox.Show("Same object");}
else
{ MessageBox.Show("Different objects");}

The method returns true because chair1 and chair3 reference the same
instance.

Cloning to Create a Copy of an Object


The usual way to create an object is by using the new operator to invoke a construc-
tor. An alternative approach is to make a copy or clone of an existing object. The
object being cloned may be a class instance, an array, a string, or any reference type;
primitives cannot be cloned. For a class to be cloneable, it must implement the
ICloneable interface. This is a simple interface defined as

public interface ICloneable


{
Object Clone();
}

It consists of a single method, Clone, that is implemented to create a copy of the


object. The cloned object may represent a shallow copy or a deep copy of the object’s
fields. A shallow copy creates a new instance of the original object type, and then
copies the non-static fields from the original object to the new object. For a refer-
ence type, only a pointer to the value is copied. Thus, the clone points to the same
reference object as the original. A deep copy duplicates everything. It creates a copy
of reference objects and provides a reference to them in the copy.
166 Chapter 4 ■ Working with Objects in C#

Figure 4-6 depicts a shallow and deep copy for this instance of the Chair class:

Chair myChair = new Chair(150.0, "Lane", "78-0988");


Upholstery myFabric = new Upholstery("Silk");
myChair.myUpholstery = myFabric;

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.

myChair myUpholstery myChair myUpholstery

myUpholstery Fabric "Silk" myUpholstery Fabric "Silk"


myPrice 150.0 myPrice 150.0
myVendor "Lane" myVendor "Lane"
myID "78-0998" myID "78-0998"

CLONE CLONE myUpholstery

myUpholstery myUpholstery Fabric "Silk"


myPrice 150.0 myPrice 150.0
myVendor "Lane" myVendor "Lane"
myID "78-0998" myID "78-0998"
Shallow Deep

Figure 4-6 Comparison of shallow and deep copy

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.

How to Create a Shallow Copy


A shallow copy is sufficient when the object to be copied contains no reference type
fields needed by the copy. The easiest way to implement shallow copying is to use the
System.Object.MemberwiseClone method, a protected, non-virtual method that
makes a shallow copy of the object it is associated with. We use this to enable the
Chair class to clone itself:

public class Chair: ICloneable


{
//... other code from Listing 4-5
4.4 Working with .NET Collection Classes and Interfaces 167

public Object Clone()


{
return MemberwiseClone(); // from System.Object
}

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:

// Make clone of myChair


Chair chairClone = (Chair)myChair.Clone();
bool isEqual;
// (1) Following evaluates to false
isEqual = Object.ReferenceEquals(myChair,chairClone);
// (2) Following evaluates to true
isEqual = Object.ReferenceEquals(
myChair.myUpholstery,chairClone.myUpholstery);

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.

4.4 Working with .NET Collection


Classes and Interfaces
In .NET, collection is a general term that applies to the set of classes that represent
the classic data structures used to group related types of data. These classes include
stacks, queues, hash tables, arrays, and dictionaries. All are contained in one of two
namespaces: System.Collections or System.Collections.Generic. In .NET
versions 1.0 and 1.1, System.Collections was home to all collection classes. In
the .NET 2.0 release, these original classes were revised to support generics—a way
to make collections type-safe.
The .NET developers left the original namespace in for backward compatibility
and added the System.Collections.Generic namespace to contain the generic
classes. With the exception of the generics features, the classes in the two
namespaces offer the same functionality—although there are a few name changes.
To avoid confusion, much of this section refers to the System.Collections
namespace. The details of generics are presented at the end of the section.
168 Chapter 4 ■ Working with Objects in C#

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.

Table 4-2 System.Collections

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.

IComparer Exposes a method that is used to compare two objects and


plays a vital role in allowing objects in a collection to be sorted.

IDictionary Allows an object to represent its data as a collection of


key-and-value pairs.

IDictionaryEnumerator Permits iteration through the contents of an object that imple-


ments the IDictionary interface.

IEnumerator Supports a simple iteration through a collection. The iteration


IEnumerable only supports reading of data in the collection.

IHashCodeProvider Supplies a hash code for an object. It contains one method:


GetHashCode(object obj)

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>> <<Interface>> <<Interface>>


IEnumerable IEnumerator IComparer
Current
GetEnumerator MoveNext Compare
Reset

<<Interface>>
ICollection <<Interface>>
IDictionaryEnumerator
Count
IsSynchronized Entry
SyncRoot Key
CopyTo Value

<<Interface>> <<Interface>>
IList IDictionary

... ...

Figure 4-7 System.Collections Interface diagram

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#

a collection while it is being accessed by multiple clients. It’s analogous to locking


records in a database to prevent conflicting updates by multiple users.

Table 4-3 ICollection Members

Member Description

int Count Property that returns the number of entries in the col-
lection.

bool IsSynchronized Property that indicates if access to the collection is


thread-safe.

object SyncRoot Property that returns an object that can be used to


synchronize access to a collection by different threads.

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.

Using the IEnumerable and IEnumerator


Interfaces to List the Contents of a Collection
For a class to support iteration using the foreach construct, it must implement the
IEnumerable and IEnumerator interfaces. IEnumerator defines the methods and
properties used to implement the enumerator pattern; IEnumerable serves only to
return an IEnumerator object. Table 4-4 summarizes the member(s) provided by
each interface.
These members are implemented in a custom collection class as a way to enable
users to traverse the objects in the collection. To understand how to use these mem-
bers, let’s look at the underlying code that implements the foreach construct.
The foreach loop offers a simple syntax:

foreach ( ElementType element in collection)


{
Console.WriteLine(element.Name);
}
4.4 Working with .NET Collection Classes and Interfaces 171

Table 4-4 IEnumerable and IEnumerator

Member Description

IEnumerable.GetEnumerator() Returns an IEnumerator object that is used to


iterate through collection.

IEnumerator.Current Property that returns the current value in a collection.

IEnumerator.MoveNext() Advances the enumerator to the next item in the


collection. Returns bool.

IEnumerator.Reset() Sets the enumerator to its beginning position in the


collection. This method is not included in the
Generic namespace.

The compiler expands the foreach construct into a while loop that employs
IEnumerable and IEnumerator members:

// Get enumerator object using collection's GetEnumerator method


IEnumerator enumerator =
((IEnumerable) (collection)).GetEnumerator();
try
{
while (enumerator.MoveNext())
{
ElementType element = (ElementType)enumerator.Current;
Console.WriteLine(element.Name);
}
}
finally
// Determine if enumerator implements IDisposable interface
// If so, execute Dispose method to release resources
{
IDisposable disposable = enumerator as System.IDisposable;
If( disposable !=null) disposable.Dispose();
}

When a client wants to iterate across the members of a collection (enumerable), it


obtains an enumerator object using the IEnumerable.GetEnumerator method.
The client then uses the members of the enumerator to traverse the collection:
MoveNext() moves to the next element in the collection, and the Current property
returns the object referenced by the current index of the enumerator. Formally, this
is an implementation of the iteration design pattern.
172 Chapter 4 ■ Working with Objects in C#

Core Note

Enumerating through a collection is intrinsically not a thread-safe


procedure. If another client (thread) modifies the collection during the
enumeration, an exception is thrown. To guarantee thread safety, lock
the collection prior to traversing it:
lock( myCollection.SyncRoot )
{
foreach ( Object item in myCollection )
{
// Insert your code here
}
}

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

Listing 4-7 Using Iterators to Implement Enumeration


using System;
using System.Collections.Generic;
public class GenStack<T>: IEnumerable<T>
{
// Use generics type parameter to specify array type
private T[] stackCollection;
private int count = 0;
// Constructor
public GenStack(int size)
{
stackCollection = new T[size];
}
// (1) Iterator
public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < count; i++)
{
yield return stackCollection[i];
}
}
// (2) Property to return the collection in reverse order
public IEnumerable<T> Reverse
{
get
{
for (int i = count - 1; i >= 0; i--)
{
yield return stackCollection[i];
}
}
}
public void Add(T item)
{
stackCollection[count] = item;
count += 1;
}
// other class methods go here ...

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.

GenStack<string> myStack = new GenStack<string>(10);


myStack.Add("Aida");
myStack.Add("La Boheme");
myStack.Add("Carmen");
// uses enumerator from GetEnumerator()
foreach (string s in myStack)
Console.WriteLine(s);
// uses enumerator from Reverse property
foreach (string s in myStack.Reverse)
Console.WriteLine(s);

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

In order to stop iteration before an entire collection is traversed, use the


yield break keywords. This causes MoveNext() to return false.

There are some restrictions on using yield return or yield break:

• It can only be used inside a method, property (get accessor), or


operator.
• It cannot be used inside a finally block, an anonymous method, or a
method that has ref or out arguments.
• The method or property containing yield return must have a return
type of Collections.IEnumerable, Collections.Generic.
IEnumerable<>, Collections.IEnumerator, or Collections.
Generic.IEnumerator<>.
4.4 Working with .NET Collection Classes and Interfaces 175

Using the IComparable and IComparer


Interfaces to Perform Sorting
Chapter 2, “C# Language Fundamentals,” included a discussion of the Sys-
tem.Array object and its associated Sort method. That method is designed for
primitive types such as strings and numeric values. However, it can be extended to
work with more complex objects by implementing the IComparable and ICom-
parer interfaces on the objects to be sorted.

IComparable
Unlike the other interfaces in this section, IComparable is a member of the System
namespace. It has only one member, the method CompareTo:

int CompareTo(Object obj)

Returned Value Condition


Less than 0 Current instance < obj
0 Current instance = obj
Greater than 0 Current instance > obj

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.

public class Chair : ICloneable, IComparable


{
private double myPrice;
private string myVendor, myID;
public Upholstery myUpholstery;
//… Constructor and other code
// Add implementation of CompareTo to sort in ascending
int IComparable.CompareTo(Object obj)
{
if (obj is Chair) {
Chair castObj = (Chair)obj;
if (this.myPrice > castObj.myPrice)
return 1;
if (this.myPrice < castObj.myPrice)
return -1;
else return 0;
176 Chapter 4 ■ Working with Objects in C#

// Reverse 1 and –1 to sort in descending order


}
throw new ArgumentException("object in not a Chair");
}

The code to sort an array of Chair objects is straightforward because all the work
is done inside the Chair class:

Chair[]chairsOrdered = new Chair[4];


chairsOrdered[0] = new Chair(150.0, "Lane","99-88");
chairsOrdered[1] = new Chair(250.0, "Lane","99-00");
chairsOrdered[2] = new Chair(100.0, "Lane","98-88");
chairsOrdered[3] = new Chair(120.0, "Harris","93-9");
Array.Sort(chairsOrdered);
// Lists in ascending order of price
foreach(Chair c in chairsOrdered)
MessageBox.Show(c.ToString());

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:

public class CompareByVen : IComparer


{
public CompareByVen() { }
int IComparer.Compare(object obj1, object obj2)
{
// obj1 contains array being sorted
// obj2 is instance of helper sort class
Chair castObj1 = (Chair)obj1;
Chair castObj2 = (Chair)obj2;
return String.Compare
(castObj1.myVendor,castObj2.myVendor);
}
}
4.4 Working with .NET Collection Classes and Interfaces 177

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

ICollection IList IDictionary

Stack ArrayList HashTable


Queue SortedList
Figure 4-8 Selected classes in System.Collections

The purpose of interfaces is to provide a common set of behaviors for classes.


Thus, rather than look at the details of all the classes, we will look at the interfaces
themselves as well as some representative classes that illustrate how the members of
the interfaces are implemented. We’ve already looked at ICollection, so let’s begin
with two basic collections that inherit from it.

Stack and Queue


The Stack and the Queue are the simplest of the collection classes. Because they do
not inherit from IList or IDictionary, they do not provide indexed or keyed
access. The order of insertion controls how objects are retrieved from them. In a
Stack, all insertions and deletions occur at one end of the list; in a Queue, insertions
are made at one end and deletions at the other. Table 4-5 compares the two.
178 Chapter 4 ■ Working with Objects in C#

Table 4-5 Stack and Queue—Selected Members and Features

Description Stack Queue

Method of maintaining Last-in, first-out (LIFO) First-in, first-out (FIFO)


data

Add an item Push() EnQueue()

Remove an item Pop() DeQueue()

Return the current Peek() Peek()


item without removing
it

Determine whether an Includes() Includes()


item is in the collection

Constructors Stack() Queue()


• Empty stack with default • Default capacity and growth
capacity factor
Stack(ICollection) Queue(ICollection)
• Stack is filled with received • Filled with received
collection collection
Stack(int) Queue(int)
• Set stack to initial int • Set queue to initial int
capacity capacity
Queue(int, float)
• Set initial capacity and
growth factor

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.

Listing 4-8 Using the Stack Container


public class ShowStack {
public static void Main()
{
Stack myStack = new Stack();
myStack.Push(new Chair(250.0, "Adams Bros.", "87-00" ));
myStack.Push(new Chair(100.0, "Broyhill","87-04" ));
myStack.Push(new Chair(100.0, "Lane","86-09" ));
PrintValues( myStack ); // Adams – Broyhill - Lane
4.4 Working with .NET Collection Classes and Interfaces 179

Listing 4-8 Using the Stack Container (continued)


// Pop top object and push a new one on stack
myStack.Pop();
myStack.Push(new Chair(300.0, "American Chair"));
Console.WriteLine(myStack.Peek().ToString()); // American
}
public static void PrintValues( IEnumerable myCollection )
{
System.Collections.IEnumerator myEnumerator =
myCollection.GetEnumerator();
while ( myEnumerator.MoveNext() )
Consle.WriteLine(myEnumerator.Current.ToString());
// Could list specific chair fields with
// myChair = (Chair) myEnumerator.Current;
}
}

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#

ArrayList chairList = new ArrayList( );


// alternative: ArrayList chairList = new ArrayList(5);
chairList.Add(new Chair(350.0, "Adams", "88-00"));
chairList.Add(new Chair(380.0, "Lane", "99-33"));
chairList.Add(new Chair(250.0, "Broyhill", "89-01"));
PrintValues(chairList); // Adams – Lane - Broyhill
//
chairList.Insert(1,new Chair(100,"Kincaid"));
chairList.RemoveAt(2);
Console.WriteLine("Object Count: {0}",chairList.Count);
//
PrintValues(chairList); // Adams – Kincaid - Broyhill
// Copy objects to an array
Object chairArray = chairList.ToArray();
PrintValues((IEnumerable) chairArray);

Table 4-6 IList Members

Interface Description

bool IsFixedSize Indicates whether the collection has a fixed size. A


fixed size prevents the addition or removal of items
after the collection is created.

bool IsReadOnly Items in a collection can be read but not modified.

int IndexOf(object) Determines the index of a specific item in the


collection.

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.

void Clear() Remove all items from a collection.

bool Contains(object) Returns true if a collection contains an item with a


specified value.

This parameterless declaration of ArrayList causes a default amount of memory


to be initially allocated. You can control the initial allocation by passing a size param-
eter to the constructor that specifies the number of elements you expect it to hold. In
both cases, the allocated space is automatically doubled if the capacity is exceeded.
4.4 Working with .NET Collection Classes and Interfaces 181

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:

public class Hashtable : IDictionary, ICollection, IEnumerable,


ISerializable, IDeserializationCallback, ICloneable

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.

Table 4-7 IDictionary Member

Member Description

bool IsFixedSize Indicates whether IDictionary has a fixed size. A fixed


size prevents the addition or removal of items after the col-
lection is created.

bool IsReadOnly Elements in a collection can be read but not modified.

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.

bool Contains Returns true if a collection contains an element with a


specified key.

IDictionaryEnumerator Returns an instance of the IDictionaryEnumerator


GetEnumerator () type that is required for enumerating through a dictionary.
182 Chapter 4 ■ Working with Objects in C#

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.

Table 4-8 IDictionaryEnumerator Members

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

Hashtable chairHash = new Hashtable(1000, .6)

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:

// Check for existence of a key


bool keyFound;
if (chairHash.ContainsKey("88-00"))
{ keyFound = true;}
else
{keyFound = false;}

List Keys in a Hashtable


The following iterates through the Keys property collection:

// List Keys
foreach (string invenKey in chairHash.Keys)
{ MessageBox.Show(invenKey); }

List Values in a Hashtable


These statements iterate through the Values in a hash table:

// List Values
foreach (Chair chairVal in chairHash.Values)
{ MessageBox.Show(chairVal.myVendor);}

List Keys and Values in a Hashtable


This code lists the keys and values in a hash table:

foreach ( DictionaryEntry deChair in chairHash)


{
Chair obj = (Chair) deChair.Value;
MessageBox.Show(deChair.Key+" "+ obj.myVendor);
}
184 Chapter 4 ■ Working with Objects in C#

The entry in a Hashtable is stored as a DictionaryEntry type. It has a Value


and Key property that expose the actual value and key. Note that the value is
returned as an object that must be cast to a Chair type in order to access its fields.

Core Note

According to .NET documentation (1.x), a synchronized version of a


Hashtable that is supposed to be thread-safe for a single writer and
concurrent readers can be created using the Synchronized method:
Hashtable safeHT = Hashtable.Synchronized(newHashtable());
Unfortunately, the .NET 1.x versions of the Hashtable have been
proven not to be thread-safe for reading. Later versions may correct this
flaw.

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

ArrayList primes = new ArrayList();


primes.Add(1);
primes.Add(3);
int pSum = (int)primes[0] + (int)primes[1];
primes.Add("test");

can be replaced with the generics version:

List<int> primes = new List<int>();


primes.Add(1);
primes.Add(3);
int pSum = primes[0] + primes[1];
primes.Add("text"); // will not compile

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.

Comparison of System.Collections and


System.Collections.Generic Namespaces
As the following side-by-side comparison shows, the classes in the two namespaces
share the same name with three exceptions: Hashtable becomes Dictionary<>,
ArrayList becomes List<>, and SortedList is renamed SortedDictionary<>.

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.

An Example Using a Generics Collections Class


Switching to the generics versions of the collection classes is primarily a matter of
getting used to a new syntax, because the functionality provided by the generics and
non-generics classes is virtually identical. To demonstrate, here are two examples.
The first uses the Hashtable to store and retrieve instances of the Chair class
(defined in Listing 4-5); the second example performs the same functions but uses
the Dictionary class—the generics version of the Hashtable.
This segment consists of a Hashtable declaration and two methods: one to store
a Chair object in the table and the other to retrieve an object based on a given key
value.

// Example 1: Using Hashtable


public Hashtable ht = new Hashtable();
// Store Chair object in table using a unique product identifier
private void saveHT(double price, string ven, string sku)
{
if (!ht.ContainsKey(sku))
{
ht.Add(sku, new Chair(price,ven,sku));
}
}
// Display vendor and price for a product identifier
private void showChairHT(string sku)
{
if (ht.ContainsKey(key))
{
if (ht[key] is Chair) // Prevent casting exception
{
Chair ch = (Chair)ht[sku];
Console.WriteLine(ch.MyVen + " " + ch.MyPr);
}
else
{ Console.WriteLine("Invalid Type: " +
(ht[key].GetType().ToString()));
}
}
}

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.

// Example 2: Using Generics Dictionary to replace Hashtable


// Dictionary accepts string as key and Chair as data type
Dictionary<string,Chair> htgen = new Dictionary<string,Chair>();
//
private void saveGen(double price, string ven, string sku)
{
if (!htgen.ContainsKey(sku))
{
htgen.Add(sku, new Chair(price,ven,sku));
}
}

private void showChairGen(string sku)


{
if (htgen.ContainsKey(key))
{
Chair ch = htgen[sku]; // No casting required
Console.WriteLine(ch.MyVen + " " + ch.MyPr);
}
}

The important advantage of generics is illustrated in the showChairGen method.


It has no need to check the type of the stored object or perform casting.
In the long run, the new generic collection classes will render the classes in the
System.Collections namespace obsolete. For that reason, new code develop-
ment should use the generic classes where possible.

4.5 Object Serialization


In .NET, serialization refers to the process of converting an object or collection of
objects into a format suitable for streaming across a network—a Web Service, for
example—or storing in memory, a file, or a database. Deserialization is the reverse
process that takes the serialized stream and converts it back into its original object(s).
188 Chapter 4 ■ Working with Objects in C#

.NET support three primary types of serialization:

• Binary. Uses the BinaryFormatter class to serialize a type into a


binary stream.
• SOAP. Uses the SoapFormatter class to serialize a type into XML
formatted according to SOAP (Simple Object Access Protocol)
standards.
• XML. Uses the XmlSerializer class to serialize a type into basic
XML (described in Chapter 10, “Working with XML in .NET”). Web
Services uses this type of serialization.

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.

Listing 4-9 Serialization of a Hashtable


using System;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
4.5 Object Serialization 189

Listing 4-9 Serialization of a Hashtable (continued)


// Store Chair objects in a Hashtable
Hashtable ht = new Hashtable();
// Chair and Upholstery must have [Serializable] attribute
Chair ch = new Chair(100.00D, "Broyhill", "10-09");
ch.myUpholstery = new Upholstery("Cotton");
ht.Add("10-09", ch);
// Add second item to table
ch = new Chair(200.00D, "Lane", "11-19");
ch.myUpholstery = new Upholstery("Silk");
ht.Add("11-19", ch);
// (1) Serialize
// Create a new file; if file exits it is overwritten
FileStream fs= new FileStream("c:\\chairs.dat",
FileMode.Create);
BinaryFormatter bf= new BinaryFormatter();
bf.Serialize(fs,ht);
fs.Close();
// (2) Deserialize binary file into a Hashtable of objects
ht.Clear(); // Clear hash table.
fs = new FileStream("c:\\chairs.dat", FileMode.Open);
ht = (Hashtable) bf.Deserialize(fs);
// Confirm objects properly recreated
ch = (Chair)ht["11-19"];
Console.WriteLine(ch.myUpholstery.Fabric); // "Silk"
fs.Close();

Observe the following key points:

• The serialization and IO namespaces should be declared.


• The Chair and Upholstery classes must have the [Serializable]
attribute; otherwise, a runtime error occurs when Serialize()is
executed.
• Serialization creates an object graph that includes references from one
object to another. The result is a deep copy of the objects. In this
example, the myUpholstery field of Chair is set to an instance of the
Upholstery class before it is serialized. Serialization stores a copy of
the object—rather than a reference. When deserialization occurs, the
Upholstery object is restored.
190 Chapter 4 ■ Working with Objects in C#

Excluding Class Members from Serialization


You can selectively exclude class members from being serialized by attaching the
[NonSerialized] attribute to them. For example, you can prevent the myUphol-
stery field of the Chair class from being serialized by including this:

[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

A class cannot inherit the Serializable attribute from a base class; it


must explicitly include the attribute. On the other hand, a derived class
can be made serializable only if its base class is serializable.

Binary Serialization Events


.NET 2.0 introduced support for four binary serialization and deserialization events,
as summarized in Table 4-9.

Table 4-9 Serialization and Deserialization Events

Event Attribute Description

OnSerializing [Serializing] Occurs before objects are serialized. Event


handler is called for each object to be serialized.

OnSerialized [Serialized] Occurs after objects are serialized. Event


handler is called once for each object serialized.

OnDeserializing [Deserializing] Occurs before objects are deserialized. Event


handler is called once for each object to be
deserialized.

OnDeserialized [Deserialized] Occurs after objects have been deserialized.


Event handler is called for each deserialized
object.
4.5 Object Serialization 191

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:

void <event name>(StreamingContext context)

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.

public class Chair


{
// other code here
[OnDeserialized]
void OnDeserialized(StreamingContext context)
{
// Edit vendor name after object is created
if (MyVen == "Lane") MyVen = "Lane Bros.";
}
}

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.

Handling Version Changes to a Serialized Object


Suppose the Chair class from the preceding examples is redesigned. A field could be
added or deleted, for example. What happens if one then attempts to deserialize
objects in the old format to the new format? It’s not an uncommon problem, and
.NET offers some solutions.
If a field is deleted, the binary formatter simply ignores the extra data in the dese-
rialized stream. However, if the formatter detects a new field in the target object, it
throws an exception. To prevent this, an [OptionalField] attribute can be
attached to the new field(s). Continuing the previous example, let’s add a field to
Chair that designates the type of wood finish:

[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#

void OnDeserialized(StreamingContext context)


{
if (MyVen == "Lane") finish = "Oak"; else finish = "Cherry";
}

4.6 Object Life Cycle Management


Memory allocation and deallocation have always been the bane of developers. Even
the most experienced C++ and COM programmer is faced with memory leaks and
attempts to access nonexistent objects that either never existed or have already been
destroyed. In an effort to remove these responsibilities from the programmer, .NET
implements a memory management system that features a managed heap and auto-
matic Garbage Collection.
Recall from Chapter 2, “C# Language Fundamentals,” that the managed heap is a
pre-allocated area of memory that .NET uses to store reference types and data. Each
time an instance of a class is created, it receives memory from the heap. This is a
faster and cleaner solution than programming environments that rely on the operat-
ing system to handle memory allocation.
Allocating memory from the stack is straightforward: A pointer keeps track of the
next free memory address and allocates memory from the top of the heap. The
important thing to note about the allocated memory is that it is always contiguous.
There is no fragmentation or complex overhead to keep track of free memory blocks.
Of course, at some point the heap is exhausted and unused space must be recovered.
This is where the .NET automatic Garbage Collection comes into play.

.NET Garbage Collection


Each time a managed object is created, .NET keeps track of it in a tree-like graph of
nodes that associates each object with the object that created or uses it. In addition,
each time another client references an object or a reference is assigned to a variable,
the graph is updated. At the top of this graph is a list of roots, or parts of the applica-
tion that exist as long at the program is running (see Figure 4-9). These include static
variables, CPU registers, and any local or parameter variables that refer to objects on
the managed heap. These serve as the starting point from which the .NET Frame-
work uses a reference-tracing technique to remove objects from the heap and
reclaim memory.
The Garbage Collection process begins when some memory threshold is reached.
At this point, the Garbage Collector (GC) searches through the graph of objects and
marks those that are “reachable.” These are kept alive while the unreachable ones
are considered to be garbage. The next step is to remove the unreferenced objects
4.6 Object Life Cycle Management 193

(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.

Managed Finalization Managed Finalization


Heap Queue Heap Queue
Roots Object G
Object J Object F Object F
References to Objects

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

Figure 4-9 .NET Garbage Collection process

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:

• Finalization degrades performance due to the increased overhead.


Only use it when the object holds resources not managed by the CLR.
• Objects may be placed in the freachable queue in any order. There-
fore, your Finalize code should not reference other objects that use
finalization, because they may have already been processed.
• Call the base Finalize method within your Finalize method so it
can perform any cleanup: base.Finalize().
• Finalization code that fails to complete execution prevents the back-
ground thread from executing the Finalize method of any other
objects in the queue. Infinite loops or synchronization locks with infi-
nite timeouts are always to be avoided, but are particularly deleterious
when part of the cleanup code.

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:

Public class Chair


{
public Chair() { }
~Chair() // Destructor
{ // finalization code }
}

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.

Listing 4-10 Pattern for Calling Dispose()


public class MyConnections: IDisposable
{
public void Dispose()
{
// code to dispose of resources
base.Dispose(); // include call to base Dispose()
}
public void UseResources() { }
}
// Client code to call Dispose()
class MyApp
{
public static void Main()
{
MyConnections connObj;
connObj = new MyConnections();
try
{
connObj.UseResources();
}
finally // Call dispose() if it exists
{
IDisposable testDisp;
testDisp = connObj as IDisposable;
if(testDisp != null)
{ testDisp.Dispose(); }
}
}
196 Chapter 4 ■ Working with Objects in C#

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() }

Using Dispose and Finalize


When Dispose is executed, the object’s unmanaged resources are released and the
object is effectively disposed of. This raises a couple of questions: First, what hap-
pens if Dispose is called after the resources are released? And second, if Finalize
is implemented, how do we prevent the GC from executing it since cleanup has
already occurred?
The easiest way to handle calls to a disposed object’s Dispose method is to raise
an exception. In fact, the ObjectDisposedException exception is available for this
purpose. To implement this, add a boolean property that is set to true when Dispose
is first called. On subsequent calls, the object checks this value and throws an excep-
tion if it is true.
Because there is no guarantee that a client will call Dispose, Finalize should
also be implemented when resource cleanup is required. Typically, the same cleanup
method is used by both, so there is no need for the GC to perform finalization if
Dispose has already been called. The solution is to execute the SuppressFinalize
method when Dispose is called. This static method, which takes an object as a
parameter, notifies the GC not to place the object on the freachable queue.
Listing 4-11 shows how these ideas are incorporated in actual code.

Pattern for Implementing Dispose() and


Listing 4-11
Finalize()

public class MyConnections: IDisposable


{
private bool isDisposed = false;
protected bool Disposed
{
get{ return isDisposed;}
}
public void Dispose()
{
if (isDisposed == false)
{
4.6 Object Life Cycle Management 197

Pattern for Implementing Dispose() and


Listing 4-11
Finalize() (continued)

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();
}
}

The key features of this code include the following:

• A common method, CleanUp, has been introduced and is called from


both Dispose and Finalize . It is defined as protected and virtual,
and contains no concrete code.
• Classes that inherit from the base class MyConnections are responsi-
ble for implementing the CleanUp. As part of this, they must be sure
to call the Cleanup method of the base class. This ensures that
cleanup code runs on all levels of the class hierarchy.
198 Chapter 4 ■ Working with Objects in C#

• The read-only property Disposed has been added and is checked


before methods in the base class are executed.

In summary, the .NET Garbage Collection scheme is designed to allow program-


mers to focus on their application logic and not deal with details of memory alloca-
tion and deallocation. It works well as long as the objects are dealing with managed
resources. However, when there are valuable unmanaged resources at stake, a deter-
ministic method of freeing them is required. This section has shown how the Dis-
pose and Finalize methods can be used in concert to manage this aspect of an
object’s life cycle.

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.

4.8 Test Your Understanding


1. What are the advantages of using a class factory to create objects?

2. Which class should custom exceptions inherit from? Which construc-


tors should be included?
4.8 Test Your Understanding 199

3. How does the default System.Object.Equals method determine if


two objects are equal?

4. Which interface(s) must a class implement in order to support the


foreach statement?

5. What is the main advantage of using generics?

6. What is the purpose of implementing IDisposable on a class?

7. Refer to the following code:


public class test: ICloneable
{
public int Age;
public string Name;
public test(string myname)
{ Name = myname; }
public Object Clone()
{ return MemberwiseClone(); }
}
// Create instances of class
test myTest = new test("Joanna");
myTest.Age = 36;
test clone1 = (test) mytest.Clone();
test clone2 = myTest;
Indicate whether the following statements evaluate to true or false:
a. Object.ReferenceEquals(myTest.Name, clone1.Name)
b. Object.ReferenceEquals(myTest.Age, clone1.Age)
c. myTest.Name = "Julie";
Object.ReferenceEquals(myTest.Name, clone1.Name)
d. Object.ReferenceEquals(myTest.Name, clone2.Name)

8. How does implementing Finalize on an object affect its Garbage


Collection?
CREATING
APPLICATIONS
USING THE
.NET FRAMEWORK
CLASS LIBRARY
II
■ Chapter 5
C# Text Manipulation and File I/O 202

■ 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

Topics in This Chapter

• Characters and Unicode: By default, .NET stores a character as a


16-bit Unicode character. This enables an application to support
international character sets—a technique referred to as localization.
• String Overview: In .NET, strings are immutable. To use strings
efficiently, it is necessary to understand what this means and how
immutability affects string operations.
• String Operations: In addition to basic string operations, .NET
supports advanced formatting techniques for numbers and dates.
• StringBuilder: The StringBuilder class offers an efficient
alternative to concatenation for constructing screens.
• Regular Expressions: The .NET Regex class provides an engine
that uses regular expressions to parse, match, and extract values
in a string.
• Text Streams: Stream classes permit data to be read and written as
a stream of bytes that can be encrypted and buffered.
• Text Reading and Writing: The StreamReader and
StreamWriter classes make it easy to read from and write to
physical files.
• System.IO: Classes in this namespace enable an application to
work with underlying directory structure of the host operating
system.
5

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

5.1 Characters and Unicode


One of the watershed events in computing was the introduction of the ASCII 7-bit
character set in 1968 as a standardized encoding scheme to uniquely identify alpha-
numeric characters and other common symbols. It was largely based on the Latin
alphabet and contained 128 characters. The subsequent ANSI standard doubled the
number of characters—primarily to include symbols for European alphabets and
currencies. However, because it was still based on Latin characters, a number of
incompatible encoding schemes sprang up to represent non-Latin alphabets such as
the Greek and Arabic languages.
Recognizing the need for a universal encoding scheme, an international consor-
tium devised the Unicode specification. It is now a standard, accepted worldwide,
that defines a unique number for every character “no matter what the platform, no
matter what the program, no matter what the language.”1

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.

Figure 5-1 Unicode memory layout of a character

1. Unicode Consortium—www.unicode.org.
5.1 Characters and Unicode 205

Unicode characters have a unique identifier made up of a name and value,


referred to as a code point. The current version 4.0 defines identifiers for 96,382
characters. These characters are grouped in over 130 character sets that include lan-
guage scripts, symbols for math, music, OCR, geometric shapes, Braille, and many
other uses.
Because 16 bits cannot represent the nearly 100,000 characters supported world-
wide, more bytes are required for some character sets. The Unicode solution is a
mechanism by which two sets of 16-bit units define a character. This pair of code
units is known as a surrogate pair. Together, this high surrogate and low surrogate
represent a single 32-bit abstract character into which characters are mapped. This
approach supports over 1,000,000 characters. The surrogates are constructed from
values that reside in a reserved area at the high end of the Unicode code space so
that they are not mistaken for actual characters.
As a developer, you can pretty much ignore the details of whether a character
requires 16 or 32 bits because the .NET API and classes handle the underlying
details of representing Unicode characters. One exception to this—discussed later in
this section—occurs if you parse individual bytes in a stream and need to recognize
the surrogates. For this, .NET provides a special object to iterate through the bytes.

Core Note

Unicode characters can only be displayed if your computer has a font


supporting them. On a Windows operating system, you can install a font
extension (ttfext.exe) that displays the supported Unicode ranges for
a .ttf font. To use it, right-click the .ttf font name and select
Properties. Console applications cannot print Unicode characters
because console output always displays in a non-proportional typeface.

Working with Characters


A single character is represented in .NET as a char (or Char) structure. The char
structure defines a small set of members (see char in Chapter 2, “C# Language Fun-
damentals”) that can be used to inspect and transform its value. Here is a brief
review of some standard character operations.

Assigning a Value to a Char Type


The most obvious way to assign a value to a char variable is with a literal value. How-
ever, because a char value is represented internally as a number, you can also assign
it a numeric value. Here are examples of each:
206 Chapter 5 ■ C# Text Manipulation and File I/O

string klm = "KLM";


byte b = 75;
char k;
// Different ways to assign 'K' to variable K
k = 'K';
k = klm[0]; // Assign "K" from first value in klm
k = (char) 75; // Cast decimal
k = (char) b; // cast byte
k = Convert.ToChar(75); // Converts value to a char

Converting a Char Value to a Numeric Value


When a character is converted to a number, the result is the underlying Unicode
(ordinal) value of the character. Casting is the most efficient way to do this, although
Convert methods can also be used. In the special case where the char is a digit and
you want to assign the linguistic value—rather than the Unicode value—use the
static GetNumericValue method.

// '7' has Unicode value of 55


char k = '7';
int n = (int) k; // n = 55
n = (int) char.GetNumericValue(k); // n = 7

Characters and Localization


One of the most important features of .NET is the capability to automatically recog-
nize and incorporate culture-specific rules of a language or country into an applica-
tion. This process, known as localization, may affect how a date or number is
formatted, which currency symbol appears in a report, or how string comparisons are
carried out. In practical terms, localization means a single application would display
the date May 9, 2004 as 9/5/2004 to a user in Paris, France and as 5/9/2004 to a user
in Paris, Texas. The Common Language Runtime (CLR) automatically recognizes
the local computer’s culture and makes the adjustments.
The .NET Framework provides more than a hundred culture names and identifi-
ers that are used with the CultureInfo class to designate the language/country to
be used with culture sensitive operations in a program. Although localization has a
greater impact when working with strings, the Char.ToUpper method in this exam-
ple is a useful way to demonstrate the concept.

// Include the System.Globalization namespace


// Using CultureInfo – Azerbaijan
char i = 'i';
// Second parameter is false to use default culture settings
// associated with selected culture
CultureInfo myCI = new CultureInfo("az", false );
i = Char.ToUpper(i,myCI);
5.1 Characters and Unicode 207

An overload of ToUpper() accepts a CultureInfo object that specifies the cul-


ture (language and country) to be used in executing the method. In this case, az
stands for the Azeri language of the country Azerbaijan (more about this follows).
When the Common Language Runtime sees the CultureInfo parameter, it takes
into account any aspects of the culture that might affect the operation. When no
parameter is provided, the CLR uses the system’s default culture.

Core Note

On a Windows operating system, the .NET Framework obtains its default


culture information from the system’s country and language settings. It
assigns these values to the Thread.CurrentThread.CurrentCulture
property. You can set these options by choosing Regional Options in the
Control Panel.

So why choose Azerbaijan, a small nation on the Caspian Sea, to demonstrate


localization? Among all the countries in the world that use the Latin character set,
only Azerbaijan and Turkey capitalize the letter i not with I (U+0049), but with an I
that has a dot above it (U+0130). To ensure that ToUpper() performs this operation
correctly, we must create an instance of the CultureInfo class with the Azeri cul-
ture name—represented by az—and pass it to the method. This results in the cor-
rect Unicode character—and a satisfied population of 8.3 million Azerbaijani.

Characters and Their Unicode Categories


The Unicode Standard classifies Unicode characters into one of 30 categories. .NET
provides a UnicodeCategory enumeration that represents each of these categories
and a Char.GetUnicodecategory() method to return a character’s category. Here
is an example:

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

The method correctly identifies K as an UppercaseLetter and the carriage


return as a Control character. As an alternative to the unwieldy GetUnicodeCate-
gory, char includes a set of static methods as a shortcut for identifying a character’s
Unicode category. They are nothing more than wrappers that return a true or
false value based on an internal call to GetUnicodeCategory. Table 5-1 lists these
methods.
208 Chapter 5 ■ C# Text Manipulation and File I/O

Table 5-1 Char Methods That Verify Unicode Categories

Unicode
Method Category Description

IsControl 4 Control code whose Unicode value is U+007F, or in


the range U+0000 through U+001F, or U+0080
through U+009F.

IsDigit 8 Is in the range 0–9.

IsLetter 0, 1, 2, 4 Letter.

IsLetterorDigit 0, 1, 8, Union of letters and digits.

IsLower 1 Lowercase letter.

IsUpper 0 Uppercase letter.

IsPunctuation 18, 19, 20, 21, Punctuation symbol—for example, DashPunctua-


22, 23, 24 tion(19) or OpenPunctuation(20), OtherPunc-
tuation(24).

IsSeparator 11, 12, 13 Space separator, line separator, paragraph separator.

IsSurrogate 16 Value is a high or low surrogate.

IsSymbol 25, 26, 28 Symbol.

IsWhiteSpace 11 Whitespace can be any of these characters: space


(0x20), carriage return (0x0D), horizontal tab (0x09),
line feed (0x0A), form feed (0x0C), or vertical tab
(0x0B).

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

5.2 The String Class


The System.String class was introduced in Chapter 2. This section expands that
discussion to include a more detailed look at creating, comparing, and formatting
strings. Before proceeding to these operations, let’s first review the salient points
from Chapter 2:

• The System.String class is a reference type having value semantics.


This means that unlike most reference types, string comparisons are
based on the value of the strings and not their location.
• A string is a sequence of Char types. Any reference to a character
within a string is treated as a char.
• Strings are immutable. This means that after a string is created, it can-
not be changed at its current memory location: You cannot shorten it,
append to it, or change a character within it. The string value can be
changed, of course, but the modified string is stored in a new memory
location. The original string remains until the Garbage Collector
removes it.
• The System.Text.StringBuilder class provides a set of methods
to construct and manipulate strings within a buffer. When the opera-
tions are completed, the contents are converted to a string. String-
Builder should be used when an application makes extensive use of
concatenation and string modifications.

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

Consider the following code:

string poem1 = "Kubla Khan";


string poem2 = "Kubla Khan";
string poem3 = String.Copy(poem2); // Create new string object
string poem4 = "Christabel";

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

Figure 5-2 String interning

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

Console.WriteLine(poem1 == poem2); // true


Console.WriteLine(poem1 == poem3); // true
Console.WriteLine(ReferenceEquals(poem1, poem3)); // false
Console.WriteLine(ReferenceEquals(poem1,
"Kubla Khan")); // true

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.

string khan = " Khan";


string poem5 = "Kubla" + khan;
Console.WriteLine(ReferenceEquals(poem5, poem1)); // false
// Place the contents of poem5 in the intern pool—if not there
poem5 = String.Intern(poem5);
Console.WriteLine(ReferenceEquals(poem5, poem1)); // true

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

Use the String.Intern method to allow a string variable to take


advantage of comparison by reference, but only if it is involved in
numerous comparisons.

Overview of String Operations


The System.String class provides a large number of static and instance methods,
most of which have several overload forms. For discussion purposes, they can be
grouped into four major categories based on their primary function:
212 Chapter 5 ■ C# Text Manipulation and File I/O

• String Comparisons. The String.Equals, String.Compare, and


String.CompareOrdinal methods offer different ways to compare
string values. The choice depends on whether an ordinal or lexical
comparison is needed, and whether case or culture should influence
the operation.
• Indexing and Searching. A string is an array of Unicode characters
that may be searched by iterating through it as an array or by using
special index methods to locate string values.
• String Transformations. This is a catchall category that includes
methods for inserting, padding, removing, replacing, trimming, and
splitting character strings.
• Formatting. NET provides format specifiers that are used in con-
junction with String.Format to represent numeric and DateTime
values in a number of standard and custom formats.

Many of the string methods—particularly for formatting and comparisons—are


culture dependent. Where applicable, we look at how culture affects the behavior of
a method.

5.3 Comparing Strings


The most efficient way to determine if two string variables are equal is to see if they
refer to the same memory address. We did this earlier using the ReferenceEquals
method. If two variables do not share the same memory address, it is necessary to
perform a character-by-character comparison of the respective values to determine
their equality. This takes longer than comparing addresses, but is often unavoidable.
.NET attempts to optimize the process by providing the String.Equals method
that performs both reference and value comparisons automatically. We can describe
its operation in the following pseudo-code:

If string1 and string2 reference the same memory location


Then strings must be equal
Else
Compare strings character by character to determine equality

This code segment demonstrates the static and reference forms of the Equals
method:

string poem1 = "Kubla Khan";


string poem2 = "Kubla Khan";
string poem3 = String.Copy(poem2);
string poem4 = "kubla khan";
5.3 Comparing Strings 213

//
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:

int Compare (string str1, string str2)


Compare (string str1, string str2, bool IgnoreCase)
Compare (string str1, string str2, bool IgnoreCase,
CultureInfo ci)
Compare (string str1, int index1, string str2, int index2,
int len)

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

string stringLower = "autumn";


// (1) Lexical comparison: "A" is greater than "a"
result = string.Compare(stringUpper,stringLower); // 1
// (2) IgnoreCase set to false
result = string.Compare(stringUpper,stringLower,false); // 1
// (3)Perform case-insensitive comparison
result = string.Compare(stringUpper,stringLower,true); // 0

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:

CultureInfo ci = new CultureInfo("de-DE"); // German culture

To explicitly specify a default culture or no culture, the CultureInfo class has


two properties that can be passed as parameters—CurrentCulture, which tells a
method to use the culture of the current thread, and InvariantCulture, which
tells a method to ignore any culture.
Let’s look at a concrete example of how culture differences affect the results of a
Compare() operation.

using System.Globalization; // Required for CultureInfo

// Perform case-sensitive comparison for Czech culture


string s1 = "circle";
string s2 = "chair";
result = string.Compare(s1, s2,
true, CultureInfo.CurrentCulture)); // 1
result = string.Compare(s1, s2,
true, CultureInfo.InvariantCulture)); // 1
// Use the Czech culture
result = string.Compare(s1, s2,
true, new CultureInfo("cs-CZ")); // -1
5.3 Comparing Strings 215

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

When writing an application that takes culture into account, it is good


practice to include an explicit CultureInfo parameter in those
methods that accept such a parameter. This provides a measure of
self-documentation that clarifies whether the specific method is subject
to culture variation.

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:

string stringUpper = "AUTUMN";


string stringLower = "autumn";
//
result = string.Compare(stringUpper,stringLower,
false, CultureInfo.InvariantCulture); // 1
result = string.CompareOrdinal(stringUpper,stringLower); // -32

Compare performs a lexical comparison that regards the uppercase string to be


greater than the lowercase. CompareOrdinal examines the underlying Unicode
values. Because A (U+0041) is less than a (U+0061), the first string is less than the
second.
216 Chapter 5 ■ C# Text Manipulation and File I/O

5.4 Searching, Modifying, and


Encoding a String’s Content
This section describes string methods that are used to perform diverse but familiar
tasks such as locating a substring within a string, changing the case of a string, replac-
ing or removing text, splitting a string into delimited substrings, and trimming lead-
ing and trailing spaces.

Searching the Contents of a String


A string is an implicit zero-based array of chars that can be searched using the array
syntax string[n], where n is a character position within the string. For locating a
substring of one or more characters in a string, the string class offers the IndexOf
and IndexOfAny methods. Table 5-2 summarizes these.

Table 5-2 Ways to Examine Characters Within a String

String Member Description

[ n ] Indexes a 16-bit character located at position n within


a string.
int ndx= 0;
while (ndx < poem.Length)
{
Console.Write(poem[ndx]); //Kubla Khan
ndx += 1;
}

IndexOf/LastIndexOf Returns the index of the first/last occurrence of a spec-


ified string within an instance. Returns –1 if no match.
(string, [int start],
[int count]) string poem = "Kubla Khan";
int n = poem.IndexOf("la"); // 3
n = poem.IndexOf('K'); // 0
count. Number of chars to n = poem.IndexOf('K',4); // 6
examine.

IndexOfAny/LastIndexOfAny Returns the index of the first/last character in an array


of Unicode characters.
string poem = "Kubla Khan";
char[] vowels = new char[5]
{'a', 'e', 'i', 'o', 'u'};
n = poem.IndexOfAny(vowels); // 1
n = poem.LastIndexOfAny(vowels); // 8
n = poem.IndexOfAny(vowels,2); // 4
5.4 Searching, Modifying, and Encoding a String’s Content 217

Searching a String That Contains Surrogates


All of these techniques assume that a string consists of a sequence of 16-bit charac-
ters. Suppose, however, that your application must work with a Far Eastern character
set of 32-bit characters. These are represented in storage as a surrogate pair consist-
ing of a high and low 16-bit value. Clearly, this presents a problem for an expression
such as poem[ndx], which would return only half of a surrogate pair.
For applications that must work with surrogates, .NET provides the StringInfo
class that treats all characters as text elements and can automatically detect whether a
character is 16 bits or a surrogate. Its most important member is the GetTextEle-
mentEnumerator method, which returns an enumerator that can be used to iterate
through text elements in a string.

TextElementEnumerator tEnum =
StringInfo.GetTextElementEnumerator(poem) ;
while (tEnum.MoveNext()) // Step through the string
{
Console.WriteLine(tEnum.Current); // Print current char
}

Recall from the discussion of enumerators in Chapter 4, “Working with Objects in


C#,” that MoveNext() and Current are members implemented by all enumerators.

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.

Table 5-3 Methods for Manipulating and Transforming Strings

Tag Description

Insert (int, string) Inserts a string at the specified position.


string mariner = "and he stoppeth three";
string verse = mariner.Insert(
mariner.IndexOf(" three")," one of");
// verse --> "and he stoppeth one of three"

PadRight/PadLeft Pads a string with a given character until it is a specified


width. If no character is specified, whitespace is used.
string rem = "and so on";
rem = rem.PadRight(rem.Length+3,'.');
// rem --> "and so on..."
218 Chapter 5 ■ C# Text Manipulation and File I/O

Table 5-3 Methods for Manipulating and Transforming Strings (continued)

Tag Description

Remove(p , n) Removes n characters beginning at position p.


string verse = "It is an Ancient Mariner";
string newverse = (verse.Remove(0,9));
// newverse --> "Ancient Mariner"

Replace (A , B) Replaces all occurrences of A with B, where A and B are


chars or strings.
string aString = "nap ace sap path";
string iString = aString.Replace('a','i');
// iString --> "nip ice sip pith"

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

ToUpper() Returns an upper- or lowercase copy of the string.


ToUpper(CultureInfo)
ToLower() string poem2="Kubla Khan";
ToLower(CultureInfo) poem2= poem2.ToUpper(
CultureInfo.InvariantCulture);

Trim() Removes all leading and trailing whitespaces. If a char


Trim(params char[]) array is provided, all leading and trailing characters in the
array are removed.
string name = " Samuel Coleridge";
name = name.Trim(); // "Samuel Coleridge"

TrimEnd (params char[]) Removes all leading or trailing characters specified in a


TrimStart(params char[]) char array. If null is specified, whitespaces are removed.
string name = " Samuel Coleridge";
trimName = name.TrimStart(null);
shortname = name.TrimEnd('e','g','i');
// shortName --> "Samuel Colerid"

Substring(n) Extracts the string beginning at a specified position (n) and


Substring(n, l) of length l, if specified.
string title="Kubla Khan";
Console.WriteLine(title.Substring(2,3));
//bla
5.4 Searching, Modifying, and Encoding a String’s Content 219

Table 5-3 Methods for Manipulating and Transforming Strings (continued)

Tag Description

ToCharArray() Extracts characters from a string and places in an array of


ToCharArray(n, l) Unicode characters.
string myVowels = "aeiou";
char[] vowelArr;
vowelArr = myVowels.ToCharArray();
Console.WriteLine(vowelArr[1]); // "e"

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:

• UTF-8. Each character is encoded as a sequence of 1 to 4 bytes,


based on its underlying value. ASCII compatible characters are stored
in 1 byte; characters between 0x0080 and 0x07ff are stored in 2 bytes;
and characters having a value greater than or equal to 0x0800 are con-
verted to 3 bytes. Surrogates are written as 4 bytes. UTF-8 (which
stands for UCS Transformation Format, 8-bit form) is usually the
default for .NET classes when no encoding is specified.
• UTF-16. Each character is encoded as 2 bytes (except surrogates),
which is how characters are represented internally in .NET. This is
also referred to as Unicode encoding.
• ASCII. Encodes each character as an 8-bit ASCII character. This
should be used when all characters are in the ASCII range (0x00 to
0x7F). Attempting to encode a character outside of the ACII range
yields whatever value is in the character’s low byte.
220 Chapter 5 ■ C# Text Manipulation and File I/O

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.

string text= "In Xanadu did Kubla Khan";


Encoding UTF8Encoder = Encoding.UTF8;
byte[] textChars = UTF8Encoder.GetBytes(text);
Console.WriteLine(textChars.Length); // 24
// Store using UTF-16
textChars = Encoding.Unicode.GetBytes(text);
Console.WriteLine(textChars.Length); // 48
// Treat characters as two bytes
string decodedText = Encoding.Unicode.GetString(textChars);
Console.WriteLine(decodedText); // "In Xanadu did ... "

You can also instantiate the encoding objects directly. In this example, the UTF-8
object could be created with

UTF8Encoding UTF8Encoder = new UTF8Encoding();

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

Listing 5-1 Introduction to StringBuilder


using System;
using System.Text;
public class MyApp
{
static void Main()
{
// Create comma delimited string with quotes around names
string namesF = "Jan Donna Kim ";
string namesM = "Rob James";
StringBuilder sbCSV = new StringBuilder();
sbCSV.Append(namesF).Append(namesM);
sbCSV.Replace(" ","','");
// Insert quote at beginning and end of string
sbCSV.Insert(0,"'").Append("'");
string csv = sbCSV.ToString();
// csv = 'Jan','Donna','Kim','Rob','James'
}
}

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 Class Overview


Constructors for the StringBuilder class accept an initial string value as well as
integer values that specify the initial space allocated to the buffer (in characters) and
the maximum space allowed.

// 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);

The idea behind StringBuilder is to use it as a buffer in which string operations


are performed. Here is a sample of how its Append, Insert, Replace, and Remove
methods work:

int i = 4;
char[] ch = {'w','h','i','t','e'};
string myColor = " orange&