• | Table of Contents
.NET for Java Developers: Migrating to C#
By Jawahar Puvvala, Alok Pota
Publisher | : Addison Wesley
Pub Date | : July 25, 2003
ISBN | : 0-672-32402-4
Pages | : 720

.NET for Java Developers is the definitive guide to leveraging your Java programming experience in the .NET environment. Understanding the similarities and differences between the C# and Java APIs enables Java programmers to quickly begin rapid application Windows development using C#. The numerous code examples, which have been tested using Visual Studio(R) .NET 2003 and Java 1.4, show readers how to create feature-rich .NET applications by translating Java coding practices into C#.

The authors begin by detailing the .NET Framework and then immediately introduce readers to coding and the Visual Studio(R) .NET IDE. They compare the features of C# and Java, and provide a systematic analysis of how the two languages relate to each other. Readers cover the essentials of C# programming, from object creation to event programming, before moving into advanced areas, such as database access, XML processing, GUI programming, and reflection.

Key topics that explain how to do smart C# programming rather than emulating Java code in C#:

  • Database access

  • Inheritance and polymorphism

  • Thread programming

  • Garbage collection and memory management

  • C# application deployment

  • Delegates

  • Processing XML

.NET for Java Developers provides Java developers, programmers, and architects with experience-based advice for a successful transition into the .NET environment.


• | Table of Contents
.NET for Java Developers: Migrating to C#
By Jawahar Puvvala, Alok Pota
Publisher | : Addison Wesley
Pub Date | : July 25, 2003
ISBN | : 0-672-32402-4
Pages | : 720

Copyright
Preface
 | How This Book Is Different
 | Who Should Read This Book?
 | Overview of Chapters
 | The Code Style Used in This Book
 | From the Authors to You
 | Acknowledgments
 | About the Authors
 | About the Technical Reviewers
Part I. C# Essentials
 | Chapter 1. The .NET Framework
 |  | Section 1.1. What Is .NET?
 |  | Section 1.2. .NET Framework Terminology and Core Concepts
 |  | Section 1.3. CLR and the JVM
 |  | Section 1.4. Memory Management
 |  | Section 1.5. Security Management
 |  | Section 1.6. Exception Management
 |  | Section 1.7. Overview of the .NET Framework Class Library
 |  | Section 1.8. Summary
 | Chapter 2. Starting with C#
 |  | Section 2.1. The .NET SDK
 |  | Section 2.2. Writing Your First C# Program
 |  | Section 2.3. C# Performance
 |  | Section 2.4. C# Tools
 |  | Section 2.5. Summary
 | Chapter 3. C# and Java: What Is the Difference?
 |  | Section 3.1. Programming Platforms (.NET and Java)
 |  | Section 3.2. Runtime Architecture
 |  | Section 3.3. The OOP Model
 |  | Section 3.4. Language Constructs, Syntax, and the API
 |  | Section 3.5. Summary
 | Chapter 4. Writing Objects
 |  | Section 4.1. Object Creation
 |  | Section 4.2. Classes
 |  | Section 4.3. Constructors
 |  | Section 4.4. Methods
 |  | Section 4.5. Fields
 |  | Section 4.6. Properties
 |  | Section 4.7. Variable Scoping
 |  | Section 4.8. Object Destruction
 |  | Section 4.9. Summary
 | Chapter 5. Understanding Inheritance and Polymorphism
 |  | Section 5.1. Inheritance
 |  | Section 5.2. Polymorphism and Virtual Methods
 |  | Section 5.3. Exploring C# Polymorphism
 |  | Section 5.4. Methods and Inheritance
 |  | Section 5.5. Casting
 |  | Section 5.6. Composition versus Inheritance
 |  | Section 5.7. Summary
 | Chapter 6. Implementing Interfaces
 |  | Section 6.1. Basic Interfaces
 |  | Section 6.2. Multiple Interfaces and Explicit Interface Declaration
 |  | Section 6.3. Inheritance in Interfaces
 |  | Section 6.4. The as Operator
 |  | Section 6.5. Summary
 | Chapter 7. Data Types
 |  | Section 7.1. The Java Value Type
 |  | Section 7.2. The Java Reference Type
 |  | Section 7.3. The C# Value Type
 |  | Section 7.4. The Built-in Value Types
 |  | Section 7.5. The C# Reference Types
 |  | Section 7.6. Boxing and Unboxing
 |  | Section 7.7. Unsafe Code
 |  | Section 7.8. Summary
 | Chapter 8. Operators
 |  | Section 8.1. Arithmetic Operators
 |  | Section 8.2. Logical Operators
 |  | Section 8.3. Bitwise Operators
 |  | Section 8.4. String Concatenation Operators
 |  | Section 8.5. The Increment and Decrement Operators
 |  | Section 8.6. Shift Operators
 |  | Section 8.7. Relational Operators
 |  | Section 8.8. Assignment Operators
 |  | Section 8.9. The Member Access (Dot) Operator
 |  | Section 8.10. The Casting Operator
 |  | Section 8.11. The Indexing Operator
 |  | Section 8.12. The Conditional Operator
 |  | Section 8.13. Object Creation Operator
 |  | Section 8.14. Type Information Operators
 |  | Section 8.15. Overflow Exception Control
 |  | Section 8.16. Pointer Type Operators
 |  | Section 8.17. Operator Precedence
 |  | Section 8.18. Operator Overloading
 |  | Section 8.19. Summary
 | Chapter 9. Essential Control Flow
 |  | Section 9.1. The if Statement
 |  | Section 9.2. The while Loop
 |  | Section 9.3. The do while Loop
 |  | Section 9.4. The for Loop
 |  | Section 9.5. The switch Statement
 |  | Section 9.6. The foreach Statement
 |  | Section 9.7. Jump Statements
 |  | Section 9.8. Summary
 | Chapter 10. Programming with Exceptions
 |  | Section 10.1. Fundamentals of the try-catch-finally Construct
 |  | Section 10.2. Custom Exceptions
 |  | Section 10.3. Inheritance and Exceptions
 |  | Section 10.4. CLR Exceptions
 |  | Section 10.5. Design Considerations
 |  | Section 10.6. Summary
 | Chapter 11. Working with Arrays
 |  | Section 11.1. Java versus C# Arrays
 |  | Section 11.2. One-Dimensional Arrays
 |  | Section 11.3. Multidimensional Arrays
 |  | Section 11.4. Jagged Arrays
 |  | Section 11.5. Copying Arrays
 |  | Section 11.6. Arrays as Collections
 |  | Section 11.7. Summary
 | Chapter 12. Processing Strings
 |  | Section 12.1. The System.String Class
 |  | Section 12.2. Initializing Strings
 |  | Section 12.3. The + Operator and Strings
 |  | Section 12.4. Regular Expressions
 |  | Section 12.5. Summary
 | Chapter 13. Formatting Numbers, Strings, and Dates
 |  | Section 13.1. Formatting
 |  | Section 13.2. Number Formatting
 |  | Section 13.3. Date and Time Formatting
 |  | Section 13.4. Formatting Custom Objects
 |  | Section 13.5. Parsing
 |  | Section 13.6. Summary
 | Chapter 14. Using Collections
 |  | Section 14.1. The System.Collections Interfaces
 |  | Section 14.2. The System.Collections Classes
 |  | Section 14.3. Using the System.Collections Classes
 |  | Section 14.4. Type-Safe Collections
 |  | Section 14.5. Custom Collections
 |  | Section 14.6. Summary
 | Chapter 15. Working with the C# I/O API
 |  | Section 15.1. Streams
 |  | Section 15.2. The FileStream Class
 |  | Section 15.3. The BufferedStream Class
 |  | Section 15.4. The MemoryStream Class
 |  | Section 15.5. The NetworkStream Class
 |  | Section 15.6. Readers and Writers
 |  | Section 15.7. Files and Directories
 |  | Section 15.8. Serialization
 |  | Section 15.9. Summary
 | Chapter 16. Thread Programming
 |  | Section 16.1. The System.Threading Namespace
 |  | Section 16.2. Creating Threads Using the System.Threading.Thread Class
 |  | Section 16.3. Stopping a Thread Safely in C#
 |  | Section 16.4. Thread Synchronization
 |  | Section 16.5. Interlocked Operations
 |  | Section 16.6. Summary
 | Chapter 17. Using C# Properties, Indexers, and Attributes
 |  | Section 17.1. Properties
 |  | Section 17.2. Indexers
 |  | Section 17.3. Attributes
 |  | Section 17.4. Summary
 | Chapter 18. Delegates and Event Programming
 |  | Section 18.1. Using Delegates
 |  | Section 18.2. Multicasting
 |  | Section 18.3. Event Programming
 |  | Section 18.4. Summary
Part II. Advanced C# Topics
 | Chapter 19. Accessing Databases
 |  | Section 19.1. Inside ADO.NET
 |  | Section 19.2. JDBC 2.0+
 |  | Section 19.3. Getting Started with ADO.NET
 |  | Section 19.4. DataSets
 |  | Section 19.5. Summary
 | Chapter 20. Processing XML
 |  | Section 20.1. XML Support in Java
 |  | Section 20.2. XML and .NET
 |  | Section 20.3. Reading and Writing XML
 |  | Section 20.4. Using the DOM API in .NET
 |  | Section 20.5. Summary
 | Chapter 21. GUI Programming in C#
 |  | Section 21.1. Choosing a GUI Library: Java or .NET?
 |  | Section 21.2. Using the IDE to Develop a Small Application
 |  | Section 21.3. Windows Forms, Component Controls, and Their Associated .NET Classes
 |  | Section 21.4. Putting It All Together
 |  | Section 21.5. Anchoring and Docking
 |  | Section 21.6. Drawing with .NET GUI Classes
 |  | Section 21.7. Summary
 | Chapter 22. Reflection
 |  | Section 22.1. The Core Reflection Classes
 |  | Section 22.2. Browsing and Querying Members
 |  | Section 22.3. Invoking Methods and Setting Fields and Properties
 |  | Section 22.4. Generating Dynamic IL Using Reflection Emit
 |  | Section 22.5. Summary
 | Chapter 23. Assemblies, Application Configuration, and Process Management
 |  | Section 23.1. The .NET Assembly
 |  | Section 23.2. Creating an Assembly
 |  | Section 23.3. Programmatic Access to Assemblies
 |  | Section 23.4. Versioning
 |  | Section 23.5. Application Configuration
 |  | Section 23.6. Process Management
 |  | Section 23.7. Summary
 | Appendix Java and C# API Comparison
 |  | Section A.1. The java.lang Package
 |  | Section A.2. The java.util Package
 |  | Section A.3. The java.io Package
 |  | Section A.4. The java.sql Package
 |  | Section A.5. The java.text Package
 |  | Section A.6. The org.w3c.dom Package
 |  | Section A.7. The javax.swing Package
 |  | Section A.8. The java.awt Package


Copyright

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 Addison-Wesley was aware of a trademark claim, the designations have been printed with initial capital letters or in all capitals.

The authors 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 discounts on this book when ordered in quantity for bulk purchases and special sales. For more information, please contact:

U.S. Corporate and Government Sales

(800) 382-3419

corpsales@pearsontechgroup.com

For sales outside of the U.S., please contact:

International Sales

(317) 581-3793

international@pearsontechgroup.com

Visit Addison-Wesley on the Web: www.awprofessional.com

Library of Congress Cataloging-in-Publication Data

Puvvala, Jawahar.

.NET for Java developers migrating to C# / Jawahar Puvvala, Alok Pota.

p. cm.

ISBN 0-672-32402-4 (alk. paper)

1. C# (Computer program language) 2. Java (Computer program language) 3. Microsoft .NET. I. Pota, Alok. II. Title.

QA76.73.C154P88 2003

005.13'3—dc21

2003048068

Copyright © 2004 by Pearson Education, Inc.

All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form, or by any means, electronic, mechanical, photocopying, recording, or otherwise, without the prior consent of the publisher. Printed in the United States of America. Published simultaneously in Canada.

For information on obtaining permission for use of material from this work, please submit a written request to:

Pearson Education, Inc.

Rights and Contracts Department

75 Arlington Street, Suite 300

Boston, MA 02116

Fax: (617) 848-7047

Text printed on recycled paper

1 2 3 4 5 6 7 8 9 10—CRS—0706050403

First printing, July 2003

Dedication

To my wife, Anjani, and my parents, Lakshmi and Satyanarayana

Jawahar Puvvala

To my wife, Suzy, and my parents, Neeta and Ashwin, who taught me to always strive for the best

Alok Pota


Preface

The C# language was released by Microsoft as one of the core languages for developing .NET applications. C# bears amazing similarities to Sun Microsystems' Java programming language. But along with the similarities there are a slew of differences and deviations in the overall programming methodology, syntax, and constructs. In your enthusiasm to transfer your Java skills to C#, it is easy to overlook these differences. Each language has its own good coding practices, and good Java coding practices are not necessarily good C# coding practices. The intent of this book is to bring home the fundamentals of C# from the perspective of a Java programmer. We expose the differences in the two languages while underscoring good C# coding practices.


How This Book Is Different

This book was written by programmers for programmers, and hence you will find the book to be light on description and heavy on code. You will note a preponderance of complete code samples to illustrate C# concepts, constructs, syntax, and philosophies. Where possible, we provide Java listings. The code listings are fairly nontrivial at times, and we have tried to explain concepts by walking through code. This approach is similar to some of the Java open source project tutorials out there.

As a Java programmer you will notice that we look at Java quirks and idioms to explore whether they also exist in C#. We have also examined some features of C# to discover why they don't exist in Java and why they are designed differently in C#. In places, we give coding practices in Java that are rendered redundant in C#. For example, using the + operator to concatenate strings and create large strings has been heavily denounced in the Java world, but it is not necessarily a bad thing in C#.

We have given each chapter its unique set of code listings instead of building one massive application throughout the book. Note, too, that the book focuses on discussing the C# language and not the associated tools that come with the .NET development environment.


Who Should Read This Book?

As the title suggests, this book is tailored to suit typical Java programmers. Those who regularly program in Java should have a short C# learning curve after reading this book. If you haven't programmed in Java, you should be familiar with the fundamentals of object-oriented programming (OOP) and should have programmed with an OOP language such as C++ or Smalltalk. If you are not familiar with OOP, we suggest you hone your OOP skills and get comfortable with working with classes, interfaces, objects, and polymorphism, and then revisit C#.


Overview of Chapters

Although you are welcome to consult any random chapter as a reference for understanding its C# concept, we recommend reading the chapters in order. In that way, you won't get bogged down by syntax and construct questions when it comes to discussing the more esoteric APIs. Following is a brief description of each chapter.

Chapter 1, The .NET Framework, is an overview of the various components of the .NET framework. Readers itching to code can skip this chapter.

Chapter 2, Starting with C#, explores the proverbial Hello World program—first without using the Visual Studio .NET integrated development environment (IDE), and then using the IDE. We briefly explain the components of the IDE. Knowledge of the IDE is important if you are to develop large-scale enterprise applications. However, you don't need to be well versed in the IDE to understand the language concepts.

Chapter 3, C# and Java: What Is the Difference?, lists the features of C# and compares them to equivalent features in Java. This chapter makes for good reading for those who want to quickly know how C# differs from Java.

Chapter 4, Writing Objects, gets down to the business end of OOP: writing objects. Java programming is all about writing classes.

Chapter 5, Understanding Inheritance and Polymorphism, looks at the cornerstones of any OOP language. Both Java and C# use a single-root class hierarchy and have polymorphism. As a Java programmer you have probably been pampered by Java's always turned-on, automatic virtual dispatching. The C# approach is to have virtual dispatching turned off by default. What does that mean to you as a Java programmer? This chapter illustrates some C# pitfalls that Java programmers might fall into if they are not careful.

Chapter 6, Implementing Interfaces, explains that both Java and C# allow a class to implement multiple interfaces. We discuss several aspects of a C# interface.

Chapter 7, Data Types, explores the elements that make a programming language. C# provides a lot more data types and constructs than Java does. You should read this chapter if you want to exploit C#-specific constructs and not end up writing everything as a class in C#.

Chapter 8, Operators, covers a fairly mundane, but important, topic. You may be surprised to learn that C# supports operator overloading.

Chapter 9, Essential Control Flow, discusses control flow statements such as if-then-else, switch, for, foreach, and many more. C# is a very expressive language that contains built-in optimizations of some constructs. You will learn that C# allows strings in switch statements, along with many other surprising details.

Chapter 10, Programming with Exceptions, treats something that used to be an option in C++ and is now a major part of the Java language. Fortunately, C# and Java both agree on making exception handling a core part of the language. Unfortunately, there is no throws clause in C#. So how do you know which exceptions are thrown by a method you call? As a Java programmer you might find C#'s lenient exception handling both a blessing and a curse.

Chapter 11, Working with Arrays, explains this fundamental data structure, which is found in any programming language. C# goes a step further than most languages by having a special class for arrays.

Chapter 12, Processing Strings, deals with one of the most ubiquitous classes of Java, which also happens to be commonplace in C#. This chapter discusses the fundamental concepts of string equality, interning, and optimizations.

Chapter 13, Formatting Numbers, Strings, and Dates, discusses the C# equivalent of the java.text.* package.

Chapter 14, Using Collections, explains how both Java and C# collections let you build an automobile without reinventing the wheel. Unfortunately, the C# collection library is currently no match for what Java has to offer.

Chapter 15, Working with the C# I/O API, discusses I/O streams, files, directories, and serialization. C# makes a distinction between a file and a directory, and it gives you several ways to make a class serializable.

Chapter 16, Thread Programming, shows you how C# has simplified threading. You can finally close threads in C#, and it gives you a system-supplied thread pool—and this means no more scrambling for third-party implementations. You can have reader-writer locks on resources, something the JDK does not provide out of the box. C# also gives you four different ways to write multithreaded classes. Many more surprises unfold in this chapter.

Chapter 17, Using C# Properties, Indexers, and Attributes, may bring back memories long forgotten for Java programmers who once were Visual Basic programmers. Properties reduce code clutter by eliminating getter and setter calls. Indexers let you treat collections as arrays, and attributes let you tinker with the metadata of your code. None of these features is supported in Java.

Chapter 18, Delegates and Event Programming, explains how function pointers, abandoned in Java, are back in C# as delegates. Here's another example of how C# goes out of its way to not model every concept or construct as a class.

Chapter 19, Accessing Databases, deals with a capability that is part of the core .NET API. A unique C# data structure is the RowSet object, which is basically an in-memory representation of an RDBMS table or view. This chapter discusses some of the routine database operations that you will want to execute.

Chapter 20, Processing XML, reveals that XML processing, which has finally made it into the core API in Java 1.4, is a first-class concept in C#. You get built-in support for XML, XSL translation, and XPath in C#'s core libraries.

Chapter 21, GUI Programming in C#, is brimming with practical advice for GUI builders. The Java Swing library has become the preferred API for building client-side Java GUI applications. The Swing library consists of classes modeled after the MVC (model view controller) pattern. Although very modular, Swing can be tedious to work with because the JDK does not ship with a GUI designer. This makes it a chore to design rich, interactive screens because everything must be coded by hand. Only recently have third-party Java IDEs become available to simplify Swing programming. In contrast, the .NET Windows Forms library is reminiscent of Visual Basic drag-and-drop Forms, with event-driven code associated with the controls on the form. If you have the Visual Studio .NET handy, then GUI programming in C# is reduced to drag-and-drop and adding event-based code. The Visual Studio .NET Forms Designer is a welcome change for languishing Swing programmers.

Chapter 22, Reflection, explains that reflective capabilities go hand in hand with managed code and type-safe data types. Reflection allows you to probe the metadata behind the various language constructs. Both C# and Java provide a great set of reflection API classes. But C#'s reflection emit feature takes the cake because it lets you dynamically generate and run MSIL code.

Chapter 23, Assemblies, Application Configuration, and Process Management, argues that packaging is everything. This is true even for computer applications. An improperly packaged and deployed application either will not work or will give you runtime headaches. C# assemblies let you deploy complex applications as one unit that can be versioned. C# also provides several deep hooks into Windows operating system process management. Read all about assemblies and processes in this chapter.


The Code Style Used in This Book

Code conventions vary, and you should stick to the convention followed by your programming team or organization. Here, we differentiate code from the regular text by using a different font style (code). We avoid Hungarian notation for variable names and instead use the Java style. For example, we use

int count;

instead of

int m_IntCount;

Method names follow Pascal casing instead of Java's camel casing. So the Java method name

getName()

would be translated as

GetName()

Loops and control flow statements follow a slanting style, where open braces appear at the end of the line and close braces appear at the beginning of the line:

for (int  i = 0; i < 100; i++) {
//Code goes here
}

We have commented the code in strategic places where it is not obvious why a particular approach was taken. We feel that the code should be as self-explanatory as possible. This can be achieved by giving meaningful names to variables and methods so that the intent of the method is conveyed without additional comments. For example, the intent of the following method is obvious:

public void PrintProperties(object o)

The code listings revolve around explaining one or two concepts. We have tried to maintain the focus of the code listing while keeping it interesting. When a concept does not deserve a full-blown class, we use code snippets.


From the Authors to You

Writing this book has been a great learning experience for us. We were pleasantly surprised to discover that C# is not very different from Java, with a few exceptions. As a Java programmer, you should consider yourself equipped with the basic strategies, patterns, and idioms that you are used to coding in the Java world, because you can easily carry over most of these practices into the C# world.

The main difference we have discovered between the two languages is that Java emphasizes consistency by modeling everything as a class, whereas C# has many more programming constructs that are not classes: enums, structs, properties, indexers, delegates, and pointers. This means that you can continue to model everything as a class in C#, but if you do that you will rob yourself of the benefit of exploiting the built-in efficiencies in some of the C# constructs. These constructs also make the resulting code a little bit more expressive. With this in mind, we have encouraged you wherever possible to use the C# style.

Finally, we encourage you to play with the Visual Studio .NET IDE. It is not necessary to use it to program in C#, but we recommend it because it will help you crank out applications and reduce some of the tedium of designing a lot of big applications fast.


Acknowledgments

I don't think I could have written this book by myself without my co-author, Alok Pota. Alok is really an amazing person who constantly inspires me. I still remember the day I met him back in 1994. When I first met him I said to myself, "He is really the guy with whom I can constantly talk about technology." Even today I don't remember talking to him about anything but technology. I would like to thank senior acquisitions editor Sondra Scott, with whom we really enjoyed working. I consider myself very lucky to have met Sondra, so thanks to Larry Wall (my former manager) for introducing me to Sondra. Thanks to Laurie McGuire and Jennifer Blackwell, who did a wonderful job in making sure the content was well organized and consistent. Thanks to Mark Burhop, Howard Lee Harkness, and Kevin T. Price for all the hard work they have done in providing the right feedback. Thanks to the production team Amy Fleischer and Betsy Hardinger for their diligent and meticulous work.

I would like to thank my colleagues at Nucor Corporation, who understood when I said, "I need to leave in 10 minutes to work on the book," and my manager, Scott Messenger, for giving me an opportunity to work on exciting projects related to Microsoft technology. I would also like to personally thank Chris Osborne for his help and for giving me his spiteful habit of assembling computers rather than buying them from a store.

Special thanks are due to my wonderful wife, Anjani, for keeping up with my crazy long nights and weekend work schedules, and to my mom, dad, sisters, and brother for their support.

—Jawahar

Putting together a book is a lot of work. I would like to express my gratitude to several people for their help with this endeavor. First and foremost, I would like to thank Sondra Scott, senior acquisitions editor at Addison-Wesley Professional, for affording me the chance to join the ranks of Addison-Wesley authors. It's a great honor, and no small responsibility.

I have known my co-author, Jawahar Puvvala, for several years now. We have always been keen on trying out new technology. We have often discussed the similarities and differences between C# and Java. We decided to take these arguments a step further and thoroughly examine the differences. I am thankful for those moments of geek talk, which ignited my curiosity about C# and fueled my passion for writing this book.

Authors of technical books rely heavily on technical reviewers to detect errors and omissions. I would like to thank Mark Burhop, Howard Lee Harkness, and Kevin T. Price for giving this book a critical review. We have incorporated several of their suggestions. The development editors and production team, Jennifer Blackwell, Laurie McGuire, Betsy Hardinger, and Amy Fleischer, worked tirelessly to make sure the book was on the right track.

I have had the honor of working with many talented people during my career as a programmer. I would like to particularly thank my peers in the Core Technologies Group at Digital Motorworks LP for making me a part of their group. Their passion for programming perfection and writing good clean code is contagious, and they have made me a better programmer. Last but not least, I would like to thank my wife, Suzy, whose kind and patient attitude allowed me to accomplish this work. My parents have always been a source of encouragement for me, and although they are miles away from me, their guidance has helped me achieve my goals.

Alok


About the Authors

Jawahar Puvvala has been developing enterprise applications in Sun and Microsoft technologies for variety of high-tech companies for the past five years. Jawahar is a consultant at Nucor Corporation in Charlotte, North Carolina. It was structural engineering that made Jawahar a programming expert; his educational background is in writing matrix analysis software for mechanical and structural engineering systems in C++. Today, he specializes in client-server and distributed application development in various technologies and languages, including COM+, MFC, ATL, ASP, MSMQ, XML, XSLT, DCOM, SOAP, C++, C#, Java, and Visual Basic. He has published a couple of journal and conference articles on structural engineering.

Jawahar wrote his first enterprise-level application in Java for the textile industry. Although he is not an obsessively 100 percent pure Java programmer, he thinks that it was the interface programming concepts in Java that laid the foundation for his success in COM/DCOM in Microsoft technology.

When he is not working, he spends his time taking care of his house and home network. He can be reached at jawahar@carolina.rr.com.

Alok Pota is a Java addict currently working as a J2EE application developer at Digital Motorworks (DMi) in Austin, Texas. Alok has been developing server-side enterprise applications using J2EE technologies for the past five years and is always seeking out the latest and greatest in Java technology. Before working with Java, his expertise was in writing scientific numerical analysis and visualization software in Visual C++, Visual Basic, and FORTRAN. Alok's published work includes numerous scientific articles on mathematical modeling and expert systems.

Alok began his career in programming by writing numerical modeling software in C++. Coupled with his modeling skills and his interest in the field of artificial intelligence, this experience helped him to write a decision support system based on the expert system shell CLIPS (C Language Integrated Production Systems). When he was introduced to Java, he switched gears from writing scientific software to writing business-related software.

When not reading up on Java or tinkering with the latest APIs, Alok likes to play with his son, Rajiv. You can reach Alok at apota@austin.rr.com.


About the Technical Reviewers

Mark Burhop is a senior software engineer in Cincinnati, Ohio, and also a member of the adjunct faculty at the University of Cincinnati. He has more than 15 years of experience developing software in the computer aided engineering and product data management fields using technologies ranging from Microsoft .NET (C#) to Java to C++. As an author, he is currently focused on showing how to write high-performance C# applications. More information on Mark and his work can be found at http://www.markburhop.com.

Howard Lee Harkness has earned a living writing software in more than a dozen different languages, and teaching other programmers, for nearly three decades. He holds a degree in computer science from the University of Texas and has worked with many kinds of software, ranging from embedded systems in military and automotive applications to Web-based bill-of-materials systems. In his spare time, he plays the violin at Irish sessions.

Kevin T. Price is a senior technologist in Vienna, Virginia, specializing in security and scalability, and he has worked with all aspects of application development within the Microsoft toolset for several years. He has also written chapters for or edited several books related to XML, security, and .NET technologies. When not sitting at a computer, Kevin can frequently be found either in a kitchen or on a paintball field. He can be reached at kpcrash@patriot.net.


Part I: C# Essentials

    Chapter 1. The .NET Framework

    Chapter 2. Starting with C#

    Chapter 3. C# and Java: What Is the Difference?

    Chapter 4. Writing Objects

    Chapter 5. Understanding Inheritance and Polymorphism

    Chapter 6. Implementing Interfaces

    Chapter 7. Data Types

    Chapter 8. Operators

    Chapter 9. Essential Control Flow

    Chapter 10. Programming with Exceptions

    Chapter 11. Working with Arrays

    Chapter 12. Processing Strings

    Chapter 13. Formatting Numbers, Strings, and Dates

    Chapter 14. Using Collections

    Chapter 15. Working with the C# I/O API

    Chapter 16. Thread Programming

    Chapter 17. Using C# Properties, Indexers, and Attributes

    Chapter 18. Delegates and Event Programming


Chapter 1. The .NET Framework

This chapter explores the components of the .NET Framework, which consists of the Common Language Runtime (CLR) environment and the .NET Framework class library. The .NET Framework itself is a small part of Microsoft's .NET technology. The .NET technology consists of an assortment of technologies, services, tools, and concepts. We begin by looking at what .NET is all about and then discuss the framework components.


1.1 What Is .NET?

.NET is Microsoft's platform for developing Extensible Markup Language (XML) Web services, which allow applications to communicate and share data over the Internet. There are five aspects of the .NET platform:

  1. .NET experiences: These are XML Web services that allow you to access information across the Internet and from stand-alone applications in an integrated way. Examples of the .NET experience are MSN, bCentral, Passport, and Microsoft Visual Studio .NET.

  2. Clients: These are PCs, laptops, workstations, phones, handheld computers, tablet PCs, game consoles, and other smart devices. What makes these devices "smart" is their ability to access XML Web services. Smart clients use software that supports XML Web services and enables you to access your data regardless of the location, type, and number of clients you use.

  3. XML Web services: These offer a direct means for applications to interact with other applications. Applications hosted internally, as well as on remote systems, can communicate via the Internet by using XML and Simple Object Access Protocol (SOAP) messages. In addition to developers creating XML Web services, Microsoft is creating a core set of building block services that perform routine tasks and act as the foundation for developers to build on. The first set of XML Web services being built, known as .NET My Services, are user-centric services: services oriented toward people rather than devices, networks, or applications.

  4. Servers: The Microsoft .NET Enterprise Servers, including the Microsoft Windows 2000 server family, make up the Microsoft .NET server infrastructure for deploying, managing, and orchestrating XML Web services.

  5. Tools: Microsoft Visual Studio .NET and the Microsoft .NET Framework supply a complete solution for developers to build, deploy, and run XML Web services. These tools maximize the performance, reliability, and security of XML Web services. Visual Studio .NET is the next generation of Microsoft's popular multilanguage development tool built especially for .NET. The .NET Framework is a high-productivity, standards-based, multilanguage application execution environment that handles essential plumbing chores and eases deployment. It provides an application execution environment that manages memory, addresses versioning issues, and improves the reliability, scalability, and security of your application.

This chapter explores primarily the framework components.


1.2 .NET Framework Terminology and Core Concepts

Like any other platform, the .NET Framework introduces terminology that you need to be aware of. Following is a list of some of these terms.

1.2.1 CLR

The Common Language Runtime (CLR) is a key .NET Framework component. The CLR is the container, or the execution environment, in which all the managed code runs. The CLR gets its name from the fact that .NET provides a binary standard and any programming language that adheres to the binary standard can be used for writing code that can be run by the CLR. In its function and nature, the CLR is similar to the Java Virtual Machine (JVM). The CLR provides infrastructure services to support execution of managed code. These services include automatic memory management, type safety, thread and process management, security, and versioning. The CLR sits between the managed code and the operating system so that your code is insulated from interacting directly with the operating system. This arrangement makes your code portable to some extent and also frees you from having to do system-level tasks so that you can concentrate on writing application code. The CLR is discussed in detail later in this chapter.

1.2.2 Managed Code

Managed code runs under a contract of cooperation with the CLR. This means that managed code must supply the metadata necessary for the runtime to provide services such as memory management, cross-language integration, code access security, and automatic control of object lifetime. (For more on metadata, see Section 1.3.1.)

1.2.3 MSIL

The Microsoft Intermediate Language (MSIL) is the equivalent of Java byte code. The MSIL executes the managed code. Languages that support generating MSIL can leverage the infrastructure services of the CLR.

1.2.4 Assembly

An assembly is a versionable unit of deployment of the .NET Framework. It is equivalent to Java's .jar, .war, and .ear file formats but is a lot more feature-rich. An assembly performs the following functions:

  • It contains code that the runtime executes. MSIL code in a portable executable (PE) file will not be executed if it does not have an associated assembly manifest. Note that each assembly can have only one entry point.

  • It forms a security boundary. An assembly is the unit at which permissions are requested and granted. An assembly is to .NET security what a protection domain is to Java security.

  • It forms a type boundary. Every type's identity includes the name of the assembly in which it resides. A type MyType loaded in the scope of one assembly is not the same as a type MyType loaded in the scope of another assembly.

  • It forms a reference scope boundary. The assembly's manifest contains assembly metadata that is used for resolving types and satisfying resource requests. The manifest specifies the types and resources that are exposed outside the assembly. The manifest also enumerates other assemblies on which it depends.

  • It forms a version boundary. The assembly is the smallest versionable unit in the CLR; all types and resources in the same assembly are versioned as a unit. The assembly's manifest describes the version dependencies you specify for any dependent assemblies.

  • It forms a deployment unit. When an application starts, only the assemblies the application initially calls must be present. Other assemblies, such as localization resources or assemblies containing utility classes, can be retrieved on demand. In this way, applications can be kept simple and thin when first downloaded.

Assemblies can be static or dynamic. Static assemblies can include .NET Framework types (interfaces and classes) as well as resources for the assembly (bitmaps, JPEG files, resource files, and so on). Static assemblies are stored on disk in PE files.

You can also use the .NET Framework to create dynamic assemblies, which are run directly from memory and are not saved to disk before execution. You can save dynamic assemblies to disk after they have executed.

There are several ways to create assemblies. You can use development tools, such as Visual Studio .NET, that you have used in the past to create .dll or .exe files. You can use tools provided in the .NET Framework Software Development Kit (SDK) to create assemblies by referencing modules created in other development environments. You can also use CLR application programming interfaces (APIs) to create dynamic assemblies.

1.2.5 Application Domain

Application domains provide a secure and versatile unit of processing that the CLR can use to enable isolation between applications. You can run several application domains in a single process with the same level of isolation that would exist in separate processes, but without incurring the additional overhead of making cross-process calls or switching between processes. The ability to run multiple applications within a single process increases server scalability dramatically. The isolation provided by application domains has the following benefits:

  • Faults in one application cannot affect other applications. Because type-safe code cannot cause memory faults, using application domains ensures that code running in one domain cannot affect other applications in the process.

  • You can stop individual applications without stopping the entire process. Using application domains enables you to unload the code running in a single application.

  • Code running in one application cannot directly access code or resources in another application. The CLR enforces this isolation by preventing direct calls between objects in different application domains. Cross-domain calls use the same remote call infrastructure as calls between two processes or between two machines.

  • The behavior of code is scoped by the application in which it runs. In other words, the application domain provides configuration settings such as application version policies, the location of any remote assemblies it accesses, and information about where to locate assemblies that are loaded into the domain.

  • You can control permissions granted to code according to the application domain in which the code is running.

Application domains help to isolate multiple applications running on a single process. Much like the modern-day Java 2 Enterprise Edition (J2EE) application servers, which use dynamic class loaders to isolate applications from one another, an application domain isolates applications in their own little world.

1.2.6 Runtime Host

The runtime host loads the runtime into a process, creates the application domains within the process, and loads user code into the application domains. The various runtime hosts are as follows:

  • ASP.NET loads the runtime into the process that is to handle the Web request. ASP.NET also creates an application domain for each Web application that will run on a Web server.

  • A shell executable invokes runtime hosting code to transfer control to the runtime each time an executable is launched from the shell.

  • Microsoft Internet Explorer creates application domains in which to run managed controls. The .NET Framework supports the downloading and execution of browser-based controls. The runtime interfaces with the extensibility mechanism of Microsoft Internet Explorer through a Multipurpose Internet Mail Extensions (MIME) filter to create application domains in which to run the managed controls. By default, one application domain is created for each Web site.

1.2.7 The Common Type System

As mentioned earlier, the CLR can provide infrastructure services to MSIL generated by any programming language (as long as the language can generate valid MSIL). Uniformity of data types becomes an issue when you're dealing with a standard for multiple languages. The Common Type System (CTS) is designed to streamline the types supported in the .NET world. The CTS defines how types are declared, used, and managed in the runtime, and it is also an important part of the runtime's support for cross-language integration. The CTS performs the following functions:

  • Establishes a framework that enables cross-language integration, type safety, and high-performance code execution.

  • Provides an object-oriented model that supports the complete implementation of many programming languages.

  • Defines rules that languages must follow, something that helps to ensure that objects written in different languages can interact with one another.

Figure 1.1 shows the various .NET types.

Figure 1.1. .NET Type System

A type can have several members:

  • Event: Defines an incident that can be responded to and defines methods for subscribing to, unsubscribing from, and firing the event. Events are often used to inform other types of state changes.

  • Field: Describes and contains the value of a type. Fields can be of any type supported by the runtime.

  • Nested type: Defines a type within the scope of the enclosing type.

  • Method: Describes operations available on the type. A method's signature specifies the allowable types of all its arguments and of its return value. A constructor is a special kind of method that initializes new instances of a type.

  • Property: Names a logical value or the state of the type and defines methods for getting or setting the property's value. Properties are often used to keep the public interface of a type independent from the type's actual representation.

Type members have signatures and are inherited from their base type. Members such as methods can be overloaded and overridden.

1.2.8 Common Language Specification

The Common Language Specification (CLS) defines a set of rules for interoperability between components in different languages. Components written in other languages that adhere to this set of rules are said to be CLS-compliant and are accessible from other CLS-compliant languages. The CLS rules define a subset of the CTS; all the rules that apply to the Common Type System apply to the CLS, except where stricter rules are defined in the CLS. Most of the members defined by types in the .NET Framework are CLS-compliant. (These members are discussed later in this chapter.) Any language construct that would make it impossible to rapidly verify the type safety of code has been excluded from the CLS. As a result, all CLS-compliant languages can produce verifiable code.

CLS compliance generally refers to the claim that CLS rules and restrictions are being followed. However, the concept has a more specific meaning, depending on whether you are describing CLS-compliant code or CLS-compliant development tools, such as a compiler.

If you want your code to be CLS-compliant, you must expose functionality in a way that is CLS-compliant in the following places:

  • Definitions of your public classes

  • Definitions of the public members of public classes, and of members accessible to derived classes

  • Parameters and return types of public methods of public classes and of methods accessible to derived classes

The features you use in the definitions of your private classes, in the definitions of private methods on public classes, and in local variables do not have to follow the CLS rules. You can also use any language features you want in the code that implements your class and still have a CLS-compliant component. Code compliance is discussed in detail later, when we discuss the various code components, such as assemblies, methods, and types. For now, note that the C# language, which this book is about, has all the tools necessary to make your code CLS-compliant.


1.3 CLR and the JVM

Both .NET and Java use managed execution environments to run binary code. The two managed execution environments—the .NET CLR and Java's JVM—are quite similar in the role they play in running your code. Table 1.1 shows some of the similarities between the JVM and the CLR.

In Java, code is compiled to byte code, which is then run by the JVM. For better performance, the HotSpot JVM compiles commonly traversed parts of the byte code to native machine code. In .NET the source code is first compiled to MSIL and metadata, which is linked to form the assembly. The assembly can be an executable (EXE) file, a dynamic link library (DLL) file, or a module. At the time of execution, the MSIL and other pertinent information are sent to the class loader. The combined code is first sent to the verifier so that type safety can be checked, and then the just in time (JIT) compiler (JITer) compiles the MSIL to native managed code. .NET is different from Java because .NET code always compiles to native code.

Table 1.1. CLR versus JVM

CLR

JVM

Code management and execution environment for the .NET Framework

Code management and execution environment for the J2EE platform

Compiles application code to IL (Intermediate Language)

Compiles Java code to byte code

Does just in time (JIT) compilation; compiles IL to native code at the time of execution

Some JIT-based JVMs compile byte code to native code at the time of execution

Single unit of deployment (assembly)

Single unit of deployment (.jar files)

Automatic memory management through asynchronous garbage collection, automatic serialization, exception handling, code verification and security management, debugging, and profiling

Automatic memory management through asynchronous garbage collection, serialization, exception handling, a programmable and scriptable security model, debugging, and profiling

1.3.1 .NET MSIL

The MSIL is a set of CPU-independent instructions. The set includes instructions for loading, storing, initializing, and calling methods on objects, as well as instructions for arithmetic and logical operations, control flow, direct memory access, exception handling, and other operations. When a compiler produces MSIL, it also produces metadata. Metadata describes the types in your code, including the definition of each type, the signatures of each type's members, the members that your code references, and other data that the runtime uses at execution time. The MSIL and metadata are contained in a PE file that is based on and extends the published Microsoft PE and Common Object File Format (COFF) used historically for executable content. This file format, which accommodates MSIL or native code as well as metadata, enables the operating system to recognize CLR images. The presence of metadata in the file, along with the MSIL, enables your code to describe itself.

1.3.2 .NET JIT

The JIT compiler converts the MSIL into CPU-specific code that can then be executed. Rather than use time and memory to convert all the MSIL in a PE file into native code, it converts the MSIL as it is needed during execution and stores the resulting native code so that it is accessible for subsequent calls. The loader creates and attaches a stub to each of a type's methods when the type is loaded. On the initial call to the method, the stub passes control to the JIT compiler, which converts the MSIL for that method into native code and modifies the stub to direct execution to the location of the native code. Subsequent calls to the JIT-compiled method proceed directly to the native code that was previously generated, thereby reducing the time it takes to JIT-compile and execute the code.

Code must pass a verification process. Verification examines MSIL and metadata to find out whether the code can be determined to be type-safe, which means that it is known to access only the memory locations it is authorized to access. The runtime relies on the fact that the following statements are true for code that is verifiably type-safe:

  • A reference to a type is strictly compatible with the type being referenced.

  • Only appropriately defined operations are invoked on an object.

  • Identities are what they claim to be.

During verification, MSIL code is examined in an attempt to confirm that the code can access memory locations and call methods only through properly defined types. For example, code cannot allow an object's fields to be accessed in a manner that allows memory locations to be overrun. Additionally, verification inspects code to see whether the MSIL has been correctly generated, because incorrect MSIL could lead to a violation of the type-safety rules. The verification process passes a well-defined set of type-safe code, and it passes only code that is type-safe. However, some type-safe code might not pass verification because of limitations of the verification process, and some languages, by design, do not produce verifiably type-safe code. If type-safe code is required by security policy and if the code does not pass verification, an exception is thrown when the code is executed.

Chapter 2 discusses technical details of the MSIL and the JIT in action when you write your first C# program. But first let's discuss the equivalents of MSIL and JIT in the Java world. A first-glance comparison is that MSIL is equivalent to byte code in terms of what it does and what its responsibilities are. Also, the JIT compiler in Java has the same purpose as the JIT compiler in the CLR, although they have very different architectures. A thorough comparison between the Java byte code and MSIL and between the Java and CLR JIT compilers would not be very helpful without looking at a specific piece of code. Hence, that discussion is postponed until Chapter 2, which has many code examples.

1.3.3 The Java Virtual Machine

Using the javac compiler, the Java source code is compiled to byte code, which is also an assembly-like instruction set. Earlier versions of the Java Virtual Machine interpreted this byte code only at runtime, thereby giving true platform-independent execution. More recent versions of the Java runtime now compile sections of the byte code using the JIT compiler. The JVM then runs this native code.

Sun Microsystems' HotSpot compiler, the state of the art JIT compiler, profiles the execution of the byte code and identifies hotspots: pieces of code that get executed frequently and are performance-critical. By compiling only a small, critical portion of the byte code, the HotSpot compiler speeds startup time of execution, reduces memory consumption, and improves the overall user experience.

The Java language has some built-in optimization features that obviate the need for a full optimization of the byte code at compile time and at runtime. Most of the Java method calls are virtual (polymorphic) calls, and a frequent invocation of these methods can hurt performance; therefore, HotSpot in-lines the method call so that frequent method calls are faster. Because Java code can dynamically load and run classes, a full optimization of the byte code at compile time provides little benefit. HotSpot therefore does adaptive byte code compilation based on runtime profiling.

One of the key features of the .NET Framework and the Java environment that separates them from other traditional programming languages, such as C, C++, Fortran, and Pascal, is that these frameworks use a controlled execution environment to run the binary code. In contrast, traditional languages usually create libraries or executables that interact directly with the operating system. The .NET Framework provides the CLR, which provides basic infrastructure services for running binary code. The CLR is a piece of software that sits between your application code and the operating system. It takes care of many system-level details of process, memory, thread, type safety, and security management, thereby allowing you to concentrate on application code. We will explore some of these infrastructure services of the CLR.


1.4 Memory Management

One of the key features of modern execution environments such as the JVM and the CLR is automatic memory management through garbage collection. The CLR provides a garbage collector (GC), similar to that of the JVM, which runs in the background and releases locked-up memory by freeing and destroying unused objects. The runtime uses metadata to locate and load classes, lay out instances in memory, resolve method invocations, generate native code, enforce security, and set runtime context boundaries. The runtime automatically handles object layout and manages references to objects, releasing them when they are no longer being used. Objects whose lifetimes are managed in this way are called managed data. Automatic memory management eliminates memory leaks. Let's examine briefly what garbage collection is all about in the CLR and the JVM.

1.4.1 Garbage Collection in the CLR

The CLR allocates new objects on the managed heap, which is a contiguous block of address space. The managed heap also has a pointer, P, which initially points to the base address of the heap. When the new operator is called by the application to create a new object, the CLR determines whether the new object fits into the address space. The CLR adds the size of the object (in bytes) to the pointer P. If, after the addition, P points outside the address space, then the object cannot be allocated immediately and the heap will have to be scanned and cleared of the garbage. When space is then created on the managed heap, the new object is allocated and the address is returned. The process of identifying and removing the garbage on the heap is garbage collection, and several algorithms are available to conduct this process.

What Is Garbage?

Every application has a set of roots. Roots identify storage locations, which refer to objects on the managed heap or to objects that are set to null. For example, all the global and static object pointers in an application are considered part of the application's roots, as are any local variable or parameter object pointers on a thread's stack. Finally, any CPU registers containing pointers to objects in the managed heap are also considered part of the application's roots. The list of active roots is maintained by the JIT compiler and the CLR, and this list is accessible to the garbage collector's algorithm. Thus, garbage is defined as those objects on the heap that cannot be reached from this list of active roots.

How Does Garbage Collection Work?

When the garbage collector starts running, it assumes that all objects in the heap are garbage. In other words, it assumes that none of the application's roots refer to any objects in the heap. The GC starts walking the roots and building a graph of all objects reachable from the roots. For example, the GC may locate a global variable that points to an object in the heap. The GC continues to walk through all reachable objects recursively. When this part of the graph is complete, the GC checks the next root and walks the objects again.

As the garbage collector walks from object to object, if it attempts to add an object to the graph and discovers that the object has already been added, the GC stops walking that path. This sequence serves two purposes. First, it helps performance significantly because the GC walks any set of objects only once. Second, it prevents infinite loops should you have any circular linked lists of objects.

When all the roots have been checked, the GC's graph contains the set of all objects that are somehow reachable from the application's roots; any objects that are not in the graph are not accessible by the application and are therefore considered garbage. The GC now walks the heap linearly, looking for contiguous blocks of garbage objects and removing all gaps in the heap. Of course, moving the objects in memory invalidates all pointers to the objects. This means that the GC must modify the application's roots so that the pointers point to the objects' new locations. In addition, if any object contains a pointer to another object, the GC is also responsible for correcting these pointers.

Garbage Collection Performance

The performance of garbage collection depends on how big the managed heap is and how much of it is garbage, and also whether any of the objects in the managed heap have finalize methods defined. Just as in Java, objects can define a finalize method that can be used as a last effort to release resources. If this method is defined, it gets called before the object's memory is reclaimed.

Calling the finalize method on all reclaimable objects slows down garbage collection. Therefore, as will be explained in later chapters, the finalize method should not be used for routine resource management.

Another performance bottleneck is large objects on the heap. Imagine the structure of your C: drive stored in memory as a tree. If an application continuously references this tree throughout the course of its execution, the tree will continue to remain on the heap and will never be garbage-collected. To deal with such large objects, it's best for these objects to be garbage-collected and accessed by the application at the same time, something that is implemented via weak references. This mechanism is based on the timing of access and collection. If only weak references to an object exist and if the GC runs, the object is collected; when the application later attempts to access the object, the access will fail. On the other hand, to access a weakly referenced object, the application must obtain a strong reference to the object. If the application obtains this strong reference before the GC collects the object, then the GC can't collect the object because a strong reference to the object exists.

You can also improve the performance of the GC by making it generational and compacting only portions of the heap. A generational GC assumes three things: that newer objects have shorter lifetimes, that older objects have longer lifetimes, and that newer objects have stronger relationships and are frequently accessed at about the same time. When initialized, the managed heap consists of generation 0 objects. When it's time for garbage collection, the objects in generation 0 become generation 1 objects, and the new objects that get allocated because of the freed space now become generation 0. This cycle of pushing the surviving objects to later generations works so that GC need scan only the youngest generation to reclaim space. If the GC cannot get what it needs by examining generation 0 objects, it then goes to the older generation. Therefore, the GC ends up scanning only part of the heap.

Note that GC algorithms are not set in stone. This is one of the pieces of the CLR that is guaranteed to be tweaked more often so as to maximize performance.

1.4.2 Garbage Collection in the JVM

The HotSpot JVM employs multiple algorithms for the young and old objects on its managed heap. For the younger-generation objects it uses the copying collector, and for the older generations it uses the compact collector.

Younger objects tend to be short-lived and closely related to each other, and, as explained earlier, this is true even for the Java-based managed heap objects. The HotSpot GC uses a copying collector for this generation. The copying collector is suitable for small, more frequent collections, which are typical of younger objects. For the older objects, which need to be reclaimed only when reclaiming younger objects doesn't free up enough memory, the compact collector is used because it is efficient for handling large amounts of data, although it is slower than the copying collector.

The HotSpot GC also provides the incremental collector, which is useful for high-availability applications and those that need to execute without user-noticeable pauses.

The HotSpot JVM has evolved significantly compared with the first iteration of the JDK 1.1 garbage collector. Similarly, the CLR garbage collector is certain to be fine-tuned for future versions. Interestingly, even though the JVM and the CLR might use different garbage collecting algorithms, they share very similar concepts when it comes to identifying and defining what constitutes garbage and object allocation on the heap. They both have the concept of a managed heap, dividing the heap into generations, and identifying garbage based on reachability from the root set. Additionally, both GCs support weak references.


1.5 Security Management

Security in the .NET Framework can be defined both declaratively and programmatically. Declarative security enables a programmer to specify an assembly's security requirements directly in the metadata of that assembly's code. The permission requests and all other forms of declarative security are specified as custom attributes in code. Annotations of classes, properties, and methods are used to fine-tune permissions. Because declarative attributes become part of an assembly's metadata, the security needs of the assembly are more easily identified. Declarative checks are the solution of choice when the requested action and permission are known at compile time.

Programmatic security is implemented directly in code. Programmers take security actions programmatically, and the permission is either granted or denied based on the state of the security stack. When a method issues a demand to access a particular file, for example, that demand will fail if the caller of the method has not been granted the necessary permission.

1.5.1 Policy-Driven Security Based on Code Evidence

The .NET Framework introduces the concept of evidence-based security. Evidence refers to inputs to the security policy about the code. The assembly is the unit of code for which evidence is gathered. When an assembly is loaded, the CLR policy system determines which permissions are to be granted. It does this by gathering the assembly's evidence and evaluating it in the context of the security policy.

The CLR policy system then grants a set of permissions to the assembly based on the evaluated evidence and any permission requests that the assembly may make. An assembly can never be assigned more permissions than the policy system would grant it in the absence of any permission requests; the requests serve only to further limit the grants. Security policy consists of a number of customizable code groups that contain the permissions to be granted based on evidence. Code groups might describe the permissions available to assemblies acquired from a specific security zone, or those that are signed by a specific publisher, and so forth.

It is important to remember that you can submit anything as evidence—provided that the security policy has a use for it—by defining a code group related to the evidence. The procedure for granting permission involves evaluating the evidence to determine which code groups are applicable at three levels: the enterprise, the machine, and the user. The policy evaluates these three levels in that order and then creates a permission set that is the intersection of all three. At runtime, permissions are evaluated based on the execution of code.

Here's an example. Let's say an assembly A3 provides its evidence, along with the evidence from the host plus any specific permission requests, to the policy evaluator. The evaluator then creates a set of permission grants G3 for assembly A3. Assembly A3 is called by A2, which in turn is called by assembly A1. The policy evaluator has permission grants G1 and G2 created for assemblies A1 and A2, respectively. When code in assembly A3 triggers a security check, then G1, G2, and G3 are all checked to see whether each has the permission requested in the security check. If any one of the grants does not include that permission, then the runtime throws a security exception. All the grants must contain the checked permission for that call to be successful.

Actions such as reading or writing files, displaying dialog boxes, and reading or writing environment variables are implemented using .NET Framework methods that are included within the framework security infrastructure. In this way, the .NET Framework can allow or disallow an action based on the security policy, without the need for any additional work by the programmer. Whereas authors of managed classes that expose protected resources must make appropriate security demands in their libraries, developers who use the .NET Framework class libraries to access a protected resource get the benefits of the code access security system "for free"; they do not have to make any explicit security calls.

1.5.2 Role-Based Security, Authentication, and Authorization

Code execution security is also enforced based on an authenticated identity or on the role associated with the context of the code's execution. The .NET Framework provides programmatic and declarative means to define the identities and principals that are the core of a role-based security system. Identities can map to the user logged in to the operating system or can be defined by the application. The corresponding principal encapsulates the identity, along with any related role information.

Authentication is the process of accepting credentials from a user and validating them against some authority. If the credentials are valid, the user is said to have an authenticated identity. Authorization is the process of determining whether that authenticated identity has access to a given resource. Authentication can be accomplished by either system or business logic and is available through a single API. The authentication API is fully extensible, so developers can use their own business logic as needed.

1.5.3 Isolated Storage

The .NET Framework provides a special facility, isolated storage, for storing data even when no file access is allowed. For example, when a managed control is downloaded from the Internet and run, it is given a limited set of permissions but not the right to read or write files. Isolated storage is a new set of types and methods supported by the .NET Framework for local storage. In essence, each assembly is given access to segregated storage on disk. No access to other data is allowed, and isolated storage is available only to the specific assembly for which it was created.

An application might use isolated storage to keep activity logs, save settings, or save state data to disk for later use. Because the location of isolated storage is predetermined, isolated storage provides a convenient way to specify unique space for storage without the need to determine file paths.

1.5.4 Cryptography

The .NET Framework provides a set of cryptographic objects that support encryption, digital signatures, hashing, and random-number generation, all of it implemented through well-known algorithms, such as RSA, DSA, Rijndael/AES, Triple DES, DES, and RC2, as well as the MD5, SHA1, SHA-256, SHA-384, and SHA-512 hash algorithms. The framework also supports the XML Digital Signature specification under development by the Internet Engineering Task Force (IETF) and the World Wide Web Consortium (W3C). The .NET Framework uses cryptographic objects to support internal services. The objects are also available as managed code to developers who require cryptographic support.

1.5.5 Security in Java

Java supports security at several levels. The Java language guarantees type safety and well-defined flow of execution. The JVM in turn executes only legitimate byte code through the byte code verifier. Access to system resources is mediated by the JVM and is checked in advance by a SecurityManager that restricts the actions of a piece of untrusted code to the bare minimum. JDK 1.3 employs an easily configurable security policy, an extensible access control structure, and the ability to create and handle new typed permissions without any extra programming from the user side.

The building block of the security system in Java is the protection domain: a set of objects that are accessible to accessors that have the required permissions. The protection domain concept serves as a convenient mechanism for grouping and isolating units of protection. For example, you can prohibit protection domains from interacting with one another so that any permitted interaction either must occur through trusted system code or must be explicitly allowed by the domains concerned.

Protection domains generally fall into two categories: system domains and application domains (see also Section 1.2.5). It is important that all protected external resources—such as the file system, the networking facility, and the screen and keyboard—be accessible only via system domains. A domain conceptually encloses a set of classes whose instances are granted the same set of permissions. Protection domains are determined by the policy currently in effect. The Java application environment maintains a mapping from code (classes and instances) to their protection domains and then to their permissions.

A domain with fewer permissions cannot gain additional permissions as a result of being called by a domain with more permissions. This security policy is similar to the CLR security policy, in which less powerful assemblies cannot gain additional permissions merely by virtue of being coexecuted with more powerful assemblies. A thread of execution may occur completely within a single protection domain or may involve an application domain and also the system domain. The permission set of an execution thread is considered to be the intersection of the permissions of all protection domains traversed by the execution thread. During execution, when access to a critical system resource (such as file I/O and network I/O) is requested, the resource-handling code directly or indirectly invokes a special AccessController class method, which evaluates the request and decides whether it should be granted or denied.

Java provides a rich security API that allows for programmatic access to defining, accessing, and evaluating permissions of a piece of code. The security policy has a specific syntax for declarative definition of the security permissions. Java also has role-based security and APIs for cryptography, authentication, and authorization. The concept of keys, certificates, and signed code is common to both the CLR and the JVM. However, JDK 1.3 does not provide isolated storage.


1.6 Exception Management

Programming for exceptions and errors is a key concept of managed execution environments. Applications can achieve predictable flow of execution as long as they execute successfully. However, this well-defined path can be disturbed in the event of an error or an exception. A good exception management service allows developers to trap these exceptions and errors and thereby streamline their processing so that the predictability of the application is maintained even when it is on the road to failure. Such a management service allows for recovery from failure and discovery of replicable error paths in the application. Knowing how an application can fail is as important as knowing how an application can execute successfully. It makes troubleshooting applications very easy.

Exceptions represent a breach of an implicit assumption made within the code. For example, if your code tries to access a file that is assumed to exist, but the file is missing, an exception will be thrown. However, if your code does not assume that the file exists and thus checks for its presence first, this scenario will not necessarily generate an exception.

1.6.1 Exception Management in the CLR

The .NET CLR provides exception management by handling exceptions, logging exceptions, and generating events for notification that can be monitored externally to assist system operation. Exceptions can be handled explicitly by the programmer through coding (similar to checked exceptions in Java) or by the runtime (similar to unchecked exceptions and errors in Java).

Figure 1.2 shows a flowchart of the process of detecting and propagating exceptions. If the current method detects an exception, then it has the option of handling the exception within itself and recovering from it, or it can add any contextual information to the exception and let it propagate to the caller of the method. The onus of handling the exception then lies with the caller of the method. The exception can thus "bubble up" to the topmost method call, at which point the application can handle the exception before returning it to the user (as in a stack trace in Java). The application cannot recover from this exception. It must gather information, log information, perform any cleanups, send notification, and do any other exception processing before showing the user the exception.

Figure 1.2. Flowchart for Exception Detection and Propagation

You can code for exception detection using the try-catch-finally construct. The try block contains code that can throw an exception. When an exception is thrown within this block, the first catch block whose filter matches the class of the exception catches it. Note that the filter is specified in parentheses following the catch keyword. If you have multiple catch blocks, you should ensure that they are ordered from the most specific type to the most generic type. This arrangement ensures that the most specific type of catch block is executed for any given exception. The finally statement executes, allowing cleanup and other code to be executed, whether or not an exception is thrown.

You should catch exceptions only when you need to specifically perform any of the following actions:

  • Gather information for logging

  • Add any relevant information to the exception

  • Execute cleanup code

  • Attempt to recover

If a particular method does not need to perform any of these actions, it should not catch the exception; rather, it should allow it to propagate back up the call stack. This keeps your code clean and explicit because you catch only the exceptions that need to be handled within the scope of a particular method and allow all others to continue to propagate. In certain scenarios, an exception you throw may be caught by the runtime, and an exception of another type may be thrown up the call stack in place of the original. In these scenarios your original exception is not lost. Instead, it is set as the InnerException property of the original exception.

There are three ways to propagate exceptions:

  1. Let the exception propagate automatically. With this approach, you do nothing and deliberately ignore the exception. This causes control to move immediately from the current code block up the call stack until a catch block with a filter that matches the exception type is found.

  2. Catch and rethrow the exception. With this approach, you catch and react to the exception and clean up or perform any other required processing within the scope of the current method. If you cannot recover from the exception, you rethrow the same exception to your caller.

  3. Catch, wrap, and throw the wrapped exception. As an exception propagates up the call stack, the exception type becomes less relevant. When an exception is wrapped, a more relevant exception can be returned to the caller.

The .NET Framework has APIs for logging exceptions to the Windows log, database, or a log file; for sending them as asynchronous messages to a message queue or a Simple Mail Transfer Protocol (SMTP) mail; and for creating events out of them to be sent to an external monitoring system that is decoupled from the main application. The monitoring system then notifies interested parties to take appropriate action. This support for logging, monitoring, and notifying is built into the .NET Framework.

1.6.2 Exception Management in Java

Most of the CLR exception management mechanism just described also exists in the Java environment. Java differentiates, however, between checked and unchecked exceptions. Checked exceptions are those that are explicitly coded for. Unchecked (runtime) exceptions and errors don't have to be caught and are propagated automatically up the method call stack.

Java has a try-catch-finally construct that can be used to detect, throw, and propagate exceptions. What is missing in the current JDK 1.3 is any built-in support for exception logging to the operating system log, database, and file system. Also, there is no built-in support for asynchronous or synchronous delivery of these exceptions as messages to a message queue or an SMTP mail. Perhaps the reason these features are missing is that the Java designers did not want to make exception logging and notification part of the core exception management services of JDK 1.3. For checked exceptions, it is relatively easy to simulate any kind of logging, notification through third-party logging APIs (such as log4j), JavaMail, and Java Messaging Service (JMS). Future versions of Java may provide hooks to external logging and monitoring systems for unchecked runtime exceptions and errors.


1.7 Overview of the .NET Framework Class Library

This book is about exploring the .NET Framework C# class library. Like the Java API, the .NET Framework provides namespaces with classes that can be used to code myriad enterprise tasks. The .NET Framework class library can help you implement many tasks, including accessing databases, creating graphics, XML processing, networking, reflection, messaging, security, and directory services. The framework is vast, and to discuss all the namespaces is beyond the scope of this book. Instead, you will encounter some of the commonly used namespaces as we explore C# fundamentals. In addition, we will look at some sample namespaces for implementing some of the enterprise tasks (database access, graphics, XML processing).

Table 1.2. .NET Namespaces Covered in This Book

.NET Framework Namespace

Description

System

Contains core classes

System.Collections

Collection classes (similar to Java collections)

System.Data, System.Data.Common, System.Data.OleDb, System.Data.SqlClient, System.Data.SqlTypes

Classes for accessing databases

System.Windows.Forms, System.Windows.ComponentModel, System.Drawing

Classes for graphics and design of Windows Forms

System.IO, System.IO.IsolatedStorage

Classes for disk I/O

System.Reflection, System.Reflection.Emit

Classes for reflection

System.Text

String processing classes

System.Threading

Thread support in .NET

System.Xml, System.Xml.Schema, System.Xml.XPath, System.Xml.Xsl

Classes for XML processing.

System.Diagnostics

Classes for conducting operating system-level tasks

Table 1.2 shows the namespaces we explore in this book.


1.8 Summary

The .NET Framework is a small part of the grander vision that Microsoft has for its .NET initiative. The framework supports code management services by providing a managed runtime execution environment called the Common Language Runtime. The CLR is language-neutral, and therefore any component written in a language and compiled with a compiler that adheres to the Common Language Specification compliance rules can be used or extended by a component written in an entirely different language, as long as that language also is CLS-compliant. The CLR achieves this magic through the Common Type System (CTS) and metadata, which is the invisible glue that holds everything together. Some of the important code management services offered by the CLR are memory, security, exception, serialization and thread management, debugging and profiling, versioning, and automatic integration of multilanguage components.

The CLR is similar in some aspects to the Java Virtual Machine. The JVM and the CLR offer certain common code management services, and both execute code by converting the managed code to an intermediate binary form, which is then converted to machine code using a dynamic just in time compiler (JITer).


Chapter 2. Starting with C#

The best way to learn a new programming language is to start using it, and so this chapter gets down to the business end of programming. We introduce the proverbial C# Hello World program—first without using the Visual Studio .NET IDE, and then using the IDE. We also explore some of the other tools that ship with the .NET SDK.


2.1 The .NET SDK

The .NET SDK is the equivalent of Java's JDK, which is available from the JavaSoft Web site (http://www.javasoft.com). The .NET SDK is available for download from the Web or can be obtained in CD format (see http://www.microsoft.com). The .NET SDK comes with a compiler, the runtime engine, and a slew of other tools for programming in C#. These tools are all you need to write a C# program. The .NET SDK is currently available for use only on the following Windows platforms: Windows 2003, Windows 2000, Windows XP, Windows 98, and Windows NT.

Traditionally, programming languages from Microsoft have been tightly integrated with the supplied IDE. This means that you must use that IDE to write programs in that language. C# breaks from that tradition and is very Java-like when it comes to writing code. You can write C# programs using a simple text editor, and you will be good to go.


2.2 Writing Your First C# Program

We assume here that you have successfully downloaded and installed the bare bones .NET SDK on your C: drive. To compile your C# programs, you need the compiler, which is a file called csc.exe. By default, the compiler converts C# code to a shell executable file that can be run like any EXE file. Think of this EXE file as the Java runtime, with all the classes to be run put in one file. To compile your code from anywhere, you must set the directory so that the csc.exe file is in your system PATH environment variable. You can check this by typing csc at the command prompt, as shown in Listing 2.1. Note that fatal error CS2008 is really reminding you that the compiler has no files to compile.

Listing 2.1 Output from Typing csc at the Command Prompt
Microsoft (R) Visual C# .NET Compiler version 7.00.9466
for Microsoft (R) .NET Framework version 1.0.3705
Copyright (C) Microsoft Corporation 2001. All rights reserved.
fatal error CS2008: No inputs specified

You can obtain the various compiler options by typing csc /? at the command prompt. The default command, csc, should be sufficient for you to get started writing and compiling programs.

Let's write our first C# program. Open your text editor. Without typing anything in it, save your program file as MySample.1.cs. Here .cs is the file extension. At the command prompt, type csc MySample.1.cs

Listing 2.2 shows the output from the compiler. The compiler complains that the program lacks an entry point. It does not create the Listing.EXE file as you would expect. This behavior is similar to that of the JDK, which does not create a class file when you try to compile a Java file of size 0 bytes (although it does not issue a complaint). An EXE file is a stand-alone executable. For it to execute, the runtime thread or process should enter at a specific point so that it can continue execution from that point on.

Listing 2.2 Output from Typing csc MySample.1.cs (the Empty File) at the Command Prompt (C#)
Microsoft (R) Visual C# .NET Compiler version 7.00.9466
for Microsoft (R) .NET Framework version 1.0.3705
Copyright (C) Microsoft Corporation 2001. All rights reserved.

error CS5001: Program 'MySample.1.exe' does not have an entry
point defined

Now add an entry point to the file you just created. To do that, type the code in Listing 2.3 in the MySample.1.cs file.

Listing 2.3 Printing the First Argument to the Console (C#)
1 public class HelloWorld {
2   public static void Main(string[] args) {
3     System.Console.WriteLine(args[0]);
4   }
5 }

To write the preceding code in Java, create a file called MySample1.java (see Listing 2.4). (The significance of the file name is that Sun's JDK won't let you compile a file containing a public class whose name is different from its file name. Furthermore, JDK 1.3 does not allow the period symbol, and therefore the class must be MySample1 instead of MySample.1.)

Listing 2.4 Printing the First Argument to the Console (Java)
1 public class MySample1 {
2   public static void main(String[] args) {
3     System.out.println(args[0]);
4   }
5 }

Line 2 of Listings 2.3 and 2.4 is the entry point. In Java the public static void main is called by the main thread, which is initiated by the JVM. In C# the entry point is called by the CLR.

The following observations apply to the C# code:

  • In C# the class name is HelloWorld, and it is stored in a file having a different name.

  • The Main method must be static.

  • The public modifier on the class is optional. However, the modifier cannot be private or protected.

  • The modifier on the Main method is optional, and it can be public, private, or protected.

  • In C#, the Main method is somewhat overloaded. There is a Main method without any arguments. Because the Main method is an entry point, there can be only one in the class. So even though there are two Main methods—one with parameters and one without—a class can have only one defined.

  • An EXE file can have only one entry point. So if your .cs file (source) has multiple classes in the same file, only one of the classes can have a Main method defined. (We will get to that topic a little later in this chapter.)

To run the C# program now, you type the following command at the prompt:

MySample.1 "Hello World"

The output of MySample.1.exe is as follows:

Hello World

Line 3 of Listing 2.3 looks similar to line 3 of Listing 2.4. In Java, the System class is found in the java.lang package, which you don't have to explicitly import. In C#, the rough equivalent of a Java package is the namespace concept. Here, System is the namespace and Console is one of the classes of the System namespace.

Importing a package in Java is supported in C# by the keyword using. Specifying using System means that your code can refer to all the classes in that namespace without fully qualifying their names. It is similar to the import facility in Java. The more general the import is, the more you will have to qualify your classes in the code. As you get more specific imports, you can avoid giving fully qualified class names to variables. In C# the same rule applies to the using keyword: The more specific the class you denote with the using keyword, the less crowded your code will be.

Listing 2.5 is an example of the namespace and using keywords in C#, and Listing 2.6 is the equivalent Java program.

Listing 2.5 Use of the using and namespace Keywords (C#)
1 namespace Test {
2     using System;
3     public class HelloWorld {
4       public static void Main(string[] args) {
5         Console.WriteLine(args[0]);
6       }
7     }
8 }
Listing 2.6 Using Packages in Java
package Test;
import java.io.*;
public class HelloWorld {
    public static void main(String[] args) {
    PrintStream out = System.out;
    out.println(args[0]);
    }
}

The package structure in Java corresponds to the way source files are stored on the disk. So a class with a package Test will be stored under the Test directory under the JVM. In contrast, the namespace structure in C# has no relation to the way source files are stored on the disk.

Also note that the using keyword works only with namespaces. You cannot write using System.Console and call the method WriteLine on its own, because System.Console is a class and not a namespace. In contrast, Java lets you import all classes of a package or a specific class of that package. This difference in behavior means that the C# keyword using is not an exact match for Java's import keyword.

So far our examples have shown one source file per class. Although this practice is the preferred Java programming paradigm, it is not required. You can have multiple classes in one Java source file, but only one of them can be public. In C#, however, you can have multiple classes in the same .cs and have all these classes be public. Listing 2.7 shows MyHelloWorldApp.cs using multiple classes in the same file. The using keyword should always precede all other namespace elements, so you can also use the Console class in the HelloWorld1 class.

Listing 2.7 Multiple Classes in the Same Source File (C#)
namespace Test {
     using System;
     public class HelloWorld {
       public static void Main(string[] args) {
         Console.WriteLine("Inside HelloWorld");
         Console.WriteLine(new HelloWorld1());
       }
     }
     public class HelloWorld1 {
       public override string ToString() {
         Console.WriteLine("HelloWorld1 ToString()");
         return "Hello";
       }
     }
}

Listing 2.8 shows the Java equivalent.

Listing 2.8 Multiple Classes in the Same Source File (Java)
package Test;
  public class MyHelloWorldApp {
    public static void main(String[] args) {
      System.out.println("Inside HelloWorld");
      System.out.println(new HelloWorld1());
    }
  }
  class HelloWorld1 {
    public String toString() {
      System.out.println("HelloWorld1 toString()");
      return "Hello";
    }
  }

Compile Listing 2.7 using the command line csc MyHelloWorldApp.cs. Then run the program by typing MyHelloWorldApp at the command prompt. You will see the output shown in Listing 2.9.

Listing 2.9 Output of the MyHelloWorldApp Program
Inside HelloWorld
HelloWorld1 ToString()
Hello

In Java, if you have two classes—A and B—compiled to different files, A can call methods in B and vice versa as long as the default class loader can load the individual A and B classes from the specified classpath. For the same situation in C#, class A can access methods in the file containing class B as long as you specify at the time of compilation that the file containing class A uses classes in the namespace defined by the file containing class B. For example, the class HelloWorld2 in one source file (MyHelloWorldApp2.cs) can access the class HelloWorld1 in the file MyHelloWorldApp.cs if, at the time of compilation of MyHelloWorldApp2.cs, you specify that it uses classes in the namespace defined by MyHelloWorldApp.cs.

Listing 2.10 shows an example of how to code this. Note that the generated MyHelloWorldApp.exe file has all the metadata required to define the classes in MyHelloWorldApp.cs.

Listing 2.10 A Class That Accesses Another Class Defined in a Different Namespace (C#)
1 namespace Test2 {
2   using System;
3   using Test;
4   public class HelloWorld2 {
5     public static void Main(string[] args){
6       Console.WriteLine("Inside HelloWorld2");
7       Console.WriteLine(new HelloWorld1());
8     }
9   }
10}

Listing 2.11 shows the Java equivalent of Listing 2.10.

Listing 2.11 Java Equivalent of Listing 2.10
1     package Test2;
2     import Test.*;
3     public class MyHelloWorldApp2{
4         public static void main(String[] args){
5           System.out.println(new HelloWorld1());
6         }
7       }

The MyHelloWorldApp2.java class imports the classes in the Test package and then accesses the MyHelloWorldApp class of that package. Note that because the MyHelloWorldApp2 Java class is in a different package, it cannot have access to nonpublic classes in other packages.

In C#, to make MyHelloWorldApp2.cs run, compile it with the following command:

csc MyHelloWorldApp2.cs /r:MyHelloWorldApp.exe

To run it, call MyHelloWorldApp2 at the prompt. You will see the output shown in Listing 2.12.

Listing 2.12 Output of Running MyHelloWorldApp2.exe
Inside HelloWorld2
HelloWorld1 ToString()
Hello

An interesting point is that the HelloWorld2 and HelloWorld classes both have entry points defined. So when Listing 2.10 (containing the HelloWorld2 class) is compiled with the /r option, the resulting EXE file created gives preference to the entry point of the primary class being compiled. Also note that HelloWorld needs to be public for HelloWorld2 to access it. For more on class and method access modifiers, see Chapter 4.

It should be clear from the preceding examples that a C# EXE file is not equivalent to a Java class file. An EXE file is a self-sufficient file that contains all the information to run the entry point. It will identify and load the metadata of all the dependent classes that the entry point depends on. This obviates the need for a classpath. In Java, a single class dependent on other classes can be run provided that all the classes are accessible to the runtime through the classpath variable. You can combine several .cs files, all having entry points, and compile them into a single EXE file; you use the /m option of the compiler to specify at the time of compilation which class is to be used as the entry point.


2.3 C# Performance

Java programmers know about Java's performance issues, so at this point it is only fair to quickly glance at C#'s performance.

A good test of a language's performance is to see how long it takes the two languages to iterate over a for loop 10 million times. Listing 2.13 shows the C# code, and Listing 2.14 shows the corresponding Java code. Note that in C# Environment.TickCount returns the time elapsed in milliseconds since the system started. This is a close equivalent of Java's System .currentTimeMillis() function.

Listing 2.13 Iterating 10 Million Times (C#)
1     using System;
2     public class ForLoopTest {
3       public static void Main(string[] args) {
4           long start = Environment.TickCount;
5           for (int i =0;i < 10000000; ++i) {
6           }
7            long end = Environment.TickCount;
8            Console.WriteLine((end-start));
9           }
10    }
Listing 2.14 Iterating 10 Million Times (Java)
1     public class Performance {
2     public static void main(String[] args) {
3          long start = System.currentTimeMillis();
4          for (int i=0; i < 10000000; ++i) {}
5          long end = System.currentTimeMillis();
6            System.out.println(end-start);
7     }
8     }

The C# console prints 50, and the Java console prints 120. With this simple test we cannot conclude that the C# runtime is faster than the Java runtime; these results depend on several factors, such as hardware and software configuration. Java programmers might be curious to see such kinds of performance tests, but it is beyond the scope of the book to jump into performance testing.


2.4 C# Tools

This section explores some of the tools provided with the .NET SDK that can improve your C# programming productivity.

2.4.1 Visual Studio .NET

Microsoft provides an IDE for fast and productive development with C#. Visual Studio .NET is an IDE for doing .NET development with Visual Basic, Visual C++, and C#. Figure 2.1 shows the start screen of Visual Studio .NET.

Figure 2.1. Microsoft Visual Studio .NET Start Screen

Typically, IDEs are helpful when you have a lot of files to compile, and so you would group them into projects. Therefore, let's start a new project.

Step 1

Click on the New menu option under the File menu item.

Step 2

Click on the Project submenu option. You will see a dialog box (Figure 2.2). Select the Visual C# option on the left side. On the right side you will see several kinds of applications that you can create using the IDE. Click on Console Application.

Figure 2.2. Dialog Box Showing Project Types

Step 3

Type the name of your new project in the Name textbox, and type the name of the location where you want to create the new project. For this example, type Project1 for the name, and type C:\WorkingFolder for the location. Figure 2.3 shows what you see next.

Figure 2.3. Project1 Template for a Console Application

Step 4

The IDE creates a template class that you can modify. The class file is named Class1.cs. This name is not very intuitive, so save the file as Hello.cs and delete the Class1.cs file from the C:\WorkingFolder\Project1 directory. Now change the name of the class to Hello. It is not necessary that the class name be the same as the file name, but as you will realize when you have large projects with lots of files, it's easier to associate classes with files if each file has only one class with the same name.

Step 5

The comments that you see above the class definition and the Main method are the C# equivalent of the Javadoc documentation file. This file is XML-based. Remove the TODO comment from the body of the Main method. Type the following line inside the Main method's body:

Console.WriteLine("Hello");

The final screen looks something like Figure 2.4.

Figure 2.4. The Final Screen of the Project before Building

While typing the text in the window, you may notice pop-up boxes. This is the Visual Studio IntelliSense feature, which shows you the syntax of the construct as you are typing it.

Step 6

Now save your project by clicking on File and then Save All.

Step 7

Click on the Build menu and then click on the Build Project1 menu option.

The IDE pops up a small window indicating the status of the build process, as shown in Figure 2.5. If you now explore the c:\Workingfolder\Project1 directory, you will see that a lot of files and directories have been created. The Project1.exe file is created in the c:\Workingfolder\Project1\bin\Debug directory. You can run this program from the command line now. To add more class files to the project, click on the Project menu and then on Add Class. To remove any class file from the current project, click on Project and then on Exclude from Project.

Figure 2.5. The Final Screen of the Project after Building

Behind the scenes, all your project details are stored in an XML file called Project1.csproj. If you open the file using WordPad, you will notice that the various project files are listed. The IDE also creates an AssemblyInfo.cs file that essentially is a programmable instruction set for deploying the project. You will learn about assemblies and deployment in detail in Chapter 23. We will also discuss the IDE when it comes time to create new types of applications, such as class libraries, Webforms, Windows applications, and so on.

2.4.2 The Intermediate Language Disassembler

The Intermediate Language Disassembler (ILDASM) can do the following:

  • Open an assembly

  • Show all types in an assembly

  • Show the methods defined for those types and the IL (Intermediate Language) that was generated for those methods

To see the IL code generated for MySample.1.cs, you open the MySample.1.EXE file using the ildasm executable. The ildasm executable can be found in the following path:

C:\Program Files\Microsoft Visual Studio .NET\FrameworkSDK\Bin

If you have trouble finding the file, just do a search on the file name.

After you start the ILDASM tool, click on File and then Open. Select the EXE file you want to view. The tool then displays the EXE file in a tree format. To see the IL of the individual tree components, highlight the component and then press Enter. Alternatively, you can do a tree dump of the file in an IL file. Click on File and then Dump. A dialog box pops up asking to save the dump in an IL file. Save the details in the Project1.il file under c:\Workingfolder\Projects\bin\debug. Listing 2.15 shows the contents of the dump file.

The lines starting with // and /* and */ are comments, so you can edit them if you want to. The ILDASM can be called on the command line, too. Figure 2.6 shows the details of the Project1.IL file without the comments.

Listing 2.15 Contents of the Dump File
// Microsoft (R) .NET Framework IL
Disassembler.Version 1.0.3512.0
// Copyright (C) Microsoft Corporation 1998–2001.
All rights reserved.

.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
// .z\V.4..
  .ver 1:0:3300:0
}
.assembly Project1
{
  .custom instance void
[mscorlib]System.Reflection.AssemblyCopyrightAttribute
::.ctor(string)
= ( 01 00 00 00 00 ) .custom instance void
[mscorlib]System.Reflection.AssemblyKeyFileAttribute
::.ctor(string)
= ( 01 00 00 00 00 )
  .custom instance void
[mscorlib]System.Reflection.AssemblyDelaySignAttribute
::.ctor(bool)
= ( 01 00 00 00 00 )
  .custom instance void
[mscorlib]System.Reflection.AssemblyTrademarkAttribute
::.ctor(string)
= ( 01 00 00 00 00 ) .custom instance void
[mscorlib]System.Reflection.AssemblyKeyNameAttribute
::.ctor(string)
= ( 01 00 00 00 00 )
  .custom instance void [mscorlib]System.Reflection.
AssemblyProductAttribute
::.ctor(string)
= ( 01 00 00 00 00 ) .custom instance void
[mscorlib]System.Reflection.AssemblyCompanyAttribute
::.ctor(string)
= ( 01 00 00 00 00 )
  .custom instance void
[mscorlib]System.Reflection.AssemblyConfigurationAttribute
::.ctor(string)
= ( 01 00 00 00 00 )
  .custom instance void
[mscorlib]System.Reflection.AssemblyDescriptionAttribute
:.ctor(string)
= ( 01 00 00 00 00 ) .custom instance void
[mscorlib]System.Reflection.AssemblyTitleAttribute
::.ctor(string)
= ( 01 00 00 00 00 )
//—- The following custom attribute is added automatically,
  do not uncomment———-
      // .custom instance void
[mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(bool,
  //bool) = ( 01 00 01 01 00 00 )
  .hash algorithm 0x00008004
  .ver 1:0:715:27376
}
.module Project1.exe
// MVID: {B4CFC431-2D46-4A50-99FF-63874717B63F}
.imagebase 0x00400000
.subsystem 0x00000003
.file alignment 512
.corflags 0x00000001
// Image base: 0x03650000
//
// ============== CLASS STRUCTURE DECLARATION =================
//
.namespace Project1
{
  .class private auto ansi beforefieldinit Hello
         extends [mscorlib]System.Object
  {
  } // end of class Hello

} // end of namespace Project1
// ======== GLOBAL FIELDS AND METHODS ===================
// =============== CLASS MEMBERS DECLARATION ==================
//  note that class flags, 'extends' and 'implements' clauses
//      are provided here for information only

.namespace Project1
{
  .class private auto ansi beforefieldinit Hello
         extends [mscorlib]System.Object
  {
    .method private hidebysig static void
            Main(string[] args) cil managed
    {
      .entrypoint
      .custom instance void [mscorlib]System.
    STAThreadAttribute::.ctor()
= ( 01 00 00 00 )
       // Code size     11 (0xb)
       .maxstack 1
       IL=_0000: ldstr  "Hello"
       IL=_0005: call   void [mscorlib]System.Console::
     WriteLine(string)
       IL=_000a: ret
   } // end of method Hello::Main

     .method public hidebysig specialname rtspecialname
             instance void .ctor() cil managed
       {
       // Code size      7 (0x7)
       .maxstack 1
       IL=_0000: ldarg.0
       IL=_0001: call    instance void [mscorlib]System.
       Object::.ctor()
       IL=_0006: ret
       } // end of method Hello::.ctor

     } // end of class Hello
//=============================================================
} // end of namespace Project1

//*********** DISASSEMBLY COMPLETE ***********************
// WARNING: Created Win32 resource file
C:\Workingfolder\Project1\bin\Debug\Project1.res
Figure 2.6. Project1.IL

The details of the IL language can be found in Ilinstrset.doc, which is in C\Program Files\Microsoft.NET\FrameworkSDK\Tool Developer Guide. Reading the screen dump of Figure 2.6, you can see that the Hello class has two methods. One is the constructor, which is automatically provided by the runtime for any class, and the other is the Main method. You can see that the Main method contains the entry point instruction indicating to the CLR that it is the entry point method. The next instruction of the Main method is ldstr, which loads the string literal into the local method stack. This is followed by a call to the framework class, System.Console. The last instruction is ret (which is the return of the Main method).


2.5 Summary

You can write C# programs both from the command line and by using the Visual Studio .NET IDE. In its simplest form, a C# application is an EXE file that you create by compiling one or many class files. For the program to execute, you must define an entry point, and a C# EXE file can have only one entry point.

Java programmers should be aware of the following key points.

  • There is no classpath in C#. All the data required to run the EXE file is defined in the EXE file.

  • C# allows multiple public classes in one source file.

  • C# does not require that you give the same name to the class file that you give to the public class in that file.

  • The Main method of C# must be static (just as in Java).

  • C# namespaces are roughly equivalent to Java packages, but their structure has no relation to the way source files are saved on the disk. Java packages dictate the directory structure of the source files on the disk, but C# namespaces do not.

  • In Java you can import a package or a specific class in that package (or both) in the same source file. In C# you cannot use the using keyword with classes. The using keyword can be used only on namespaces.

  • As in Java, every C# class comes with a default no-argument public constructor (this is evident from the IL).


Chapter 3. C# and Java: What Is the Difference?

In June 2000, Microsoft introduced both the .NET platform and a new programming language called C#. The C# language was built with the hindsight gained from experience with many languages, most notably Java and C++. As a result, C# shares many similarities with Java. However, there are also some differences between the two languages—differences that go beyond syntax and constructs. To truly appreciate the differences and similarities in the languages, we encourage you to dive right into the rest of the chapters. This chapter gives you a snapshot of the differences and the similarities, which are explained in detail in the forthcoming chapters.

We will compare C# and Java using the following categories:

  • Programming platforms (.NET and Java)

  • Runtime architecture

  • The object-oriented programming (OOP) model

  • Language constructs, syntax, and the application programming interface


3.1 Programming Platforms (.NET and Java)

With the advent of C# and Java, programmers discovered a new term: programming platform. Programming platforms are now becoming as relevant as the languages in which you program.

The bottom line in providing platforms such as Java and .NET is to make it easier for the programmer to develop enterprise applications. Enterprise applications tend to be complex because they encompass several components (or subsystems) running on various operating systems under various runtime conditions. An enterprise application that is written from the ground up will directly interact with external code through the following:

  • Components or subsystems (such as databases and graphics)

  • Operating systems (such as Linux and Windows)

  • Runtime conditions (such as hardware, available memory, processors, and server nodes)

If you program from the ground up, your application will directly interact with any of these three sources of external code. Any changes made in the external code would require you to rewrite your application. To avoid having to change your application when external code changes, you need a piece of software that sits between the external code and your application and handles the changes made to the external code. Programming platforms provide this extra level of indirection (and insulation).

Platforms also abstract out routine enterprise tasks and package them into high-level, easy-to-use application programming interfaces (APIs). Routine enterprise tasks can consist of accessing databases, messaging, e-mailing, parsing XML, networking, supporting telephony, maintaining advanced GUIs, running Web applications, and so on. Platforms provide basic infrastructure services that developers can leverage using the language API.

Both C# and Java offer a level of sophistication in programming that is important if you want to implement enterprise applications rapidly. These two languages provide a one-stop, high-level API for doing myriad enterprise-level programming tasks. However, the API is merely an abstraction provided to the developer to interact with the platform; it is the platform that translates the high-level API calls to the low-level system calls.

C# runs on the .NET platform, whereas Java runs on the Java platform. Note that "Java" is used to refer to the platform as well as the language.

3.1.1 The Java Platform

Sun Microsystems has reorganized the Java platform into three main categories:

  1. J2SE: The Java platform Standard Edition targeted at desktop machines

  2. J2ME: The Java platform Standard Edition targeted at handheld devices

  3. J2EE: The platform for developing large-scale, server-side enterprise applications typically deployed on load-balanced server clusters

Although it's somewhat of a simplification, you can think of J2ME as a subset of J2SE and think of J2SE as a subset of J2EE. J2EE is a Java-based technology stack built on top of J2SE, and J2EE provides development and runtime tools used by developers to build enterprise applications. The latest J2EE technology stack (J2EE 1.3) consists of the following components: JavaServer pages (JSP), servlets, Enterprise JavaBeans (EJBs), Java Messaging Service (JMS), Java Connectivity Architecture (JCA), Java Naming and Directory Interface (JNDI), and Java Management Extensions (JMX). Although Java Database Connectivity (JDBC) and the HotSpot VM are defined in the J2SE stack, they are also an integral part of the J2EE stack.

It is important to note that J2EE is a platform specification and not a product. Java programmers get to pick from a wide variety of vendors that sell J2EE application servers. These servers are products built on top of the J2EE specification.

3.1.2 The .NET Suite

As indicated in Chapter 1, the term ".NET" covers a lot of ground. Unlike J2EE, which is a set of specifications, .NET is a suite of products (although some parts of .NET have been ratified as standards). The suite consists of the following:

  • Smart client software that runs on handheld devices, PDAs, and embedded systems.

  • XML Web services, which are used for integrating applications. There are commercial implementations of these Web services. For example, MapPoint is a Web service for providing mapping and driving directions in applications.

  • Enterprise servers, such as BizTalk and SQL Server.

  • Enterprise development tools such as Visual Studio .NET and the .NET Framework. These consist of framework classes, the CLR, ASP.NET, and managed code.

As a C# programmer, you are concerned mostly with the .NET Framework.

As mentioned earlier, both Java and .NET equip you with tools to develop an enterprise application. A somewhat routine and ubiquitous enterprise application is any nontrivial Web-based application (such as a company intranet, an e-commerce site, or a Web-based collaboration tool).

An application typically consists of three layers: the presentation layer, the business logic layer, and the data layer. The next section compares the components of J2EE and the .NET Framework that enable you to build the three layers.

3.1.3 The Presentation Layer

The J2EE specification provides for servlets and JSPs for generating dynamic HTML. The servlets and JSPs are run by servlet containers. These containers provide support for maintaining session state, security, and configuration. Tag libraries are used to make JSPs more readable to HTML coders.

ASP.NET runs Web applications using Microsoft's Internet Information Server (IIS). ASP.NET includes facilities for managing session state, security, and HTML generation using ASPX. You can match server-side Web controls using custom ASP tags. Web controls encapsulate reusable user interface logic and provide many advanced features to ease Web programming.

3.1.4 The Business Logic Layer

There is no standard definition for the term "business logic," but anything that encompasses common enterprise computing tasks—such as processing transactions, creating XML Web services, or calling methods on distributed remote objects to execute business rules based on triggered events or messages—would fall under this layer. Let's look at some examples.

Transactions

J2EE application servers support manual and container-maintained transactions through EJB. In .NET, transactions can be manual or automatic. Manual transactions can be error-prone but give finer control, whereas automatic transactions ease development.

Calling Remote Objects

Execution of business rules based on certain events or triggers is usually wrapped in server-side components such as EJBs (Java) or JMS queues. EJBs are distributed components, and JNDI provides location transparency in identifying EJBs for executing specific business rules. All calls are made through interfaces. In practice, application server vendors tend to optimize the distribution and clustering of EJBs. In this way, method calls usually end up within the same virtual machine instance, thereby avoiding out-of-process method calls.

With .NET remoting, developers can call remote objects distributed across application domains, processes, and machine boundaries. In .NET you can't automatically decide to distribute your objects as you can in J2EE, because you pay a price in network latency when you do so. In .NET, developers employ remoting to increase security and ease maintenance but not to improve scalability; you usually add Web servers for that task.

XML Web Services

By downloading and installing the Java Web Services Developer Pack from http://java.sun.com, you can begin creating Web services. Sun got on board the Web services bandwagon a little later than Microsoft, and that can be seen in Sun's current support for Web services. Microsoft placed XML Web services at .NET's core, and it shows. .NET contains the latest accepted XML Web service standards.

3.1.5 The Data Layer

The bread and butter of any enterprise application is data, which typically is stored in relational database management systems such as Oracle or Microsoft SQL Server.

Java has abstracted out the raw SQL calls to any database into a wonderful abstract layer called JDBC. The JDBC API provides everything you need to access an enterprise API. The API is used to interact with a JDBC driver, which is the pipeline through which your SQL goes to the database server. J2EE supports two approaches to accessing this data. One is through bean managed persistence (BMP), which essentially uses the JDBC driver to make the raw SQL calls to the underlying database. In the second approach, container managed persistence (CMP), you use object relational mapping to map objects to database tables, thereby providing automatic persistence to the database without needing to generate the raw SQL.

With the .NET Framework, developers can access a variety of data sources, including XML, through .NET's ADO.Net classes. ADO.Net includes data providers that let you connect to a database and execute commands. Developers can also transport data over the Web using XML Web services, or across process boundaries using .NET remoting.


3.2 Runtime Architecture

As mentioned earlier, both Java and C# differ from traditional languages such as C, C++, and Visual Basic because the code generated by Java and C# runs in a controlled execution environment. This execution environment provides infrastructure services such as memory management, security checks, and type validation to the compiled or interpreted code. Table 3.1 compares the runtime environments of Java and .NET.

Although the CLR and JVM look deceptively similar in their roles, you should note some key differences.

3.2.1 The JVM

The JVM runs only byte code, also called managed or safe code (code devoid of pointers).

Typically, a single JVM instance maps to a single operating system (OS) process. Multiple applications can run on a single JVM, but there is no logical partitioning of the applications inside a JVM. All applications inside a JVM share the same process resource. This makes interapplication communication inside a JVM (or in-process communication) fast and easy, but it has a major drawback: A single rogue application consuming a lot of memory can bog down the entire JVM instance, thereby affecting other applications running on the same JVM instance. Out-of-process communication is implemented through Remote Method Invocation (RMI) and serialization, which are the foundation of distributed computing in Java. EJBs distributed on different JVMs communicate with each other through RMI calls. A JVM instance is lighter than an OS process and can be embedded in devices. Alternatively, a JVM instance can be hosted by an application server or can be run as a stand-alone program.

Table 3.1. Runtime Execution Environments for Java and .NET

Environment Element

Java

.NET

Execution environment

Java Virtual Machine (JVM)

Common Language Runtime (CLR)

Executed instruction set

Byte code

Intermediate language (IL)

Runs what?

Managed code

Managed and unmanaged code

Multiple programming language support

Some noncommercial implementations, such as JPython and JACL, which compile to byte code

Visual Basic, C++, C#, JScript, COBOL, Perl, FORTRAN, and other languages

Runtime execution mode

Part interpreted mode and part JIT compiler

Always runs compiled native code

Unit of isolation of an application

Multiple applications can run on a single JVM. No logical partitioning between applications on the same JVM. They all share the same common OS process resources.

Multiple applications are separated by application domains inside a single OS process. Application domains provide logical partitioning of applications inside an OS process.

Operating system

Runs on a wide variety of operating systems.

Currently supported on Windows OS only, although there are noncommercial options for running .NET on other OSs (such as Mono on Linux).

3.2.2 The CLR

The CLR runs managed MSIL code. The unmanaged code, which is pointer-based, runs outside the CLR. The Windows OS does not support running a CLR application out of the box. A CLR application that ends with an .exe extension is not the kind of true stand-alone EXE application you have come to know as a C, C++, or Visual Basic programmer. Instead, the EXE runs because the CLR is first loaded by the shell executable CLR host, which then runs the user program in that loaded CLR.

A CLR host is an application that sits between the CLR application and the OS process. The host is responsible for loading the CLR into the OS process and for creating logical partitions in the OS process so that individual applications can reside in those partitions. The .NET Framework ships with the following CLR hosts.

3.2.3 ASP.NET

An ISAPI (Internet Server Application Programming Interface) filter that ships with ASP.NET loads the CLR and does the initialization necessary to handle the Web request. (In Javaspeak, ASP.NET is a servlet engine.)

3.2.4 Internet Explorer

Internet Explorer (IE) is the equivalent of a having a JVM inside the browser to run Java applets. Similarly, IE has CLR hosting hooks to run managed code that is directly referenced in the HTML code.

3.2.5 Shell Executables

When a managed application is launched from a shell, a small piece of unmanaged code loads the CLR and transitions control of the application to the CLR.

Every CLR host comes with a piece of unmanaged code that must be run before it can load the CLR. To interact with the operating system for the first time, the CLR host must run some unmanaged code (pointer-based code). After this code is run, the CLR is loaded and the host creates a default partition for running managed code. A user's application is not run in this default partition; instead, the CLR host creates partitions for running these end user programs. As mentioned in Chapter 1, these partitions are called application domains.

Traditional applications are separated by OS process boundaries, and this means that for one application to share resources with another application you must make an out-of-process application. The .NET application domain is essentially a way to partition applications within a single process so that they do not directly share any resources, thereby preventing one application from affecting the execution of another application. Application domains are flexible in the sense that they allow individual applications to be started and stopped without affecting the other applications running in the same CLR instance.

The application domain concept is somewhat equivalent to the J2EE Web application isolation. The J2EE specification mandates that J2EE-compliant application servers partition Web applications by assigning them different class loaders. As a result, two Web applications cannot directly share resources even though they are in the same JVM instance and share the same process space. This ability to have multiple applications running independently in the same process space is what makes the .NET and the Java platforms most suitable for developing scalable applications.


3.3 The OOP Model

Both C# and Java are object-oriented languages, with some striking differences and similarities in how they implement the object-oriented programming (OOP) model. Table 3.2 compares the Java and C# object-oriented programming models.

One of the most important differences between the OOP model used by Java and that of C# is that in Java all methods are, by default, virtual. This means that the method call is always on the object instantiated. Therefore,

A a = new B();

where class B inherits from A.

Calling a method on the reference a will call a method on the instantiated class B. This is not the case in C#. The method is called on the reference a unless it is explicitly marked as virtual by the superclass A and is explicitly overridden by the subclass B using the override keyword. C#'s model makes for less convenient but more efficient runtime behavior because calling virtual methods is always slower than making a straight method call on the reference. Because the ability to override superclass behavior is made explicit, a subclass cannot inadvertently change the intended behavior of the method.

Table 3.2. The Java and C# OOP Models

OOP Element

Java

C#

Inheritance

Single inheritance

Single inheritance

Object hierarchy

Single root. All objects derive from one class (java.lang.Object) except for base types such as int and float.

Single root. Everything derives from one class (System.Object).

Objects are created using the new operator.

Objects are created using the new operator.

Members are accessed using the "." operator.

Members are accessed using the "." operator.

Polymorphism

Methods are virtual by default. The only way to prevent subclasses from overwriting superclass implementations is by making the superclass method private or making it final.

Methods are nonvirtual by default. You must use the virtual and override keyword combination (on the superclass and subclass, respectively) to initiate polymorphism.

Equivalent access modifiers

public

Public

private

Private

protected

protected internal

package

Internal

Default access level

package

private

Access modifier rules

Subclasses can access only the public and protected members of the superclass.

Subclasses can access only the public and protected members of the superclass.

Subclasses cannot restrict the access of a method inherited from the superclass; a public superclass method cannot be made private in the subclass.

Subclasses can restrict the access modifier of a method on the superclass as long as the subclass is not overriding the method to activate virtual dispatching.

Static methods

Public and protected static methods can be inherited. Private static methods cannot be inherited.

Virtual dispatching is not activated for static methods.

You can access a static member through a nonstatic reference variable.

Public and protected static methods can be inherited. Private static methods cannot be inherited.

Virtual dispatching is not activated for static methods because the keywords virtual and override are not allowed in a static context.

You cannot access a static method using a nonstatic reference.

Abstract methods

All nonabstract subclasses of an abstract superclass must have an implementation for all the superclass abstract methods or must be marked as abstract.

All nonabstract subclasses of an abstract superclass must have an implementation for all the superclass abstract methods or must be marked as abstract.

Interfaces

Classes can implement multiple interfaces, and interfaces can inherit from each other.

An interface must be public if it is to be implemented by a class in a different package.

A class implementing an interface method must declare it public. A class can implement multiple interfaces with common method names. There is no explicit interface declaration, and therefore multiple interfaces share the same implementation for the common method name.

Classes can implement multiple interfaces, and interfaces can inherit from each other.

A class can implement any interface even if they are in different namespaces.

A class implementing an interface method can have the method declared as public or can explicitly declare it using explicit interface declaration. This means that you can implement two different interfaces that have the same method name.

Presence of the as operator, which reduces casting.

Constructors and destructors

Provides a default no-argument public constructor if the class does not already provide one.

Static blocks are used instead of static constructors.

Finalizers are like destructors but are not as deterministic as destructors. There is no guarantee as to when they will get called.

Provides a default no-argument public constructor if the class does not already provide one.

Static constructors are used.

C++-like destructors. Destructors can be made only for references types and not for value types, and destructors cannot be overridden. A destructor cannot be explicitly called, because it doesn't make much sense to do so when an object's lifetime is managed by the garbage collector. You are encouraged to use the IDisposable interface for releasing resources instead of releasing them in destructors.

Miscellaneous features

Inner and anonymous classes.

Inner but no anonymous classes. Support for anonymous methods.


3.4 Language Constructs, Syntax, and the API

This section compares the building blocks of C# and Java. We use the following categories:

  • Data types

  • Operators

  • Control flow statements

  • Exception support

  • Common objects (Object, String)

  • Common language elements

  • Collections library

  • Threading

  • Input/output

  • Namespaces and assemblies

  • What is missing from Java?

  • What is missing from C#?

3.4.1 Data Types

C# allows for both safe and unsafe code. You can use unsafe code to do the kind of pointer programming supported in C and C++. Safe code is what Java programmers are used to, and one of the reasons it is called safe is that type safety is guaranteed by the runtime for safe code. Table 3.3 compares some of the key points of similarity between Java and C# data types.

Built-in C# value types are of the same type as those in Java except for the unsigned short, int, and long, which are absent from Java. C# also provides a 12-byte decimal, also absent from Java.

One major difference between C# and Java is the way C# transparently interchanges primitives and their respective System classes depending on the context. This process of boxing (converting a primitive to its System namespace equivalent) and unboxing is transparent to the programmer. Therefore, unlike Java's strict treatment of primitives and their wrapper classes, boxing is highly convenient. In C++, both structs and classes (objects) can be allocated on the stack, in-line, or on the heap.

Table 3.3. Java and C# Data Types

Data Type

Java

C#

Reference types

Classes, interfaces, and arrays

Classes, interfaces, arrays, and delegates

Value types

Primitives only

Cannot use new operator to instantiate value types

Primitives, structs, and enum. All value types are allocated on the stack.

No support for unsigned short, int, or long

Can use the new operator to instantiate value types

Support for unsigned short, int, and long

Pointer types

No support

Limited form of C- and C++- style pointer programming

Transparent conversion of primitive data type to its wrapper

No support

Supported through boxing and unboxing. Each primitive is mapped to a class in the System namespace and therefore can be treated like a regular object without doing any additional wrapping.

In C#, structs are always created on the stack or in-line, and classes (objects) are always created on the heap. Structs indeed let you create more efficient code.

C# also preserves the C and C++ enum construct. Java programmers have learned to cope with the lack of enum support by using hand-crafted, type-safe enumeration classes, which are essentially wrapper classes for holding constant values.

In C#:

public enum Direction {North=1, East=2, West=4, South=8};

In Java:

public class Direction {
  public static Direction North = new Direction(1);
  public static Direction East = new Direction(2);
  public static Direction West = new Direction(4);
  public static Direction South = new Direction(8);
  private int type;
  private Direction(int I) { this.type = i; }
}

3.4.2 Operators

Table 3.4 compares Java and C# operators of different categories. The operators have the same default meanings in both languages.

In C#, you can overload operators to customize the meanings of your custom classes. Java does not allow end-user operator overloading, although the API itself overloads some operators for end-user convenience (such as + operators on strings). User-defined operator declarations cannot modify the syntax, precedence, or associativity of an operator, and user-defined operators cannot have the same signature as the predefined operators.

Table 3.4. Java and C# Operators

Operator Category

Java

C#

Arithmetic

+, -, *, /, %

+, -, *, /, %

Logical

&&, ||, true, false, !

&&, ||, true, false, !

Bitwise

&, |, ^, /, ~

&, |, ^, ~

String concatenation

+

+

Increment, decrement

++, --

++, --

Shift

>>, <<

>>, <<

Relational

==, <=, >=, <, >, !=

==, <=, >=, <, >, !=

Assignment

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

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

Member access

.

.

Casting

()

()

Indexing

[]

[]

Conditional

?:

?:

Object creation

new

new

Type information

instance of

is, as, sizeof, typeof

Overflow exception control

None

checked, unchecked

Pointer-based

None

*, &, ->, []

User-defined operators (if defined) take precedence over the default operators. C# ensures at compile time that both operators in logical pairs (!= and ==, > and <, >= and <=) are defined.

3.4.3 Control Flow Statements

Table 3.5 compares the Java and C# control flow structures. Note that all control flow structures have the same meaning in the two languages.

The foreach() statement is a new statement not supported in Java. It is used for iterating over collections of objects of type class, interface, or struct. It can be used as follows:

foreach (object o in collection) {
}

Table 3.5. Java and C# Control Flow Structures

Control Flow Statement

Java

C#

Conditional

if-elseif-else
  switch(condition)

if-elseif-else
  switch (condition)

Iterative

while (condition) {}
  do{ } while (condition)
  for(start;condition;step)
  N/A

while (condition) {}
  do{ }while (condition)
  for(start;condition;step)
  foreach()

Jump statements

break, continue

break; continue, goto

This statement iterates through the collection.

The C# switch statement also is a little different from Java's switch-case construct. The C# switch statement allows you to use strings in the case block of the switch statement. This comparison is case-sensitive. Therefore, the following can be done in C# but not in Java:

switch (name) {
case "Jack":break;
case "Jill":break;
};

A C# goto statement must point to a label or one of the options in a switch statement. This is similar to the continue label statement in Java. A goto statement can point anywhere within its scope, and that restricts it to the same method (or finally block if it is declared within one). It cannot jump into a loop statement that it is not within, and it cannot leave a try block before the enclosing finally blocks are executed. The continue statement in C# is equivalent to the continue statement in Java, except that the C# version can't point to a label.

3.4.4 Exception Support

Both Java and C# support the try-catch-finally construct for dealing with exceptional conditions. Programming with exceptions lends direction to the flow of execution of your program when things go wrong. The try-catch-finally construct in both languages follows the same rules. Table 3.6 lists the similarities and differences.

Table 3.6. Java and C# Exception Support

Exception Element

Java

C#

Single exception class from which all other exceptions derive

java.lang.Throwable

System.Exception

Exceptions and errors

Differentiates between exceptions (java.lang.Exception) and errors (java.lang.Error). Errors tend to indicate more fatal failures. Both inherit from java.lang.Throwable.

Does not differentiate between errors and exceptions.

Checked and unchecked exceptions

Supports checked and unchecked exceptions.

No support for checked and unchecked exceptions. All exceptions are unchecked.

throws clause

throws clause is an integral part of a method signature and clearly indicates which exceptions are getting thrown.

No throws clause. Only way to know which exceptions are being thrown is to read the code or trust the documentation!

Classes implementing interfaces with a throws clause in the method signature must adhere to only those exceptions.

Classes implementing interfaces can throw exceptions not explicitly declared in the methods on the interfaces.

Inheritance

Subclasses cannot throw new exceptions in over-ridden methods of the superclass.

Because of method hiding, you can have a subclass throw an exception not formally thrown by the superclass method.

3.4.5 Common Objects

The java.lang.Object and java.lang.String classes are the two ubiquitous classes of the Java API. Their C# equivalents are no less ubiquitous. Tables 3.7 and 3.8 compare java.lang.Object and java.lang.String to their C# equivalents.

Table 3.7. Java and C# Root Object Classes

Java (java.lang.Object)

C# (System.Object)

toString()

ToString()

equals()

Equals()

Object()

Object()

hashCode()

GetHashCode()

getClass()

GetType()

finalize()

Finalize()

clone()

MemberWiseClone()

Table 3.8. Java and C# String Classes

String Class Element

Java

C#

Immutable

Yes

Yes

Can be inherited from?

No (final class)

No (sealed class)

Supports interning

Yes

Yes

Comparing string values

equals() method

== operator

Comparing string object instances

== operator: s == t

== operator on cast object: (object) s == (object) t

String concatenation

Object creation if one of the concatenated strings is not a literal.

No object creation. Safe as using the StringBuilder class.

Interfaces implemented

Comparable, Serializable

IComparable, ICloneable, IConvertible, IEnumerable

The java.lang.Object class of Java and the System.Object class of C# are the root classes from which all other classes inherit. From Table 3.7, it is apparent that both C# and Java maintain a striking similarity in the method names, signatures, and functionality. Note that the C# object has an alias keyword object (with a lowercase o) that can be used instead of the fully qualified System.Object. Also, all value types in C# inherit from System.Object through boxing.

In both languages, string objects are immutable. Moreover, because they are ubiquitous, the runtime optimizes string literals by interning them: representing the same literal by the same object on the heap. The one major difference in C# is that the == operator for System.String is overloaded to compare the strings for their values and therefore is equivalent to Java's equals method. Also, to compare two string instances you cast them to their object equivalents and then use the == operator.

3.4.6 Common Language Elements

This section briefly compares some of the core language elements common to C# and Java.

Classes

Classes form one of the fundamental language elements in C# and Java. Here's a typical class signature:

attributes class-modifiers class class-name class-base {}

Optional parts of the signature are italicized. Notice that almost everything except for the class name and the keyword class is optional in both languages. Attributes are applicable only in C#. In Java, classes are closely tied to the actual file in which they are written (a .java file); according to the Sun JDK, a public class must be declared in a .java file whose name is the same as the class name. In C#, classes are not tied to the concept of a .cs file. A .cs file can contain any number of public classes.

Classes in both C# and Java can nest inner classes. Accessibility of a class determined by the access modifier on the class, and the set of applicable access modifiers that can be used on a class is determined by the context in which the class is declared. For example, in Java a .java file must have one public class defined. Absence of an access modifier gives default accessibility to the class. The default accessibility is internal for a nonnested C# class, and private for a nested C# class. The default class access level for both nested and nonnested Java classes is package level (similar to the default access level of fields and methods). In addition to access modifiers, you can use other modifiers to indicate what kind of a class is being created. Table 3.9 compares the Java and C# class modifiers.

You can nest classes in both Java and C#. Nested classes in Java can be regular inner classes or anonymous classes, and they can have the same class access modifiers as those of nonnested Java classes. In C#, nested classes can have the same access modifiers as those of nonnested types. In C#, a nested class has access to all the members that are accessible to its containing class, including members of the containing class that have declared private and protected accessibility. A nested class also can access protected members defined in a superclass of its containing class.

Table 3.9. Java and C# Class Modifiers

Java

C#

Meaning

public

public

A public Java class can be accessed from any package

A public C# class can be accessed from any namespace.

protected

protected

A protected Java class can be accessed only by classes within that package.

A protected C# class is declared as protected, and its type is accessible by a containing type and by any type that inherits from this containing type. This modifier should be used only for internal classes.

N/A

internal

If a class is declared as internal, the type it defines is accessible only to types within the same assembly. This is the default access level of nonnested classes.

N/A

protected internal

The permissions allowed by this access level are those allowed by the protected level plus those allowed by the internal level. The access level is thus more liberal than its parts taken individually. This modifier should be used only for internal classes (classes declared within other classes).

private

private

In Java, a private class has visibility only in the file where it is declared. A private class is always declared inside another class.

In C#, when a class is declared as private, access to the type it defines is limited to a containing type only. This modifier should be used only for internal classes.

static

N/A

A static class is used to declare a top-level class as opposed to an inner class.

N/A

new

The new keyword can be used for nested classes. A nested class is one that is defined in the body of another class; it is in most ways identical to a class defined in the normal way, but its access level cannot be more liberal than that of the class in which it is defined. A nested class should be declared using the new keyword just in case it has the same name as (and thus overrides) an inherited type.

final

sealed

A final Java class and a sealed C# class cannot have subclasses.

abstract

abstract

In both C# and Java, abstract classes have one or more abstract methods. An abstract class cannot be instantiated.

Constructors

Constructors are special methods that are used to create instances of a particular class. Constructors can be instance constructors or static constructors. Both C# and Java support instance constructors. Static constructors are C#-specific, and static blocks in Java are somewhat similar to static constructors.

If you do not provide an instance constructor for your class, both languages provide a default no-argument public instance constructor. However, if you do provide a constructor, the default constructor is not provided. You can have any access modifier on a constructor. When a class declares only private instance constructors, it is not possible for other classes to derive from that class or to create instances of that class.

A utility class consisting of only static methods typically uses a private constructor. Private constructors can also be used to create singleton classes (classes having only one instance object).

Static constructors are like Java's static blocks, and they are used to initialize classes. The acceptable modifiers on a static constructor are extern and static.

Destructors

C# introduces the concept of destructors. Unlike C++, C# destructors are not deterministic. You cannot call a C# destructor and expect the memory associated with the object to be reclaimed. Memory management is done exclusively by the garbage collector (as in Java), but a destructor is like Java's finalize method. Destructors are invoked automatically and cannot be invoked explicitly. When an instance is destroyed, the destructors in that instance's inheritance chain are called, in order, from most derived to least derived. You can implement a destructor by overriding the virtual method finalize on System.Object. In any event, C# programs are not permitted to override this method or to call it (or overrides of it) directly.

Class Members

Class members in C# can be fields, methods, constants, delegates, operators, indexers, and properties. In Java there are only fields and methods.

Let's compare the field and method members. Class fields and methods in Java and C# share similar declaration syntax. They all have associated modifiers that specify where (access level) and how (context) a member can be used. The member static modifier makes the member static and hence accessible only in static contexts. Similarly, a nonstatic member can be accessed only from a nonstatic context. The following paragraphs discuss variables (which are what class fields are) and methods.

Variables

Variables represent storage locations. Variables point to values that are of a specific data type. To indicate this relationship between a variable and its value, variables are declared as having the same type as the value they are pointing to. C# and Java are both type-safe languages. The runtime environment guarantees that a variable of type T always points to a value of the same type T.

C# and Java do not support the C and C++ concept of differentiating between defining a variable and declaring it. In C# and Java, you just declare the variable.

Variables have scope depending on where they are declared. Variables also must be initialized (again, depending on the scope in which they are declared). Variables have default values.

Table 3.10 compares the variable categories in Java and C#. Regardless of the Table 3.10 categories, for variables pointing to value types, you obtain the default value by calling the constructor on the value type. For variables pointing to reference types, the default value is the null reference.

Methods

A typical Java or C# method declaration looks like this:

[View full width]

attributes method-modifiers return-type method-name (formal parameter list)
  throws exceptions

Table 3.10. Variables in Java and C#

Variable Category

Java

C#

Static variables

A field declared in a static context (static block) or static class member.

A field declared in a static constructor or a static class member.

Automatically initialized to the default value of the variable's type.

Automatically initialized to the default value of the variable's type.

Modifiers that go with static variables: private, public, protected, volatile, final

Modifiers that go with static variables: private, public, protected, internal, readonly, volatile

Class instance variables

A field declared without the static modifier is an instance variable.

A field declared without the static modifier is an instance variable.

Automatically initialized to the default value of the variable's type.

Automatically initialized to the default value of the variable's type.

Modifiers that go with instance variables: private, public, protected, volatile, final

Modifiers that go with instance variables: private, public, protected, internal, volatile, const, readonly

Struct instance variables

N/A

An instance variable of a struct has exactly the same lifetime as the struct variable to which it belongs.

The initial assignment state of an instance variable of a struct is the same as that of the containing struct variable.

Value parameter variables

Method parameters are passed by value and are called value parameters.

Method parameters are passed by value and are called value parameters.

A value parameter variable ceases to exist after the method call.

A value parameter variable ceases to exist after the method call.

Variables pointing to primitive types do not retain changes made to them inside the method (passed by value).

Variables pointing to primitive types do not retain changes made to them inside the method (passed by value).

These are initialized with the value passed by the calling method.

These are initialized with the value passed by the calling method.

Reference parameter variables

N/A

Allows method parameters to be passed by reference as opposed to the default passed by value.

A parameter declared with a ref modifier is a reference parameter.

Value of the reference parameter is the same as that of the underlying variable.

Variables must be assigned before they are passed in as ref parameters.

Output parameter variables

N/A

A parameter declared with a ref modifier is a reference parameter.

Value of the reference parameter is the same as that of the underlying variable. Variables need not be assigned before they are passed in as ref parameters but must be assigned before the containing method returns.

Useful for returning multiple values from a single method call.

Local variables

Local variables have the narrowest scope and cease to exist as soon as the block in which they are declared ends.

Local variables have the narrowest scope and cease to exist as soon as the block in which they are declared ends.

Local variables are never initialized by default. You must initialize them before using them.

Local variables are never initialized by default. You must initialize them before using them

Valid modifiers: volatile, final

Valid modifiers: const

Table 3.11. Java and C# Method Modifiers

Java

C#

Meaning

N/A

new

Used when a superclass method is marked with the virtual modifier and the corresponding subclass method uses the new modifier. The subclass hides the superclass implementation.

N/A

override

Used when a superclass method is marked with the virtual modifier and the corresponding subclass method uses the override modifier. The subclass overrides the superclass implementation.

N/A

virtual

Used in combination with the new and override modifiers.

public

public

Makes the method public to other classes in other namespaces or packages.

N/A

internal

Makes the members accessible to files within the same assembly.

protected

protected

Makes the method accessible to objects of this class and its subclass(es).

N/A

protected internal

The permissions allowed by this access level are those allowed by the protected level plus those allowed by the internal level. The access level is thus more liberal than its parts taken individually.

static

static

Class-level methods.

final

sealed

Subclasses cannot override a sealed or final method.

abstract

abstract

Abstract methods do not provide any implementation.

native

extern

These modifiers are used to identify methods that use external libraries or code.

Note that attributes (C#), method-modifiers, and parameter list are optional parts of a method signature. The throws clause of a method signature is optional and applies only to Java.

Method modifiers are used in combination. Table 3.11 compares the Java and C# method modifiers.

Certain method modifiers cannot be used in combination with each other. The following method modifier combinations are not allowed in C#:

  • abstract and sealed

  • abstract and static

  • abstract and override

  • static and override

  • override and new

  • override and virtual

  • abstract and extern

The following method modifier combinations are not allowed in Java:

  • abstract and final

  • abstract and static

  • abstract and native

You can overload methods in Java and C# by changing the parameter list of the method signature. Changing the return type of the attributes (C#) on a method does not overload the method. Overloaded methods have the same method modifiers.

Interfaces

Like a class construct, an interface is a first-class type in Java and C#. Here is a typical interface signature:

attributes modifiers interface name interface-base body;

Optional parts are italicized. Attributes are applicable only to C#. Also, Java interfaces can contain throws clauses, which indicate the checked exceptions thrown by the method.

An interface can inherit from zero or more interfaces, which are called the explicit base interfaces of the interface. The explicit base interfaces of an interface must be at least as accessible as the interface itself (that is, a public interface cannot derive from a private interface). A class that implements an interface also implicitly implements all of the interface's base interfaces. Like classes, interfaces can nest other interfaces. Interface method signatures follow the same rules as class method signatures except that interface methods have no associated body. A class can declare explicit interface member implementations. An explicit interface member implementation is a method, property, event, or indexer declaration that references a fully qualified interface member name. Explicit interface implementation is present only in C#.

3.4.7 Collections Library

The C# language defines its collection classes and interfaces in the System.Collection namespace. The C# ICollection interface closely matches the java.util.Collection interface. The IList and the IDictionary interfaces match the java.util.List and java.util.Map interfaces, respectively. Following are the differences between the C# and Java collections APIs:

  • C# has no separate implementations for thread-safe and thread-unsafe collections. By default, all collections are thread-unsafe, something that follows the Java 2 collections paradigm. Therefore, C#'s Hashtable is equivalent to Java's HashMap and not to java.util.Hashtable.

  • C# has no equivalent of the java.util.Collections class. This means that methods such as Reverse, Sort, and BinarySearch are on the concrete collection implementation (i.e., ArrayList, Hashtable) instead of being pulled out in a class with only static methods.

  • C# has no equivalent of java.util.Set.

  • C# collections can be accessed using array-like indexing syntax.

  • C# supports case-insensitive lists and Hashtables.

  • C# collections can hold primitives (because of boxing and unboxing).

3.4.8 Threading

In Java, you can create threads by extending the java.lang.Thread class or by implementing the java.lang.Runnable interface. Threads must be created manually, and the programmer is responsible for managing the life cycle of the thread (aborting it properly). Java also supports Timer classes for executing periodic asynchronous activities. Multiple threads can simultaneously access and change a given object's state. For an object to be thread-safe, it should allow only one thread to change its state at any given time. In Java, you implement thread safety by using the synchronize keyword.

Only one thread can access a synchronized method of an object. You can synchronize static and nonstatic methods or even blocks of code, synchronizing the object whose state is to be made thread-safe. The wait(), notify(), and notifyAll() methods on the java.lang.Object signal the calling thread to go into a wait state and wait for further notification.

C# supports four ways to create threads:

  1. Java-like manual construction and running of a thread. Recommended for long-running, coarse-grained threads that require finer control of the thread's life cycle.

  2. Using the System.Threading.ThreadPool class. C# provides a systemwide thread pool class that handles the life cycle of a thread C# doles out to the end user. Recommended for short-running, fine-grained tasks.

  3. Using the System.Threading.Timer class. This is the recommended approach for executing periodic tasks.

  4. Using the asynchronous callback mechanism. Although this approach does not directly create a thread, it simulates asynchronous behavior and provides a generic mechanism for information sharing between the caller thread and the execution thread.

You can implement thread safety in C# by using the lock keyword, which is similar to the synchronize keyword of Java. C# also provides classes in the System.Threading namespace that can be used for explicitly acquiring and releasing locks. C# provides reader/writer locks, which are useful for the typical programming problem of multiple threads trying to read an object and only one thread trying to write to it. The C# Interlocked class provides operations that can be executed on the int and long data types. These operations cannot be interrupted; they are atomic and therefore preclude the need for specifying locks for trivial tasks.

3.4.9 Input/Output

Both C# and Java deal with input/output through Stream classes, whereas Java differentiates between InputStream and OutputStream. C# maintains only one Stream class, which has a property to indicate whether it is for output or input. The subclasses of the Stream class (MemoryStream, FileStream, NetworkStream, and BufferedStream) are differentiated based on the underlying medium from which the sequence of bytes is read.

The Stream classes are useful for lower-level byte reading from the underlying medium. However, it is often convenient to use the corresponding Reader/Writer classes because they simplify the method calls. As of JDK 1.3, there is no nonblocking I/O in Java. To simulate nonblocking I/O behavior, you must initiate a thread that does the actual I/O. JDK 1.4 introduces the java.nio package, which simulates nonblocking behavior. In C#, nonblocking I/O is not a first-class concept (unlike JDK 1.4); instead, C# provides a convenient API so that writing nonblocking I/O is relatively easy. Most of the C# I/O classes have constructors that take an asynchronous callback delegate, and therefore the need to create a thread to simulate I/O is eliminated.

C# provides a rich API for dealing with files and directories. Unlike Java, C# does differentiate between a file and a directory. Note that in C#, IOException is an uncaught exception, and hence it won't be caught at compile time. In Java, IOException is a checked exception and is thrown by most java.io class methods, thereby forcing the user to account for I/O failures. You can activate basic serialization in C# by tagging a class with the Serializable attribute or implementing the ISerializable interface. You can use custom serialization to achieve more control over the serialization and deserialization process.

3.4.10 Namespaces and Assemblies

In Java, a unit of compilation is a single source file, typically containing a single public class. In practice, there is usually a one-to-one correspondence between the source file and the single class it defines. Because an application can contain multiple classes and because class names could very well collide, it is customary to arrange classes in packages. Absence of a package declaration in a class assumes the default package.

Packages have special meaning in Java, in the sense that they closely map to the disk structure in which the source files and the compiled class files are stored. Packages prevent naming conflicts and provide a logical organizational system for arranging classes based on functionality (for example, all GUI-related classes hang together in a single package). Packages also affect the accessibility of a class. A package-level class or interface is accessible only to other class and interface peers in the same package. To reference classes and interfaces in other packages, you use the import keyword. This lets you refer to the class or interface without specifying its fully qualified name.

Namespaces play a similar role in C#. Namespaces allow logical organization of C# types. You can refer to another namespace through the using keyword, which is a close equivalent of the import keyword. But using is more flexible because it can be used to alias a long, fully qualified name to something more user-friendly:

using GUIToolKit = com.software.programmer.gui.toolkit;

Here, if a class SplashScreen happens to be in that namespace, it can be referred to as GUIToolKit.SplashScreen elsewhere in the code.

Namespaces can be nested. Namespaces have no access restrictions. It is not possible to declare private, protected, or internal namespaces, and namespace names are always publicly accessible. This is similar to Java packages. A unit of compilation in C# is a source file. A source file can consist of multiple namespaces and multiple public classes. Source files can have namespaces and classes cross-referencing each other. Unlike Java, the C# namespace is not indicative of any directory structure on the file system.

Assemblies are EXEs or DLLs generated from compiling a project of files. To greatly simplify deployment, the .NET runtime uses the configurable attributes and versioning rules built into assemblies. Assemblies also form a boundary to deal with type-name collisions, to the extent that multiple versions of an assembly can coexist in the same process. Each file can contain multiple classes and multiple namespaces. A namespace can also be spread across several assemblies. An assembly can refer to another assembly. The equivalent of an assembly in Java is the .jar file. Classes within the same packages can be deployed in different .jar files, although it is not intuitive to do so.

3.4.11 What's Missing from Java?

From the discussion so far it should be clear that C# and Java, though touted as similar, are quite different in their approach to OOP. However, certain elements in C# do not have any equivalents in Java. Note that the following discussion does not cover all that is missing from Java but only some key concepts.

Structs

C# structs are lightweight constructs that inherit from the C# class System.Object. Unlike regular objects, structs are allocated on the stack. Structs can implement interfaces and can have the same kinds of members as classes. However, structs differ from classes in several important ways: Structs are value types rather than reference types, and inheritance is not supported for structs. Although structs can improve performance in places where a full class definition is not warranted, structs are, after all, value types, and you should take care when passing a large number of structs as method parameters; the latter are copied every time they are passed by value to the method, thereby consuming more memory. Structs are discussed in detail in Chapter 7.

Constants

A constant is a class member that represents a constant value: a value that can be computed at compile time. Constants are permitted to depend on other constants within the same program as long as there are no circular dependencies. Even though constants are considered static members, a constant declaration neither requires nor allows the modifier static. Here is an example:

public const int A = 1;

A const can have the following modifiers: new, public, protected, private, and internal. A constant declaration can be of the following types: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, string, an enum, or a reference type. Constants are typically used in expressions that in turn can be made up of other constants.

Properties

A property is a member that provides access to a characteristic of an object or a class. Examples of properties include the length of a string, the size of a font, the caption of a window, the name of a customer, and so on. Properties are a natural extension of fields. Unlike fields, properties do not denote storage locations. Instead, properties have accessors that specify the statements to be executed when their values are read or written.

Access modifiers on a property apply to both the get and the set block of a property. Here is a sample property:

public string Name {
    get {
      return name;
  }
    set {
      this.name = value;
  }
}
Console.WriteLine (person.Name);

Properties with only the get block are read-only properties. Properties can be static or nonstatic. They can be referenced through the name, and because the get and the set blocks are transparently called, properties simplify syntax.

Indexers

An indexer is a member that enables an object to be indexed in the same way as an array. Indexer declarations are similar to property declarations. The main differences are that indexers are nameless and include indexing parameters. The indexing parameters are provided between square brackets. A sample indexer definition is as follows:

public class Stack {
  public object this [int index] {
    get {
    }
    set {
    }
  }
}

This code enables the call Stack[i].

Enums

An enum type is a distinct type that declares a set of named constants. Each enum type has a corresponding integral type called the underlying type of the enum type. This underlying type must be able to represent all the enumerator values defined in the enumeration. An enum declaration that does not explicitly declare an underlying type has an underlying type of int. Enums can have the following modifiers: new, public, protected, internal, and private.

Delegates

Event programming is typical in GUI applications, in which specific user interactions fire off events. Associated with every event is a generator (source) and a receiver (target). The general paradigm of event programming is that the receiver registers itself with the generator. Upon registration, the generator adds a listener to an internal list of listeners it maintains. The generator can also remove a listener from this list. When an event is fired, the generator enumerates through this list of listeners and notifies them of the event. In Java, the generator, listener, and the event itself are all classes. In C#, however, the event is modeled after delegates.

C# delegates can be thought of as type-safe function pointers. Function pointers are references to actual functions. A delegate can also wrap a call to a series of functions that can be added or deleted dynamically. Because of the indirect nature of the function call, delegates are useful for simulating asynchronous method calls (for example, the System.Threading and System.IO namespaces). Delegates are also used in event-based programming in C#. Thus, event programming in C# is similar to the JavaBeans property change listener paradigm. Because you can add or delete functions to a delegate, which in turn points to multiple functions, delegates can conveniently simulate the JavaBeans property change listener paradigm.

Attributes

Attributes form the declarative elements of the C# language. C# allows programmers to invent new kinds of declarative information, attach this declarative information to various program entities, and retrieve this declarative information at runtime. Programs specify this additional declarative information by defining and using attributes. Custom attributes can be created and attached to program elements (class, methods, assemblies, etc.). You can create a custom attribute and attach it to your class so that you can keep track of who made what contribution to the class source.

In addition to the features we've discussed, Java lacks the following:

  • The ref and out method parameters

  • Boxing and unboxing of primitive types

  • Operator overloading

  • Explicit interface declaration

  • Runtime code generation

  • Support for pointer-based code (built into the API)

  • The foreach statement and support for strings in the switch statement

  • Deterministic object cleanup

  • Pass by reference

  • Variable-length method parameter lists

  • Versioning

To get a feel for a more comprehensive list of differences between C# and Java, you're encouraged to look at the summaries of the individual chapters. These have not been duplicated here for the sake of brevity.

3.4.12 What's Missing from C#?

Although C# has improved a lot upon Java, there are certain language features Java programmers will notice are missing from C#:

  • Checked exceptions

  • Typed collections

  • Anonymous inner classes

  • Dynamic class loading

  • Interfaces that contain fields

C# has its own slew of features that, as a Java programmer, you wish it had. One of the most glaring cutbacks is the lack of support for checked exceptions. Accounting for checked exceptions is as routine as declaring variables in Java, and in fact, many Java APIs enforce predictable execution paths by throwing checked exceptions instead of returning null or silently ignoring the exceptional condition. There are quite a few places in the C# API where a method returns null instead of throwing an exception. This is something Java programmers will have to be extremely careful about.


3.5 Summary

C# was created to appeal to C, C++, and Visual Basic programmers as well as Java programmers. It is an expressive language, and it has several constructs borrowed from other languages and neatly packaged in one API. Its support for unsafe code, enums, delegates, operator overloading, and deterministic cleanup is an attempt to lure C and C++ programmers. At the same time, its use of indexers, properties, and boxing of primitive types makes it easy for Visual Basic programmers to learn the language. As a result, C# often has multiple constructs to achieve a single task. This usually means more constructs for the programmer to learn. Java, on the other hand, models everything as a class or an interface, and therefore the learning curve for Java is fairly short. Java has sacrificed most of C#'s syntactic sugar for the sake of consistency.

Java, however, makes up for the lack of expressiveness through its ability to be able to write portable, cross-platform code.


Chapter 4. Writing Objects

This chapter introduces C# objects and classes. We discuss object creation, class design, access modifiers of class members, the concepts of overloading and overriding, and general object-oriented concepts. Basic inheritance and polymorphism are discussed in Chapter 5.


4.1 Object Creation

The cornerstone of any object-oriented programming language is the concept of an object. An object is a data structure that has methods and fields. Fields maintain the state of the object, and methods query or modify that state. Objects provide a convenient container for storing data. The number and type of lids on that container dictate the access control to the data. For example, an object that has only public methods is like an open container. Objects can get heavy (consume more memory) depending on the data they store. Objects are also used for abstracting the relationships among data.

In OOP languages such as C++, Java, and C#, objects are created using the new operator. An object is created in memory (usually the heap), and a reference to that object is obtained as shown here:

object o = new object();

The left side has the reference o, and it is assigned to the newly created object on the right side. Note that the reference o is merely a pointer to the actual object on the heap. You can use the reference to modify and query the state or data of the actual object by calling the object's method or setting its field:

o.field = "c#"
o.sayHello();

When you null an object, you merely set its reference to null. The keyword null is used in C++, Java, and C# to null objects. Note that null is not an object. Instead, it is a standardized reference to which references such as o can be set in order to null objects. Therefore, the following statement merely makes the reference o point to the null reference:

o = null;

After the reference is set to null, calling a method or accessing a field on that reference will give the classic NullPointerException (Java). This is obvious because there is no underlying object to which this reference is now pointing.

Note that a reference is a façade used to manipulate the actual object. References can be assigned to each other. Therefore, the following statement will assign an additional reference o1 to the object created earlier:

object o1 = o;

Assigning null to the reference o will not affect the reference o1. This latter reference will still keep pointing toward the actual object. When there are no such pointers (references) to that object, the object in memory is considered garbage, and the garbage collector (if one is supported by the language) will free up the memory consumed by that object.

In C# and Java, automatic garbage collection takes care of deallocating memory that was allocated using the new operator. Because object destruction is no longer an issue, programmers can now focus on proper class design and efficient object creation, thereby simplifying OOP. Still, you do need some knowledge of memory management and garbage collection to avoid mistakes that would hamper the garbage collection process. Section 4.8 discusses these issues in more detail.

4.1.1 The C# Equivalent of java.lang.Object

Both Java and C# have single-root object hierarchies. All objects always extend a standard root class. In Java this is the java.lang.Object class, and in C# it is the System.Object class. Table 4.1 compares System.Object and java.lang.Object and their approximate method equivalents.

If you are confused as to why the C# System.Object is referred to as object, it's because object is an alias for System.Object. To illustrate the various methods of System.Object, let's create a sample object as shown in Listing 4.1.

Table 4.1. The C# and Java Object Classes

System.Object (C#)

java.lang.Object (Java)

public object()

public Object()

public Equals(object o)

public equals(Object o)

public static Equals(object o1, object o2)

GetHashCode()

hashCode()

GetType

getClass()

ReferenceEquals (object a, object b)

ToString()

toString()

Finalize()

finalize()

MemberWiseClone()

Listing 4.1 Sample Object (C#)
  using System;
  public class Person {
    string name;
    public Person (string name) {
      this.name = name;
    }
    public override string ToString() {
      return this.name;
    }
    public override int GetHashCode() {
      return this.name.GetHashCode();
    }
    public override bool Equals(object o) {
      if (o != null && o.GetType().Equals(GetType())) {
        return this.name.Equals(((Person)o).name);
      }
      return false;
  }
    public Person copy() {
      return (Person)this.MemberwiseClone();
    }
}

Listing 4.2 runs some tests on the Person object.

Listing 4.2 Testing Person Objects (C#)
using System;
public class Test {
  public static void Main(string[] args) {

    Person p1 = new Person("chris");
    Person p2 = new Person("chris");
    Person p3 = p1;

    //Comparing p2 and p1;
    Console.WriteLine("Comparing p1 and p2");
    Console.WriteLine (p1.Equals(p2));
    Console.WriteLine (p1 == p2);
    Console.WriteLine (Person.ReferenceEquals(p1, p2));

    //Comparing p3 and p1;
    Console.WriteLine("Comparing p1 and p3");
    Console.WriteLine (p1.Equals(p3));
    Console.WriteLine (p1 == p3);
    Console.WriteLine (Person.ReferenceEquals(p1, p3));

    //Comparing p4 and p1;
    Console.WriteLine("Comparing p1 and p4");
    Person p4 = p1.copy();
    Console.WriteLine (p1.Equals(p4));
    Console.WriteLine (p1 == p4);
    Console.WriteLine (Person. ReferenceEquals (p1, p4));

    //Miscellaneous object methods
    Console.WriteLine(p1.GetType());
    Console.WriteLine(p1.ToString());
    Console.WriteLine(p1.GetHashCode());
  }
}

The output of Listing 4.2 is as follows:

Comparing p1 and p2
True
False
False
Comparing p1 and p3
True
True
True
Comparing p1 and p4
True
False
False
Miscellaneous object methods
Person
chris
177073830

The output will be obvious to most Java programmers because it is in tune with Java's notion of equality of objects. The following are the common ways to check for equality of objects:

  • The Equals method (public virtual bool Equals (object)). This code determines whether two Object instances are equal. This method is equivalent to the equals method of the java.lang.Object class. C# also provides a static version of the Equals method that takes two object instances as its argument.

  • The == operator. This determines whether the specified Object instances are the same instance. The == operator is similar to the same operator in Java, which compares the two references. So in the example, p3 and p1 are equal, but p1 and p2 are not equal because they point to two different object instances.

  • The ReferenceEquals method. This determines whether the specified Object instances are the same instance. The behavior is similar to that of the == operator. However, remember that operator overloading can sometimes change the meaning of the == operator, and hence it is safer to use the ReferenceEquals method to check for equality of references.

Note that in the Person class we created a copy() method that wraps the protected MemberwiseClone method. MemberwiseClone() is a protected method of the Object class that creates a shallow copy of the object. The method creates a new object in memory, and hence the == operator and the ReferenceEquals method return false for equality. The other methods of the System.Object class are similar in nature to those of the java.lang.Object class. The next section explores writing C# classes.


4.2 Classes

Although classes constitute the fundamental concept in Java, in C# they are one of the various types supported by the runtime. An instance of the System.Type class can be a class, an interface, a value type, an array, a pointer, or an enumeration. Chapter 7 discusses System.Type.

A class can be thought of as the metadata, or the definition, of an object. A class has members, and these can be its constructors, methods, fields, properties, and operators. Members help maintain and query the state of the objects created by a class. Members that maintain the state of any one object are called instance members, and members that maintain the state of all objects created on that class are called static members. Before we explore the instance and static members of a class, we will quickly describe some class access modifiers.

Because this chapter is about objects, inheritance automatically comes into play. Later sections mention the significance of access modifiers in the context of inheritance. However, Chapter 5 gives a more comprehensive treatment of inheritance.

4.2.1 Class Modifiers

Class modifiers are keywords that can go in front of the class definition. Modifiers can denote access or behavior. Class access modifiers are internal, public, private, and protected. Non-access-based modifiers are sealed, abstract, and new. We will first explore class access modifiers.

internal

When a class has no access modifier specified, the default access level is internal. An internal class is accessible only within files in the same assembly.

public

A public class has no restrictions on its accessibility.

Top-level types, which are not nested into other types, can have only internal or public accessibility. The default accessibility for these types is internal.

Nested types, which are members of other types, can have declared accessibilities as indicated in Table 4.2.

Table 4.2. Nested Types

Members of

Default Member Accessibility

Allowed Declared Accessibility of the Member

enum

public

None

class

private

public protected internal

private protected internal

interface

public

None

struct

private

public internal private

Listings 4.3 and 4.4 illustrate the access modifier keywords. Listing 4.3 defines three classes in a People namespace. The three classes (Developer, Actress, and Scientist) have default access, public access, and internal access, respectively. A Test class in Listing 4.4 is used to access these classes.

Listing 4.3 Sample Classes with Different Access Modifiers (C#)
using System;
namespace People {
  public class Developer {
    public void Fears (Object o) {
      Console.WriteLine("A developer fears "+o);
    }
  }
  class Actress {
    public void Fears (Object o) {
      Console.WriteLine("An actress fears "+o);
    }
  }
  internal class Scientist {
    public void Fears (Object o) {
      Console.WriteLine("A scientist fears "+o);
    }
  }
}
Listing 4.4 A Driver Class That Accesses the Classes Defined in Listing 4.3 (C#)
using People;
public class Test {
  public static void Main(string[] args) {
    Developer d = new Developer();
    d.Fears(new Actress());
    Actress a = new Actress();
    a.Fears(new Scientist());
  }
}

When you compile Listings 4.3 and 4.4 and run them in a single assembly, it is clear that classes with the public, internal, and default access modifiers can be accessed by any class in that assembly (a unit of deployment in C#). Note that even if the class access modifiers are restrictive, the methods of a class can have more liberal access modifiers. Hence, the classes Scientist and Actress can have public methods.

To quickly prove that classes Actress and Scientist in Listing 4.3 are inaccessible to the Test class in Listing 4.4 if it is in a different assembly, let's compile Listing 4.3 using csc /t:library Listing 4.3.cs. This creates a Listing 4.3.dll file. Now try compiling Listing 4.4 using the /r:Listing 4.3.dll option. The compiler gives the following output:

[View full width]

Listing4.4.cs(4,21): error CS0122: 'People.Actress' is inaccessible due to its protection
  level
Listing4.4.cs(5,9): error CS0122: 'People.Actress' is inaccessible due to its protection
  level
Listing4.4.cs(6,21): error CS0122: 'People.Scientist' is inaccessible due to its
  protection level

Access modifiers in Java follow somewhat similar rules.

  • In Java, a public class can be accessed from any package by any class. A class without access modifiers can be accessed only by classes in the same package. The access modifiers private and protected are allowed only on inner classes. Multiple packages and classes can be deployed in a single .jar file. A Java class with package-level access cannot be accessed by classes outside the .jar, nor by classes in the same .jar that are not in the same package.

  • In C#, the default and internal access levels disallow classes outside the assembly to access a class. However, classes inside the assembly can access these classes.

private and protected

The access modifiers private and protected can be put on classes that are part of an outer enveloping class. Such a nested class behaves like any other member (field, method, property) of a class. Nested classes can therefore be private or protected and are accessible only to objects of that class and not to objects of other classes or subclasses. Listing 4.5 shows a nested class with private and protected access. Only the Developer class can access the ApplicationDeveloper and WebDeveloper classes.

Listing 4.5 Access to Private and Protected Inner Classes (C#)
using System;
namespace People {
  public class Developer {
    public void Fears (Object o) {
      Console.WriteLine("A developer fears "+o);
      ApplicationDeveloper ad = new ApplicationDeveloper();
      ad.Fears(o);
      WebDeveloper wd = new WebDeveloper();
      wd.Fears(o);
    }
    private class ApplicationDeveloper {
      public void Fears (Object o) {
        Console.WriteLine("An application developer fears"
                                     +o+" and software bugs ");
      }
    }
    protected class WebDeveloper {
      public void Fears (Object o) {
        Console.WriteLine("A web developer fears "
                                       +o+" and slow modems ");
      }
    }
  }
  class Actress {
    public void Fears (Object o) {
      Console.WriteLine("An actress fears "
                                        +o+" a makeup famine");
    }
  }
  internal class Scientist {
    public void Fears (Object o) {
      Console.WriteLine("A scientist fears "
                                   +o+" and loss of gravity ");
    }
  }
}

Non-access-based modifiers of a C# class can be abstract, sealed, or new.

abstract

Abstract classes have the following features:

  • An abstract class cannot be instantiated.

  • An abstract class can contain abstract methods and accessors.

  • You cannot modify an abstract class using the sealed modifier, and this means that the class cannot be inherited.

  • A nonabstract class derived from an abstract class must include actual implementations of all inherited abstract methods and accessors.

  • An abstract C# class is equivalent to an abstract Java class.

Abstract classes are ideal when you want to put most of the functionality in a base class, leaving a few methods for subclasses to implement so that they can plug in their specific behavior. For example, a class that models persisting an object's state to the database can be declared as abstract. The general logic of saving can be captured in the base class, and the subclasses supply the names of the database tables where the data is to be stored. This practice helps to keep all the complex code in one base class, and subclasses end up being fairly simple.

abstract class PersistentObject {
  public void Save() {
    string tableName = GetTableName();

    //Do your saving here..
  }
  abstract public string GetTableName();
}

class Person : PersistentObject {
  public string GetTableName() { return "PERSONS_TABLE"; }
}
sealed

A sealed class cannot be inherited. It is an error to use a sealed class as a base class. Use the sealed modifier in a class declaration to prevent accidental inheritance of the class. Using the abstract modifier with a sealed class is not permitted. A sealed C# class is similar to a final Java class.

new

Used only with nested classes, new indicates that the class hides an inherited member of the same name.

4.2.2 Class Access Modifiers and Inheritance

Inheritance places some restrictions on the type of access modifiers that can be put on a class. In Java, a class that has default package-level access can have a subclass that is public, as shown here:

//Test.java
package com.acme;
class Test {}
//Test2.java
package com.acme;
public class Test2 extends Test {}

These two classes compile without any errors. In C#, a subclass of a public class either can be public or can have default access (no access modifier specified). A subclass of a class with default access level cannot be public.

Access modifiers on classes dictate the manner in which classes interact and are deployed or exposed to the end user. In Java, classes that are to be part of the public API are declared as public, as is the case with most of the Java API classes and any third-party library distributed as a .jar file. Classes that are used by these public classes but are not necessarily relevant to the end user have package-level access. Furthermore, some classes have private nested classes for situations when the nested private class makes sense only in the scope of the outer class.

If you anticipate that the classes you write are generic enough to be shipped as a library, you should consider carefully which classes you want to expose to the users of the library. If a given class has the default class access, classes in other assemblies cannot access it. This default access should be good enough for most applications.

Next, we discuss the keywords associated with class members such as constructors, methods, fields, and properties. We briefly look at how inheritance affects these members.


4.3 Constructors

Constructors are a special breed of methods that are used for creating object instances. In C# and Java, objects can have multiple constructors. In the absence of a constructor, the JVM and CLR runtimes provide a default public no-argument constructor. Constructors can call each other.

In Listing 4.6, the class has three constructors. The least specific constructor in turn calls the next more specific constructor and so on, thereby leaving the actual implementation in one constructor (the most specific one).

Listing 4.6 Multiple Constructors (C#)
using System;
public class Cube {
  int width, height, length;

  public Cube() : this(60,60,60) {}
  public Cube(int width, int length) : this(width,length,60) {}
  public Cube(int width, int length, int height) {
    this.width = width;
    this.length = length;
    this.height = height;
  }
  public override string ToString() {
    return "Volume is "+(this.width*this.height*this.length);
  }
  public static void Main(string[] args) {
    Console.WriteLine(new Cube());
    Console.WriteLine(new Cube(10,20));
    Console.WriteLine(new Cube(10,20,40));
  }
}

A subclass of Cube can refer to its superclass constructors using the base keyword. This is equivalent to Java's super keyword. Therefore, the following class will compile:

using System;
public class DerivedCube : Cube{
  public DerivedCube (int width, int length) : base (width, length){}
}

Now let's remove the default constructor Cube() from Listing 4.6 and remove the DerivedCube(int width, int length) constructor from the preceding code and try compiling. The compiler complains. In the absence of a constructor specified for DerivedCube, the runtime tries to allocate the default public no-args constructor and notices that the superclass does not have that default constructor specified. However, when the Derived Cube(int width, int length) constructor is put back in place with the superclass not having the Cube() constructor, the compiler does not complain. This behavior is similar to that of Java.

C# provides a default no-args public constructor to a class if there is no constructor specified. However, if a constructor is specified for a class, then the runtime does not provide a default constructor. The default constructor provided by the runtime automatically calls the base(), in which case it becomes mandatory for the superclass to have a default no-args public constructor. Constructors can be private. This modifier is typically used in objects implementing the singleton pattern. Making the constructor private allows only one copy of the object (a singleton) to exist:

using System;
public class CubeFactory {
  private static singleton = new CubeFactory();
  private CubeFactory(){}
  public CubeFactory GetSingleton() { return singleton; }
}

Also, subclasses cannot refer to this private base constructor using the base() call.

Constructors can be protected, in which case subclasses can access them using the base keyword as long as the subclasses are in the same assembly as the superclass. Constructors can also be static. Static C# constructors are equivalent to the Java static initializer blocks. Static constructors cannot have any parameters. They are specified as follows:

using System;
public class RubickCube : Cube {
  static RubickCube() {
    Console.WriteLine("Static block");
  }
}

Note that static blocks do not follow the typical inheritance behavior. The static constructor of the subclass does not automatically call the static constructor of the superclass. This behavior is similar to that of Java's static initializers.


4.4 Methods

You use methods to query and modify the state of the objects. In OOP, method calls are the preferred way to interact with objects. Most class members are methods, so we've adapted the following recommended guidelines for method usage from the Visual Studio help files:

  • Method names should be verbs or verb phrases and in Pascal case (unlike the Camel casing of Java).

  • Do not use Hungarian notation.

  • By default, methods are nonvirtual.

The method signature indicates the return type, any access modifiers or qualifiers preceding the method name, method parameters, and any exceptions that the method throws.

The following access modifiers are typically used in front of C# methods.

4.4.1 public

A public method of an object can be called by any class.

4.4.2 protected

A protected member is accessible from within the class in which it is declared, and from within any class derived from the class that declared this member. A protected member of a base class is accessible in a derived class only if the access takes place through the derived class type.

4.4.3 private

Private members are accessible only within the body of the class or the struct in which they are declared. Nested types in the same body can also access those private members.

4.4.4 internal

Internal methods can be called by any class that exists in the same assembly as the class in which the method is defined.

4.4.5 static

A static method is a class-level method and does not require an object instance to access the method.

4.4.6 sealed

A sealed method lets a class override a virtual function and prevents a derived class from overriding that same function. In other words, having sealed on a virtual method stops virtual dispatching. For example:

using System;
class SuperClass {
  public virtual string DoIt () { return "DoIt"; }
}
class SubClass : SuperClass {
  public override sealed string DoIt () { return "DoIt "; }
  static void Main(string[] args) {
    SubClass sc = new SubClass();
    Console.WriteLine(sc);
  }
}

Here, the subclass defines the DoIt() method to be sealed. This allows it to override the virtual method of the superclass, but a subclass of SubClass cannot override that method.

4.4.7 extern

Use the extern modifier in a method declaration to indicate that the method is implemented externally. This is equivalent to the native keyword in Java. Because an external method declaration provides no actual implementation, there is no method body; the method declaration simply ends with a semicolon, and there are no braces ({ }) following the signature. For example:

public static extern int MyMethod(int x);

4.4.8 unsafe

The unsafe keyword denotes an unsafe context, which is required for any operation involving pointers. unsafe is applied as a modifier in the declaration of callable members such as methods, properties, constructors, and extenders (but not static constructors):

static unsafe void FastCopy (byte[] src, byte[] dst, int count)
{
  // unsafe context: can use pointers here
}

The scope of the unsafe context extends from the parameter list to the end of the function, so pointers can also be used in the parameter list:

static unsafe void FastCopy ( byte* ps, byte* pd, int count )
{ . . . }

The unsafe keyword has no equivalent in Java.

4.4.9 virtual

In Java, all methods of a given class can be overridden by a subclass by default, unless you use the final keyword on the method. However, in C#, this is not the default behavior. Class methods that can be possibly overridden by a subclass must be marked as virtual. Subclasses can change the implementation of these virtual methods by using the override keyword, explicitly indicating that the subclass is overriding the superclass's behavior.

When a virtual method is invoked, the runtime type of the object is checked for an overriding member. The overriding member in the most derived class is called; this member might be the original member if no derived class has overridden the member. By default, methods are nonvirtual. You cannot override a nonvirtual method. In Java, however, all methods are virtual unless otherwise indicated. You cannot use the virtual modifier with the following modifiers: static, abstract, override, and private.

4.4.10 override

An override method provides a new implementation of a member inherited from a base class. The method overridden by an override declaration is known as the overridden base method. The overridden base method must have the same signature as the override method. You cannot override a nonvirtual or static method. The overridden base method must be virtual, abstract, or override. An override declaration cannot change the accessibility of the virtual method. Both the override method and the virtual method must have the same access-level modifier. You cannot use the following modifiers to modify an override method: new, static, virtual, and abstract.

4.4.11 abstract

Use the abstract modifier in a method declaration to indicate that the method or property does not contain implementation. Abstract methods have the following features:

  • An abstract method is implicitly a virtual method.

  • Abstract method declarations are permitted only in abstract classes.

  • Because an abstract method declaration provides no actual implementation, there is no method body; the method declaration simply ends with a semicolon, and there are no braces ({ }) following the signature. For example:

    public abstract void MyMethod();

  • The implementation is provided by an overriding method, which is a member of a nonabstract class.

    It is an error to use the static, virtual, private, or override modifier in an abstract method declaration.

4.4.12 new

Use the new modifier to explicitly hide a member inherited from a base class. To hide an inherited member, declare it in the derived class using the same name, and modify it with the new modifier.

In combination with the access modifiers (internal, private, public, and protected), the keywords virtual, override, sealed, new, and abstract provide several degrees of accessibility and extensibility for a class. Listing 4.7 shows the modified Person class with methods that have several access modifiers and the keywords defined here.

Listing 4.7 Modified Person Class (C#)
using System;

abstract public class Person {
  string name;
  string ssn;
  int age;
  float salary;

  public Person (string name, string ssn, int age, float sal) {
    this.name = name;
    this.ssn = ssn;
    this.age = age;
    this.salary = sal;
  }
  private void GetSSN() {
    this.ssn = ssn;
  }
  protected int GetAge() {
    return age;
  }
  internal float GetSalary() {
    return salary;
  }
  string GetName() { return name; }

  protected static String GetBloodColor() {
    return "Red";
  }
  static int GetNumberOfEyes() {
    return 2;
  }
  public override String ToString() { return name; }
  public override int GetHashCode() { return 12222; }
  public override sealed bool Equals(Object o) {
    Person p = o as Person;
    if (p == null) return false;
    return this.name.Equals(p.GetName());
  }
  public virtual void CalcInvestments() {
    Console.WriteLine("Person funds");
  }
  public virtual void CalcDebt() {
    Console.WriteLine("Person debt");
  }
  public abstract void Work();
}

Note the following details about our Person class:

  • The class is declared abstract because it contains the abstract Work method.

  • The class overrides the ToString() and GetHashCode() methods of System.Object. These are virtual methods of the base class.

  • The class overrides and seals the implementation of the Equals method. This means that subclasses cannot provide an implementation of the Equals method.

  • Methods GetAge(), GetSSN(), GetSalary(), and GetName() are instance methods and have protected, private, internal, and default access, respectively.

  • GetBloodColor() and GetNumberOfEyes() are static methods and have protected and default access levels.

  • CalcInvestments() and CalcDebt() are virtual methods. These can be either overridden or hidden by the subclasses.

Listing 4.8 is a subclass Man of the Person class. Note that we have commented out code that won't compile and have specified the reason.

Listing 4.8 Subclass of the Person Class (C#)
using System;

public class Man : Person {
  public Man (string name, string ssn, int age, float sal): base
                     (name, ssn, age, sal) {}
  public override void Work() {
    Console.WriteLine("Works like a dog");
  }
  public override void CalcInvestments() {
    Console.WriteLine("Man funds");
  }
  public new void CalcDebt() {
    Console.WriteLine("Man debt");
  }

  //Entry point
  public static void Main(string[] args) {
    Man m = new Man("Jack", "12354544", 23, 40000f);

    /******
    *  Won't compile, cannot call static method using the
    *  instance reference. Can do this in Java though!
    *****/
    //Console.WriteLine(m.GetBloodColor());

    /******
      Won't compile, improper access level.
    *****/
    //Console.WriteLine(m.GetNumberOfEyes());
    //Console.WriteLine(m.GetSSN());
    //Console.WriteLine(m.GetName());
    Console.WriteLine(m.GetSalary());
    Console.WriteLine(m.GetAge());

    /**
      *  All print subclass's implementation of
    CalcInvestments()*/
    m.CalcInvestments();

    Person p = m as Person;
    p.CalcInvestments();

    ((Person)m).CalcInvestments();

    m.CalcDebt();
    p.CalcDebt();
    ((Person)m).CalcDebt();
  }
}

Note the following details about the subclass.

  • GetBloodColor() is a static method. Unlike Java, C# does not let you invoke a static method using an object instance.

  • Methods with the default access level behave the same as private methods.

  • The Man class overrides the base class's virtual CalcInvestments() method. It does so by providing the override keyword. Note that despite all our efforts to cast it to the Person object, the invoked method is always on the subclass. The runtime always dispatches a virtual method to the subclass overriding it.

  • The Man class "hides" the base class's virtual CalcDebt() method. It does this by providing the new keyword. Note that it is possible to invoke the base class's implementation by casting out the reference.

  • The Man class must provide an implementation of the abstract method Work().

  • A class can have a method without it having to hide any superclass implementation. If a class uses the override keyword, the compiler will complain if it cannot find a virtual method to override.

4.4.13 Method Inheritance

Virtual methods in the base class can be either overridden or hidden by the subclass. Also, a subclass must implement any abstract methods present in the base class if the subclass does not want to be abstract itself. In Java, all methods of a class are virtual by default; furthermore, subclasses do not have to explicitly indicate that they are overriding a method. A method with the same signature as that in the base class will be overridden in the subclass. In fact, you can simulate Java-like behavior in C# through method hiding and using the new keyword. A subclass can invoke its superclass's implementation using the base keyword:

class Sub : Super
{
  public void DoIt() {
    base.Doit();
  }
}

Method signatures often include any exceptions that are thrown. Chapter 10 discusses details about inheritance and method exceptions.

4.4.14 Method Overloading

As in Java, you cannot differentiate a method solely by its return value. For a method to be overloaded it should have parameters that differ from the parameters of any like-named method. The following recommended guidelines for method overloading are from the Visual Studio help files:

  • Use method overloading to provide different methods that do semantically the same thing.

  • Use method overloading instead of allowing default arguments. Default arguments do not version well and therefore are not allowed in the Common Language Specification (CLS). The following code example illustrates an overloaded String.IndexOf method.

    int String.IndexOf (String name);
int String.IndexOf (String name, int startIndex);

  • Use default values correctly. In a family of overloaded methods, the complex method should use parameter names that indicate a change from the default state assumed in the simple method. For example, in the following code, the first method assumes the search will not be case-sensitive. The second method uses the name ignoreCase rather than caseSensitive to indicate how the default behavior is being changed.

    // Method #1: ignoreCase = false.
  MethodInfo Type.GetMethod(String name);
// Method #2: Indicates how the default behavior of method
#1 is being changed.
  MethodInfo Type.GetMethod (String name, Boolean ignoreCase);

  • Use a consistent ordering and naming pattern for method parameters. It is common to provide a set of overloaded methods with an increasing number of parameters to allow the developer to specify a desired level of information. The more parameters that you specify, the more detail the developer can specify. In the following code example, the overloaded Execute method has a consistent parameter order and naming pattern variation. Each of the Execute method variations uses the same semantics for the shared set of parameters.

    public class SampleClass {
  readonly string defaultForA = "default value for a";
  readonly string defaultForB = "default value for b";
  readonly string defaultForC = "default value for c";

  public void Execute() {
    Execute(defaultForA, defaultForB, defaultForC);
  }

  public void Execute (string a) {
    Execute(a, defaultForB, defaultForC);
  }

  public void Execute (string a, string b) {
    Execute (a, b, defaultForC);
  }

  public virtual void Execute (string a, string b, string c) {
    Console.WriteLine(a);
    Console.WriteLine(b);
    Console.WriteLine(c);
    Console.WriteLine();
  }
}

    This consistent pattern applies if the parameters have different types. Note that the only method in the group that should be virtual is the one that has the most parameters.

  • Use method overloading for variable numbers of parameters. Where it is appropriate to specify a variable number of parameters to a method, use the convention of declaring n methods with increasing numbers of parameters. Provide a method that takes an array of values for numbers greater than n. For example, n=3 or n=4 is appropriate in most cases. The following example illustrates this pattern.

      public class SampleClass {
    public void Execute(string a) {
    Execute(new string[] {a});
  }

  public void Execute(string a, string b) {
    Execute(new string[] {a, b});
  }

  public void Execute(string a, string b, string c) {
    Execute(new string[] {a, b, c});
  }

  public virtual void Execute(string[] args) {
    foreach (string s in args) {
      Console.WriteLine(s);
    }
  }
}

  • If you must provide the ability to override a method, make only the most complete overload virtual and define the other operations in terms of it. The following example illustrates this pattern.

    public class SampleClass {
  private string myString;

  public MyClass(string str) {
    this.myString = str;
  }

  public int IndexOf(string s) {
    return IndexOf (s, 0);
  }

  public int IndexOf(string s, int startIndex) {
    return IndexOf(s, startIndex, myString.Length —start
    Index);
  }

  public virtual int IndexOf(string s, int startIndex,
int count) {
    return myString.IndexOf(s, startIndex, count);
  }
}

When a method is overloaded, to call the right method the compiler compares the parameters the method takes. Methods cannot be overloaded based on their return type. If there is an implicit conversion from the argument type to the parameter type, it will prefer calling that method. Two methods cannot be overloaded if the only difference between them is that the ref parameter of one method is an out on the other. Methods can be overloaded if one of the methods passes parameters by value and other passes parameters by reference.

4.4.15 Method Parameters

In Java, there is not much variety in terms of method parameters. Method parameters are passed by value in Java. Changes made to primitive parameters are lost after the method exits. Object reference parameters are also passed by value. This means that a copy of the reference is made and passed to the method, but the object that the copied reference points to remains the same. The following Java program brings home this point:

import java.util.ArrayList;
import java.util.List;
  public class MethodParameterTest {
    public static void main(String[] args) {

      //Testing primitives
      int a = 3;
      testPrimitive(a);
      System.out.println(a);

      //Testing testObjectList1
      List list1 = new ArrayList();
      System.out.println(list1.size());
      testObjectList1(list1);
      System.out.println(list1.size());

      //Testing testObjectList2
      List list2 = new ArrayList();
        list2.add("Major");
      testObjectList2(list2);
      System.out.println( list2.size());

      //Testing Strings
      String s = "Jack";
      testString(s);
      System.out.println(s);
  }
  private static void testPrimitive(int a) {
    a *=2;
  }
  private static void testObjectList1(List l ) {
    l.add("Test");
  }
  private static void testObjectList2(List l ) {
     l = null;
  }
  private static void testString(String s) {
    s = "John";
  }
}

The preceding program prints the following:

3
0
1
1
1
Jack.

The primitive value does not get changed when the testPrimitive() method exits. The primitive a is 3 before the method call, 6 inside the method, and again 3 outside the method. The variable list1, which is a reference to the actual ArrayList object, is passed to the method. A copy of the reference is made on the method call, but this copy points to the same ArrayList() object created just before the method. Using this copied reference, list1 adds an object to the ArrayList() object. The method exits, and the size of the ArrayList() object has now changed. Note that although the reference is copied (by value), the actual object pointed to remains the same; hence, any changes made to that object will be reflected outside the method.

The variable list2, which is a reference to the actual ArrayList(), is passed to the method. A copy of the reference is made on the method call, but this copy points to the same ArrayList() object created just before the method. The reference is then set to null. As mentioned in the beginning of this chapter, null is a reference and not an object. Thus, the copied reference is set to null, the original reference to the ArrayList created outside the method remains intact, and the size of the ArrayList() remains the same both before and after the method executes.

The last example is tricky. Strings are passed by value to the method. A copy of the string reference is made, and this copy also points to the string literal "Jack". Inside the method, the copied reference is made to point to another literal. This, however, does not affect the original string reference pointing to the literal "Jack". Hence, when the method exits, the reference still points to the same literal.

In Java, parameters (both primitive and object references) are always passed by value.

Listing 4.9 shows a C# program similar to the Java program just shown. Not surprisingly, C# adheres to the same principle of passing both primitives and object references by value.

Listing 4.9 Method Parameter Passing by Value (C#)
using System;
using System.Collections;
public class Test {

  public static void Main(string[] args) {

    //Testing primitives
    int a = 3;
    TestPrimitive(a);
    Console.WriteLine(a);

    //Testing TestObjectList1
    ArrayList l = new ArrayList();
    Console.WriteLine(l.Count);
    TestObjectList1(l);
    Console.WriteLine(l.Count);

    //Testing testObjectList2
    ArrayList ll = new ArrayList();
    ll.Add("Major");
    Console.WriteLine(ll.Count);
    TestObjectList2(ll);
    Console.WriteLine(ll.Count);

    //Testing Strings
    String s = "Jack";
    TestString(s);
    Console.WriteLine(s);
  }
  private static void TestPrimitive(int a) {
    a *= 2;
  }
  private static void TestObjectList1(ArrayList l) {
    l.Add("Jumbo");
  }
  private static void TestObjectList2(ArrayList l) {
    l = null;
  }
  private static void TestString(String s) {
    s = "John";
  }
}

The output of Listing 4.9 is as follows:

3
0
1
1
1
Jack.
The ref Keyword

C#, however, allows you to change this default passing by value behavior using the ref keyword. To Listing 4.9, we add the ref keyword to all the methods and the parameters when the method is called (see Listing 4.10).

Listing 4.10 Using the ref Keyword (C#)
using System;
using System.Collections;
public class Test {

  public static void Main(string[] args) {

    //Testing primitives
    int a = 3;
    TestPrimitive(ref a);
    Console.WriteLine(a);

    //Testing TestObjectList1
    ArrayList l = new ArrayList();
    Console.WriteLine(l.Count);
    TestObjectList1(ref l);
    Console.WriteLine(l.Count);

    //Testing testObjectList2
    ArrayList ll = new ArrayList();
    ll.Add("Major");
    Console.WriteLine(ll.Count);
    TestObjectList2(ref ll);
    if (ll != null) {
      Console.WriteLine(ll.Count);
    }

    //Testing Strings
    String s = "Jack";
    TestString(ref s);
    Console.WriteLine(s);
  }
  private static void TestPrimitive(ref int a) {
    a *= 2;
  }
  private static void TestObjectList1(ref ArrayList l) {
    l.Add("Jumbo");
  }
  private static void TestObjectList2(ref ArrayList l) {
    l = null;
  }
  private static void TestString(ref String s) {
    s = "John";
  }
}

Here is the output of Listing 4.10:

6
0
1
1
John

//Testing primitives
  int a = 3;
  TestPrimitive(ref a);
  Console.WriteLine(a);

The primitive value is changed in the method. Hence, the value of the primitive before the method call is 3, inside the method is 6, and outside the method is also 6.

//Testing TestObjectList1
ArrayList l = new ArrayList();
Console.WriteLine(l.Count);
TestObjectList1(ref l);
Console.WriteLine(l.Count);

Adding the ref keyword here does not change the fact that the object being added to the ArrayList() in the method is actually being added to the original ArrayList() object created before the method. Hence, the size of the ArrayList() before and after the method call is the same as that of Listing 4.9.

//Testing testObjectList2
ArrayList ll = new ArrayList();
ll.Add("Major");
Console.WriteLine(ll.Count);
TestObjectList2(ref ll);
if (ll != null) {
    Console.WriteLine(ll.Count);
}

Adding the ref keyword here passes the reference to the ArrayList as is, and a copy is not made. As a result, when the reference is set to null, the actual reference, and not its copy, is set to null. Therefore, after the method call, the reference is null and we must check for that before calling the Count property on it. Otherwise, we get a NullPointerException.

//Testing Strings
String s = "Jack";
TestString(ref s);
Console.WriteLine(s);

The string reference is passed as is to the method. Inside the method it is made to point to a different literal, and hence that change remains after the method call.

The out Keyword

The following details about the out keyword have been duplicated here from the Microsoft Visual Studio Help files.

The out method parameter keyword on a method parameter causes a method to refer to the same variable that was passed into the method. Any changes made to the parameter in the method will be reflected in that variable when control passes back to the calling method.

Declaring an out method is useful when you want a method to return multiple values. A method that uses an out parameter can still return a value. A method can have more than one out parameter.

To use an out parameter, the argument must explicitly be passed to the method as an out argument. The value of an out argument will not be passed to the out parameter. A variable passed as an out argument need not be initialized. However, the out parameter must be assigned a value before the method returns.

Listing 4.11 shows how an out parameter could be used to create a method that returns multiple values.

Listing 4.11 Using the out Keyword to Create Methods That Return Multiple Values (C#)
using System;
public class Test {
  public static void Main(string[] args) {

    int a;
    int b = DoIt(out a);
    Console.WriteLine(b);
    Console.WriteLine(a);
  }

  private static int DoIt(out int a) {
    a = 2;
    return 1;
  }
}
The params Keyword

The params keyword lets you specify a method parameter that takes an argument where the number of arguments is variable. No additional parameters are permitted after the params keyword in a method declaration, and only one params keyword is permitted in a method declaration.

Listing 4.12 shows the params keyword.

Listing 4.12 Using the params Keyword (C#)
using System;
public class Test {
  public static void Main(string[] args) {
      DoIt(1,2,3,4);

  }
  private static void DoIt(params int[] list) {
    foreach(int i in list)
      Console.WriteLine(i);
  }
}


4.5 Fields

Fields maintain the state of an object. Chapter 7 discusses the data types that are allowed for fields. The following field usage guidelines are taken from the Visual Studio help file:

  • Do not use instance fields that are public or protected. If you avoid exposing fields directly to the developer, classes can be versioned more easily because a field cannot be changed to a property while maintaining binary compatibility. Consider providing get and set property accessors for fields instead of making them public. The presence of executable code in get and set property accessors allows later improvements, such as creation of an object on demand, upon usage of the property, or upon a property change notification. The following code example illustrates the correct use of private instance fields with get and set property accessors.

    public struct Point
{
  private int xValue;
  private int yValue;

  public Point(int x, int y)
  {
    this.xValue = x;
    this.yValue = y;
  }

  public int X
  {
    get
    {
      return xValue;
    }
    set
    {
      xValue = value;
    }
  }
  public int Y
  {
    get
    {
      return yValue;
    }
    set
    {
      yValue = value;
    }
  }
}

  • Expose a field to a derived class by using a protected property that returns the value of the field. This is illustrated in the following code example.

    public class Control: Component
{
  private int handle;
  protected int Handle
  {
    get
    {
      return handle;
    }
  }
}

  • It is recommended that you use read-only static fields instead of properties where the value is a global constant. This pattern is illustrated in the following code example.

    public struct Int32
{
  public static readonly int MaxValue = 2147483647;
  public static readonly int MinValue = 2147483648;
  // Insert other members here.
}

  • Spell out all words used in a field name. Use abbreviations only if developers generally understand them. Do not use uppercase letters for field names. The following is an example of correctly named fields.

    class SampleClass
{
  string url;
  string destinationUrl;
}

  • Do not use Hungarian notation for field names. Good names describe semantics, not type.

  • Do not apply a prefix to field names or static field names. Specifically, do not apply a prefix to a field name to distinguish between static and nonstatic fields. For example, applying a g_ or s_ prefix is incorrect.

  • Use public static read-only fields for predefined object instances. If there are predefined instances of an object, declare them as public static read-only fields of the object itself. Use Pascal case because the fields are public. The following code example illustrates the correct use of public static read-only fields.

    public struct Color
{
  public static readonly Color Red = new Color (0x0000FF);
  public static readonly Color Green = new Color (0x00FF00);
  public static readonly Color Blue = new Color (0xFF0000);
  public static readonly Color Black = new Color (0x000000);
  public static readonly Color White = new Color (0xFFFFFF);

  public Color(int rgb)
  { // Insert code here.}
  public Color(byte r, byte g, byte b)
  { // Insert code here.}

  public byte RedValue
  {
    get
    {
      return Color;
    }
  }
  public byte GreenValue
  {
    get
    {
      return Color;
    }
  }
  public byte BlueValue
  {
    get
    {
      return Color;
    }
  }
}

The following keywords are typically used with fields.

4.5.1 public

Public fields, like public methods, are accessible to any class. Use of public static or instance fields is strongly discouraged.

4.5.2 private

Private fields are accessible only to the internal members of a class. Private fields can be defined at the object level (instance) or the class level (static).

4.5.3 protected

Protected fields of a class are accessible to the subclasses of that class.

4.5.4 internal

Internal fields are accessible to classes within the same assembly.

4.5.5 const

The const keyword is used to modify a declaration of a field or local variable. It specifies that neither the value of the field nor the local variable can be modified. A const can be any of the following types: byte, char, short, int, long, float, double, decimal, bool, string, an enum type, or a reference type. The only possible values for constants of reference types are string and null. Fields that are const cannot be static.

Listing 4.13 shows the use of const. Note that multiple declarations of const are allowed, and also a const can be used in an expression.

Listing 4.13 Using the const Keyword (C#)
using System;
public class Test {

  public static void Main(string[] args) {
    const int a = 12;
    const string b = "Jack";
    const int c = a *2;

    Modify (a, b);
    Console.WriteLine(a+" "+b);
    Console.WriteLine(c);
  }
  private static void Modify(int a, string b) {
    a = 4;
    b = "John";
  }
}

Note that the compiler does not complain when we try to assign a value to the constant the second time (in the Modify method). In Java, the standard way to simulate a const is to declare const as final. Although final primitives are similar to the C# const, final object references are not the same as the C# const. In C#, the only valid values for constant references are null and a string literal.

That's not the case with Java. You can declare a final HashMap and have the HashMap() be initialized later in the program, as shown in the following Java snippet. Also note that the Java compiler will complain, unlike C#'s, when you try to reassign a final primitive.

import java.util.HashMap;
public class ConstantTest {
  public static void main(String[] args) {
    final HashMap map = new HashMap();
    map.put("1", "1");
    final int java = 2;
    //java = 3;
    System.out.println(java);
    System.out.println(map);
  }
}

4.5.6 readonly

Fields marked readonly show behavior similar to that of Java final fields. The readonly keyword is a modifier that you can use on fields. When a field declaration includes a readonly modifier, assignments to the fields introduced by the declaration can occur only as part of the declaration or in a constructor in the same class.

You can assign a value to a readonly field only in the following contexts:

  • When the variable is initialized in the declaration—for example:

    public readonly int y = 5;

  • For an instance field, in the instance constructors of the class that contains the field declaration; or for a static field, in the static constructor of the class that contains the field declaration. These are also the only contexts in which it is valid to pass a readonly field as an out or ref parameter.

Trying to reassign a readonly field outside the eligible contexts will cause a compiler error similar to that of Java. Listing 4.14 shows the use of readonly fields.

Listing 4.14 Using the readonly Keyword for Fields (C#)
using System;
class Direction {
  int north;
  int south;
  int east;
  int west;
  public Direction (int north, int south, int east, int west) {
      this.north = north;
      this.south = south;
      this.east = east;
      this.west = west;
  }
      public static readonly Direction North;
      public static readonly Direction South;
      public static readonly Direction East;
      public static readonly Direction West;
  static Direction () {
      North = new Direction(1,0,0,0);
      South = new Direction(0,1,0,0);
      East = new Direction(0,0,1,0);
      West = new Direction(0,0,0,1);
  }
  static void Main(string[] args) {
      Direction coor = Direction.North;
  }
}

Note that the readonly fields are initialized in the static constructor.

4.5.7 static

Static fields are applicable to the entire class of objects instead of being local only to the object. In C#, the use of public static fields is strongly discouraged. Instead, public properties should be used (as discussed in Section 4.6).

4.5.8 volatile

The volatile keyword indicates that a field can be modified in the program by something such as the operating system, the hardware, or a concurrently executing thread.


4.6 Properties

Properties are like smart fields. A property generally has a private data member accompanied by accessor functions and is accessed syntactically as a field of a class. Although properties can have various access levels, the most common and useful access level is public access. You should expose properties instead of public fields from your components. Properties can be versioned, they allow data hiding, and the accessor methods can execute additional logic. Generally, because of JIT optimizations, properties are no more expensive than fields. Properties should be stateless with respect to other properties. They can be set in any order. Properties are discussed further in Chapter 17.

The following keywords are applicable to properties:

  • public. Public properties are accessible to any class. This is the most common use of a property. Properties are usually public, encapsulating the more private field.

  • private. Properties can also be private. This access modifier rarely makes sense for a property, but it is there in case you ever need a private property.

  • protected. Properties can be protected, in which case they are available only to the members of that class as well as its subclass.

  • internal. Internal properties are available to any class of the assembly.

  • override. When a subclass chooses to override a property, it uses the override modifier in front of the property definition.

  • new. When a subclass chooses to hide the superclass's implementation of the property, it uses the new modifier in front of the property definition.

  • abstract. Properties that have no implementation for their get or set or both are considered abstract, and such properties should be defined in an abstract class.

  • static. Properties that belong to a class of objects instead of a specific object instance are called static properties.

4.6.1 Inheritance and Properties

Properties wrap a getter and a setter, and therefore the access modifiers used on a property are applicable to both the get and the set blocks of the property. A subclass can choose to inherit, hide, or override a property of the superclass. A subclass can inherit public, protected, and internal properties. It cannot inherit private properties of the superclass. A subclass that hides a property implementation of the superclass uses the new keyword. Such a subclass can provide a getter or a setter for the property if it is missing in the superclass. So technically, a subclass could convert a read-only superclass property into a mutable one using the new keyword.

A virtual property is like a virtual method. It can be overridden by the subclass. An overridden property cannot provide additional get or set blocks if the superclass does not provide them. That is, a subclass can override only that behavior already provided by the superclass and cannot add new behavior.

A sealed property follows the same rules as a sealed method. A sealed property usually allows a superclass's property implementation to be overridden but does not allow a subclass to override its sealed property.

Listing 4.15 shows an abstract property being defined by the subclass. Note that the subclass cannot provide a setter for the property.

Listing 4.15 Property Inheritance C#
using System;
abstract class SuperClass {
  public abstract int Color {
    get;
  }
}
class SubClass : SuperClass {

  int color;
  public override int Color {
    get {
      return color;
    }
  }
  static void Main(string[] args) {
    SubClass sc = new SubClass();
    Console.WriteLine(sc.Color);
  }
}


4.7 Variable Scoping

In C#, local variables can be given only those names that allow them to be uniquely identified within a scope. The following code illustrates variable scoping:

using System;
public class Test {
  public static void Main(string[] args) {
    int i = 4;
    for (int j = 0; j < 10; ++j) {
      int i = 6 *j;
    }
  }
}

If you cut and paste this snippet, the compiler will complain, stating the following:

A local variable named 'i' cannot be declared in this scope because it would give a different meaning to 'i', which is already used in a 'parent or current' scope to denote something else.

This restriction is new to Java programmers because this code would compile and run fine in Java.

Although it is easy to create nested scopes having the same variable name, it is a bad practice. Lack of code readability may not be an issue for a snippet, but can be nerve-wracking in a large project. C# has taken steps to discourage this by disallowing the practice of using the same variable name. That does not mean you cannot use something like the following:

using System;
public class Person {
  string name;
  int age;
  public Person (string name, int age) {
    this.name = name;
    this.age = age;
  }
}

Here, the name and age variables are qualified using the this keyword, which the compiler recognizes.

Method variables or class variables must be initialized in C# before they can be accessed. Therefore, the following class doesn't compile:

using System;
public class Test {
  public static void Main(string[] args) {
    int n;
    Console.WriteLine("Value of n is ", n);
  }
}

That's because the local variable n is not initialized. Class variables assume a default value. In the case of int n in the following code, the default value is 0:

using System;
public class Test {
  int n;
  public static void Main(string[] args) {
    Test t = new Test();
    Console.WriteLine("Value of n is ", t.n);
  }
}

You don't have to initialize arrays and structs before accessing them.


4.8 Object Destruction

As mentioned in Chapter 1, Java and C# provide automatic garbage collection. Therefore, objects allocated in memory are reclaimed by the runtime environment without any special coding by the programmer. This feature alone makes programming in Java and C# easier than in C++. However, programmers need some knowledge of memory management and garbage collection to avoid making mistakes that would hamper the garbage collection process.

The java.lang.Object class provides a finalize() method that gets called during garbage collection. You cannot control the order and timing of this call. Nonetheless, Java programmers tend not to use finalizers for memory management for the following reasons:

  • There is usually no need for explicit memory management because the onus is usually on smart garbage collectors.

  • There are better constructs for handling memory, such as soft, weak references.

  • Short-term release of resources is handled in try-finally blocks.

C# is no different when it comes to memory management. Even though you can make extern DLL calls to reclaim memory in the destructor, such coding practices are the exception and not the norm. Sometimes you need to release non-memory-based resources in a more timely manner. Releasing such resources in the finalize() method is not recommended at all. Nor should these resources (file handles, database connections) be released in the destructor. In Java, a resource typically is released in the finally block:

Resource res = null;
try {
  res = getResource();
} finally {
  freeResource(res);
}

C# provides a similar paradigm and goes further in facilitating the release of resources using the IDisposable interface. The IDisposable interface provides a Dispose() method by which the classes can free up resources. Listing 4.16 shows a class that implements the IDisposable interface.

Listing 4.16 Using the IDisposable Interface
using System;
using System.Runtime.InteropServices;
public class DatabaseWrapper : IDisposable {

  int handle = 0;
  public DatabaseWrapper() {
    handle = GetNewHandle();
  }
  public void Dispose() {
    FreeHandle(handle);
    GC.SuppressFinalize(this);
  }
  [DllImport("dll.dll")]
  static extern int GetNewHandle();
  [DllImport("dll.dll")]
  static extern void FreeHandle(int handle);
  static void Main(string[] args) {
    DatabaseWrapper dw = null;
    try {
      dw = new DatabaseWrapper();
    } finally {
      dw.Dispose();
    }
  }
}

C# also provides the using construct, letting you avoid the try-finally block. Therefore,

using (DatabaseWrapper dw = new DatabaseWrapper()) {
//Use the dw object to do stuff here
}

The using block will take care of calling the Dispose() method properly.


4.9 Summary

This chapter discussed object creation and compares System.Object to java.lang.Object. We also looked at class members such as methods, constructors, fields, and properties along with their access modifiers. Here's a summary:

  • Public class members are accessible to all classes.

  • Private class members are accessible to only that class.

  • Protected members are accessible to that class as well as its subclass.

  • Internal members are accessible to all classes in that assembly.

  • Static members are defined on the class level and do not need an object instance reference to access them. In fact, in C# you cannot use an instance reference to call a static method.

  • Abstract methods and properties must be inside abstract classes. Abstract methods and properties must be overridden by a subclass if it is not abstract.

  • Sealed methods and properties allow you to override virtual methods and properties, but subclasses cannot further override sealed methods and properties. You cannot derive from sealed classes.

  • Unlike Java, methods and properties in C# are nonvirtual by default. Subclasses can choose to inherit, hide, or override superclass methods and properties.

  • You can use the new keyword to hide methods and properties.

  • You can use the override keyword to override methods and properties.

  • Access modifiers on a property are applicable to both the set and the get block. A subclass that hides a superclass property can provide an additional get and set block if one is absent in the superclass. A subclass that overrides a superclass property cannot provide an additional get or set block if one is absent in the superclass.

  • C# provides a default no-argument constructor if one is not explicitly provided in the class.

  • Local variables must be initialized before they can be used. Class variables, references, and arrays need not be initialized before use.

  • Use of finalizers and destructors to release external resources is discouraged.

  • You can release external resources consistently by using the IDisposable interface.


Chapter 5. Understanding Inheritance and Polymorphism

This chapter dives into class inheritance and polymorphism. You will learn about the behavior of the object references of base and derived classes. In this chapter you will use the access modifiers discussed in Chapter 4 to actually model classes.


5.1 Inheritance

Inheritance allows a class to extend and reuse behavior defined in its ancestor class (superclass). Java and C# support only single class inheritance, in which a given class can inherit from only one other class. This is in contrast to C++, which allows a class to inherit behavior from multiple classes.

Inheritance creates an is-a relationship between the subclass and the superclass; in other words, a subclass is a type of the superclass. Because of the single class inheritance in Java and C#, you must be careful in choosing which class to inherit your class from. For example, if you have a custom maplike data structure, should you inherit from java.util.HashMap? Doing so would instantly make your data structure a java.util.HashMap, but suppose that in the future you want to maintain the order in which you put the key-value entries in your map. In that case, you would have rewrite your class because java.util.HashMap does not maintain the order in which key-value entries are put in it.

Inheritance is a useful mechanism for leveraging and extending an existing code base. Exactly which class members can be inherited from a superclass depends on the access modifiers used on its class members. Later in this chapter we will look at the C# access modifiers that control its inheritance mechanism.


5.2 Polymorphism and Virtual Methods

As the name suggests, polymorphism is the ability to have multiple (poly) faces (morphs). In the context of OOP languages, this means that the behavior of a method of a class can be changed at runtime depending on the object on which it is called. In Chapter 4 you learned about the concept of object references and instances. A reference is a pointer to the actual instance. In OOP, you can have a reference to a superclass and assign an instance of the subclass to that reference:

Shape s = new Circle();
s.draw();

Here, Shape is a superclass having one subclass, Circle. There is a draw() method in the Shape class, and there is a draw() method in the Circle class. When you call the draw() method on the reference, s, which class's method does it actually call? Does it call the Shape class's draw() method, or does it call the Circle class's draw() method? In Java, the draw() method of the object instantiated will be called—in other words, the draw() method of the Circle class. In C#, depending on whether the superclass method has a virtual keyword and the subclass has the override keyword on the same method, the draw() method of the Circle class will get called.

Similarly, you can have several other subclasses of the Shape class, each having its own implementation of the draw() method. Depending on the polymorphic mechanism implemented by the language, the draw() method of the individual subclasses will get called. The end effect is that the same method call, draw(), has multiple behaviors at runtime.

How does the runtime know that it must call the method on the Circle class even though the reference is that of the Shape class? All this magic can be attributed to the mechanism of late binding (also called dynamic binding) and virtual methods.

Connecting a function call to a function body is called binding. When binding is performed before the program is run (by the compiler and linker), it's called early binding, or static binding. When binding is performed at runtime based on the type of the object on which the method is called, it is called late binding, or dynamic binding.

When a language implements late binding, there must be a mechanism that determines the type of the object at runtime and calls the appropriate member function. In a compiled language, the compiler still doesn't know the actual object type, but it inserts code that finds out the object type and calls the correct function body. The late-binding mechanism varies from language to language, but you can imagine that some sort of type information must be installed in the objects.

To accomplish this, the typical compiler creates a single table (called the VTABLE) for each class that contains virtual methods. The compiler places the addresses of the virtual functions for that particular class in the VTABLE. In each class with virtual functions, the compiler secretly places a pointer, called the vpointer (abbreviated VPTR), which points to the VTABLE for that object. When you make a virtual function call through a base-class pointer (that is, when you make a polymorphic call), the compiler quietly inserts code to fetch the VPTR and look up the method address in the VTABLE, thus calling the correct method and causing late binding to take place.

Late binding is always slower than static binding because of the extra discovery the runtime must do to find the type of the object. With that in mind, let's explore inheritance and polymorphism in C# and Java.


5.3 Exploring C# Polymorphism

Let's say that we are doing some statistical forecasting of book profits for a retail store. Before we do the GUI and the backend database work, we model the core components of this imaginary application. A Book class seems like an obvious place to start. Listing 5.1 shows the Book class.

Listing 5.1 The Book Class (C#)
public class Book {

  protected string isbn;
  protected float costprice;
  protected int copiessold;
  protected float sellingprice;
  protected float popularityfactor;

  public Book (string isbn, float costprice,
      float sellingprice, int copiessold, float popularityfactor)
  {
    this.isbn = isbn;
    this.costprice = costprice;
    this.sellingprice = sellingprice;
    this.copiessold = copiessold;
    this.popularityfactor = popularityfactor;
  }

  public float EstimatedProfit() {
    return TheoreticalProfit() * popularityfactor;
  }

  protected float TheoreticalProfit() {
    return (sellingprice  costprice)*copiessold;
  }

  public string GetGenre() {
    return "Book";
  }

  //static void Main(string[] args) {}
}

The following can be observed from this first implementation:

  • Book class is declared as public. Because this class is the core component of the application, it makes sense that the class be accessible to all parts of the application. The Book class has the following fields: isbn, costprice, sellingprice, copiessold, and popularityfactor. The fields are all protected. We anticipate that the subclasses will need access to these fields to make various calculations.

  • Most methods are public except for the TheoreticalProfit() method, which is protected.

  • The GetGenre() method is hardcoded for now to return the string "Book".

Listing 5.2 shows a subclass of the Book class.

Listing 5.2 Subclass of the Book Class (C#)
public class RomanceNovel : Book {

  public RomanceNovel (string isbn, float costprice,
         float sellingprice, int copiessold,
            float popularityfactor) :base (isbn,
               costprice, sellingprice, copiessold,
                                              popularityfactor) {
  }

  public float EstimatedProfit() {

    if (popularityfactor < 0.5f) {
      return TheoreticalProfit() * (1-popularityfactor);
    } else {
      return TheoreticalProfit() * (1-popularityfactor);
    }
  }
  public string GetGenre() { return "Romance"; }
}

The method TheoreticalProfit() and the fields are protected so that they can be used by the subclass. Note that the subclass calls the superclass constructor using the base keyword. Listing 5.3 shows the driver class that will serve as the entry point.

Listing 5.3 Entry Point for Listings 5.1 and 5.2 (C#)
using System;

public class BookTest {

  static void Main(string[] args) {
    Console.WriteLine("Reference = Book, Object = Book");
    Book book = new Book("85745", 45.6f, 65.5f, 200, 0.2f);
    Console.WriteLine(book.EstimatedProfit());
    Console.WriteLine("Reference = RomanceNovel,
                                       Object = RomanceNovel");
    RomanceNovel novel = new RomanceNovel("85745", 45.6f,
                                             65.5f, 200, 0.2f);
    Console.WriteLine(novel.EstimatedProfit());
    Console.WriteLine("Reference = Book, Object = RomanceNovel");
    book = new RomanceNovel("85745", 45.6f, 65.5f, 200, 0.2f);
    Console.WriteLine(book.EstimatedProfit());
  }

}

The output of Listing 5.3 is as follows:

Reference = Book, Object = Book
Calling Constructor of Book class
Calling EstimatedProfit of Book class
796.0001

Reference = RomanceNovel, Object = RomanceNovel
Calling Constructor of Book class
Calling Constructor of RomanceNovel class
Calling EstimatedProfit of RomanceNovel class
3184

Reference = Book, Object = RomanceNovel
Calling Constructor of Book class
Calling Constructor of RomanceNovel class
Calling EstimatedProfit of Book class
796.0001

The first example uses a Book reference and assigns a Book object to it. Therefore, Book's constructor gets called and Book's EstimatedProfit() method gets called. No surprises there.

The second example assigns a RomanceNovel object to a RomanceNovel reference. The RomanceNovel constructor calls the superclass constructor first. The EstimatedProfit() of the RomanceNovel class gets called. So far, the behavior is similar to that of Java.

The third example is what makes Java programmers gasp and wonder what is going on. The reference is that of the Book class, but the object created is that of RomanceNovel; Java fundamentals suggest that the method being called (EstimatedProfit) should be on the object instantiated and not on the reference to which it is assigned.

The reason for this apparent discrepancy is that all C# methods are nonvirtual by default. Java methods are virtual by default, and this means that the virtual dispatching mechanism will invoke the method on the object instantiated and not on the reference to which it is assigned. That is not the case with C#. The EstimatedProfit() methods are not virtual, and therefore the method called will be on the reference. To get Java-like behavior you would have to cast the book reference to a RomanceNovel reference and then call the method on this new reference:

RomanceNovel rm = (RomanceNovel) book;
rm.EstimatedProfit();

C#, unlike Java, does not assume that subclasses should always be able to override a superclass's methods. As mentioned in earlier chapters, subclasses have the option to inherit the superclass implementation as is, hide the implementation, or override it. The default behavior, as you have seen from this shocking example, is that subclasses will neither hide nor override the superclass implementation without explicitly doing so (more about this later).

What are the implications of this important difference between C# and Java? Let's say you have an array of Account objects that you have treated generically by assigning all of them to the Account reference. The Account class has a method CalculateBalance that remains the same for all types of accounts. The Account class and the method have been working perfectly for years, and things are running smoothly. Now let's say someone decides to write a subclass of your golden Account class. The developer overrides the implementation of CalculateBalance() and writes a corrupt version of that method. Now suddenly your application behaves differently after years of consistency, simply because of a subclass that decided to change the way things get done. This example might seem far-fetched, but it is always a possibility.

Both C# and Java are derivatives of the C++ language. C++ has the virtual keyword, which is used for signaling explicit virtual dispatching. C# is closer to C++ in this regard. In Java, all methods are by default virtual and polymorphic. This makes for easier extension, but it is also slower because late-binding virtual method calls are usually slower than early-binding nonvirtual method calls.

5.3.1 Using the virtual and override Keywords

In OOP, the virtual keyword means that when a call to a method is made, the compiler should look at the real type of the object (and not only the reference) and then should call the appropriate method on the object. To activate virtual dispatching, we now add the virtual keyword to Listing 5.1 and add the override keyword to the subclass, as shown in Listings 5.4 and 5.5.

Listing 5.4 Listing 5.1 with the virtual Keyword (C#)
using System;

public class Book {

  private string isbn;
  protected float costprice;
  protected int copiessold;
  protected float sellingprice;
  protected float popularityfactor;

  public Book (string isbn, float costprice, float sellingprice,
                        int copiessold, float popularityfactor) {
    this.isbn = isbn;
    this.costprice = costprice;
    this.sellingprice = sellingprice;
    this.copiessold = copiessold;
    this.popularityfactor = popularityfactor;
    Console.WriteLine("Calling Constructor of Book class");
  }

  public virtual float EstimatedProfit() {
    Console.WriteLine("Calling EstimatedProfit of Book class");
    return TheoreticalProfit() * popularityfactor;
  }

  protected float TheoreticalProfit() {
    return (sellingprice  costprice)*copiessold;
  }

  public string GetGenre() {
    return "Book";
  }
}
Listing 5.5 Listing 5.2 with the override Keyword (C#)
using System;

public class RomanceNovel : Book {

  public RomanceNovel (string isbn, float costprice,
         float sellingprice, int copiessold,
            float popularityfactor) : base (isbn,
               costprice, sellingprice, copiessold,
                                              popularityfactor) {

    Console.WriteLine("Calling Constructor of RomanceNovel class");
  }

  public override float EstimatedProfit() {
    Console.WriteLine("Calling EstimatedProfit of RomanceNovel
                                                        class");
    if (popularityfactor < 0.5f) {
      return TheoreticalProfit() * (1-popularityfactor);
    } else {
      return TheoreticalProfit() * (1-popularityfactor);
    }
  }

  public string GetGenre() { return "Romance"; }
}

The entry point program (Listing 5.3), when run along with Listing 5.4 and Listing 5.5 instead of Listing 5.1 and Listing 5.2, now gives the following output:

Reference = Book, Object = Book
Calling Constructor of Book class
Calling EstimatedProfit of Book class
796.0001
Reference = RomanceNovel, Object = RomanceNovel
Calling Constructor of Book class
Calling Constructor of RomanceNovel class
Calling EstimatedProfit of RomanceNovel class
3184
Reference = Book, Object = RomanceNovel
Calling Constructor of Book class
Calling Constructor of RomanceNovel class
Calling EstimatedProfit of RomanceNovel class
3184

What happens when the reference on the left side is an interface and not the base class or the derived class? Let's create an interface, IBook (Listing 5.6).

Listing 5.6 Interface IBook (C#)
public interface IBook {
  float EstimatedProfit();
}

Next, we have the Book and the RomanceNovel classes implement that interface. Note that we have not shown the complete listings of the modified Book and RomanceNovel classes because only one line has changed in the classes:

public class Book : IBook
public class RomanceNovel : Book, IBook

Next, we rewrite the entry point class (Listing 5.3) so as to take into account our IBook interface, as shown in Listing 5.7.

Listing 5.7 Entry Point Program Using the IBook Interface (C#)
using System;
public class BookTest {

  static void Main(string[] args) {
    Console.WriteLine("Reference = IBook, Object = Book");
    IBook book = new Book("85745", 45.6f, 65.5f, 200, 0.2f);
    Console.WriteLine(book.EstimatedProfit());
    Console.WriteLine("Reference = IBook, Object =
    RomanceNovel");
    IBook novel = new RomanceNovel("85745", 45.6f, 65.5f,
                                                   200, 0.2f);
    Console.WriteLine(novel.EstimatedProfit());
  }
}

Here is the output of Listing 5.7:

Reference = IBook, Object = Book
Calling Constructor of Book class
Calling EstimatedProfit of Book class
796.0001
Reference = IBook, Object = RomanceNovel
Calling Constructor of Book class
Calling Constructor of RomanceNovel class
Calling EstimatedProfit of RomanceNovel class
3184

When the reference is that of an interface, then the methods called will always be those of the object instantiated.

So far, we have seen the use of the override and virtual keywords in relatively shallow class hierarchies. How do things work out when there are several levels of inheritance? To understand this we create a subclass of RomanceNovel (Listing 5.5). Listing 5.8 shows this subclass.

Listing 5.8 A Subclass of the RomanceNovel class (C#)
using System;

public class MillsAndBoone : RomanceNovel {

  public MillsAndBoone (string isbn, float costprice,
           float sellingprice, int copiessold,
              float popularityfactor) :base (isbn,
                 costprice, sellingprice, copiessold,
popularityfactor) {
    Console.WriteLine("Calling Constructor of
                             MillsAndBoone class");
  }
  public override float EstimatedProfit() {
    Console.WriteLine("Calling EstimatedProfit of
                             MillsAndBoone class");
    if (popularityfactor < 0.5f) {
      return TheoreticalProfit() * (1-popularityfactor);
    } else {
      return TheoreticalProfit() * (1-popularityfactor);
    }
  }

  public string GetGenre() { return "Romance"; }
}

The driver class (Listing 5.3) is now modified to accommodate this new subclass. The new driver class is shown in Listing 5.9.

Listing 5.9 A Driver Class Using the Subclass of the RomanceNovel Class (C#)
using System;

public class BookTest {

  static void Main(string[] args) {
    Console.WriteLine("Reference = Book, Object = Book");
    Book book = new Book("85745", 45.6f, 65.5f, 200, 0.2f);
    Console.WriteLine(book.EstimatedProfit());
    Console.WriteLine("Reference = RomanceNovel,
                            Object = RomanceNovel");
    RomanceNovel novel = new RomanceNovel("85745", 45.6f,
                                             65.5f, 200, 0.2f);
    Console.WriteLine(novel.EstimatedProfit());
    Console.WriteLine("Reference = Book, Object = RomanceNovel");
    book = new RomanceNovel("85745", 45.6f, 65.5f, 200, 0.2f);
    Console.WriteLine(book.EstimatedProfit());
    Console.WriteLine("Reference = Book, Object = MillsAndBoone");
    book = new MillsAndBoone("85745", 45.6f, 65.5f, 200, 0.2f);
    Console.WriteLine(book.EstimatedProfit());
  }

}

The output of Listing 5.9 is as follows:

Reference = Book, Object = Book
Calling Constructor of Book class
Calling EstimatedProfit of Book class
796.0001
Reference = RomanceNovel, Object = RomanceNovel
Calling Constructor of Book class
Calling Constructor of RomanceNovel class
Calling EstimatedProfit of RomanceNovel class
3184
Reference = Book, Object = RomanceNovel
Calling Constructor of Book class
Calling Constructor of RomanceNovel class
Calling EstimatedProfit of RomanceNovel class
3184
Reference = Book, Object = MillsAndBoone
Calling Constructor of Book class
Calling Constructor of RomanceNovel class
Calling Constructor of MillsAndBoone class
Calling EstimatedProfit of MillsAndBoone class
3184

In the last output, the MillsAndBoone class defined the method to be overridden, and the method call was made on that object. A subclass can use the override modifier on a method only if the superclass has the virtual, override, or abstract modifier for that method. Table 5.1 shows the virtual and override combinations for our class hierarchy. This illustrates what happens when virtual and override modifiers are on different methods of an inheritance hierarchy. In hierarchy A, the methods on the Book, RomanceNovel, and MillsAndBoone class are all virtual. In hierarchy B, the method on the Book class is virtual, and the methods on the RomanceNovel and MillsAndBoone classes are override, and so on.

Table 5.2 shows the actual method that gets called in Listing 5.3 when an object of that specific class is instantiated and assigned to a Book reference. For hierarchy A, no matter which object is instantiated, method 1 (Book's EstimatedProfit()) always gets called. For hierarchy B, the virtual dispatching mechanism calls the appropriate method on the object instantiated. Hierarchy C shows that the runtime decided not to do any virtual dispatching. Hierarchy D shows that for both RomanceNovel and MillsAndBoone the method called was that of the RomanceNovel class.

Virtual dispatching is a mechanism by which the compiler generates a piece of code that identifies the type of the object the reference is pointing to. Instead of making the direct function call on the reference, the compiler discovers the object type and then calls the method on the type closest to the one pointed to by the reference. This process of microdiscovery—identifying the type of the object instantiated and traversing the object hierarchy to find which object the method has overridden—happens extremely fast. However, it is still slower than executing a direct function call on the reference. In the case of hierarchy C in Table 5.2, the runtime determined its actions based on the fact that the direct subclass of Book (i.e., RomanceNovel) does not override the method; the runtime converted the virtual dispatch call to a straight function call on the reference, thereby calling method 1 (Book's EstimatedProfit() method) for all three objects.

Table 5.1. virtual-override Keyword Combinations

(Method ID) Method

Hierarchy A

Hierarchy B

Hierarchy C

Hierarchy D

(1) Book.Estimated Profit()

virtual

virtual

virtual

virtual

(2) RomanceNovel.EstimatedProfit()

virtual

override

virtual

override

(3) MillsAndBoone.EstimatedProfit()

virtual

override

override

virtual

Recall the critical difference between C# and Java discussed earlier: In C# all methods are nonvirtual by default, whereas in Java all methods are virtual. As a result, virtual dispatching is turned off by default in C#. A method call will be made on the reference rather than on the object that is instantiated and assigned to the reference. To turn on virtual dispatching in C#, you must use either the virtual or the override modifier on the superclass and explicitly add an override modifier on the subclass.

5.3.2 Effects of Method Parameters and Conversion Rules

So far we've looked at two things that influence method invocation: the override and virtual modifiers. Also influencing which method gets called are the method parameters and their conversion rules (Listing 5.10).

Table 5.2. Methods Called Based on Table 5.1 Keyword Combination

Method Number

Hierarchy A

Hierarchy B

Hierarchy C

Hierarchy D

1

1

1

1

1

2

1

2

1

2

3

1

3

1

2

Listing 5.10 Influence of Method Parameter Types on Method Invocation (C#)
using System;

public class SuperClass {

  public void SetVal(int i) {
    Console.WriteLine("SetVal Super class method");
  }

  public void Calc(short i) {
    Console.WriteLine("Calc Super class method");
  }
}

public class SubClass : SuperClass {

  public void SetVal(short i) {
    Console.WriteLine("SetVal Sub class method");
  }

  public void Calc(int i) {
    Console.WriteLine("Calc Sub class method");
  }

  static void Main(string[] args) {
    int i = 7;
    short j = 8;
    SubClass sc = new SubClass();
    sc.SetVal(i);
    sc.Calc(i);
    sc.SetVal(j);
    sc.Calc(j);
  }
}

In Listing 5.10, a subclass object is instantiated and assigned to a subclass reference, and therefore any method call on the reference should always call the subclass method. This, however, is not what happens; the first method call, sc.SetVal(i), is made on the superclass because the int argument cannot be converted to the short parameter of the subclass's SetVal method. The runtime calls the next best version of the method, which exists in the superclass. The output of Listing 5.10 is as follows:

SetVal Super class method
Calc Sub class method
SetVal Sub class method
Calc Sub class method


5.4 Methods and Inheritance

We mentioned earlier that you can control inheritance in C# by using various access modifiers on the class members. This section explores the C# access modifiers.

5.4.1 Access Modifiers

In Java, a subclass cannot override a superclass method and make its access level more restrictive. That is, a public superclass method cannot be made private or protected, and a protected superclass method cannot be made private in the subclass. Subclasses can, however, make the methods less restrictive. C# does not have this restriction; in fact, a subclass can make a method less or more restrictive than the superclass, as long as the method can be called on the reference to which the created object is assigned. In Listing 5.11, the SubClass class makes the SetVal method less restrictive, but that is not a concern as long as the call to SetVal is made on the reference (which is the SuperClass reference here). If the line

SuperClass sc = new SubClass();

were changed to the following, the compiler would complain about the protected access modifier because the method call is made on the reference (SubClass):

SubClass sc = new SubClass();

Remember, for virtual dispatching to work in C#, you must use the virtual–override modifier combination; and whenever you use it, the access modifiers on the methods of both the superclass and the subclass should be the same. So it is safe to say that an overridden method in C# cannot be made less or more restrictive.

Listing 5.11 Access Modifiers in Inheritance (C#)
using System;

public class SuperClass {

  public void SetVal() {
    Console.WriteLine("Super class method");
  }
}

public class SubClass : SuperClass {
  protected void SetVal() {
    base.SetVal();
    Console.WriteLine("Sub class method");
  }

  static void Main(string[] args) {
    SubClass sc = new SubClass();
    sc.SetVal();
  }
}

Listing 5.12 shows a three-class hierarchy. The Test class calls the appropriate method on the objects. Note that the compiler complains when we try to access private and protected members of the classes from the Test class.

Listing 5.12 Class Hierarchy and Access Modifiers (C#)
using System;

public class SuperClass {

  public void PublicM () {
      Console.WriteLine("Public Super Class");
  }

  protected void ProtectedM () {
      Console.WriteLine("Protected Super Class");
  }

  private void PrivateM () {
      Console.WriteLine("Private Super Class");
  }
}

public class SubClass : SuperClass {
  public void PublicM () {
      Console.WriteLine("Public Sub Class");
  }

  protected void ProtectedM () {
      Console.WriteLine("Protected Sub Class");
  }

  private void PrivateM () {
      Console.WriteLine("Private Sub Class");
  }
}

public class LeafClass : SubClass {

  public void PublicM () {
      Console.WriteLine("Public Leaf Class");
  }

  protected void ProtectedM () {
      Console.WriteLine("Protected Leaf Class");
  }

  private void PrivateM () {
      Console.WriteLine("Private Leaf Class");
  }
}

public class Test {
  static void Main(string[] args) {
    LeafClass lc = new LeafClass();
    lc.PublicM();
    //lc.PrivateM();
    //lc.ProtectedM();
    SubClass sc = new LeafClass();
    sc.PublicM();
    //sc.PrivateM();
    //sc.ProtectedM();
    SuperClass suc = new LeafClass();
    suc.PublicM();
    //suc.PrivateM();
    //suc.ProtectedM();
  }
}

The output of Listing 5.12 is as follows:

Public Leaf Class
Public Sub Class
Public Super Class

Now let's move the static Main method from the Test class to the LeafClass and remove the Test class. We will test the effect of access modifiers when method calls are made from within the class hierarchy to classes in the same hierarchy (Listing 5.13).

Listing 5.13 Moving the Main Method to LeafClass (C#)
using System;

public class SuperClass {

  public void PublicM () {

      Console.WriteLine("Public Super Class");
  }
  protected void ProtectedM () {
      Console.WriteLine("Protected Super Class");
  }

  private void PrivateM () {
      Console.WriteLine("Private Super Class");
  }
}

public class SubClass : SuperClass {

  public void PublicM () {
      Console.WriteLine("Public Sub Class");
  }

  protected void ProtectedM () {
      Console.WriteLine("Protected Sub Class");
  }

  private void PrivateM () {
      Console.WriteLine("Private Sub Class");
  }
}

public class LeafClass : SubClass {

  public void PublicM () {
      Console.WriteLine("Public Leaf Class");
  }

  protected void ProtectedM () {
      Console.WriteLine("Protected Leaf Class");
  }

  private void PrivateM () {
      Console.WriteLine("Private Leaf Class");
  }

  static void Main(string[] args) {
    LeafClass lc = new LeafClass();
    lc.PublicM();
    lc.PrivateM();
    lc.ProtectedM();
    SubClass sc = new LeafClass();
    sc.PublicM();
    //sc.PrivateM();
    //sc.ProtectedM();
    SuperClass suc = new LeafClass();
    suc.PublicM();
    //suc.PrivateM();
    //suc.ProtectedM();
  }
}

Here's the output of Listing 5.13:

1. Public Leaf Class
2. Private Leaf Class
3. Protected Leaf Class
4. Public Sub Class
5. Public Super Class

The first three lines of the output are the result of the three method calls on the LeafClass reference lc. The next two lines are the result of the calls to the SubClass and the SuperClass reference. Because the Main method is inside the LeafClass, it has access to all the members of the LeafClass. The Main method does not have access to the private and protected members of SubClass and SuperClass.

Now let's move the Main method into the SubClass class. Main now has access to the previously inaccessible private and protected methods of the SubClass.

static void Main(string[] args) {
  LeafClass lc = new LeafClass();
  lc.PublicM();
  lc.PrivateM();
  lc.ProtectedM();
  SubClass sc = new LeafClass();
  sc.PublicM();
  sc.PrivateM();
  sc.ProtectedM();
  SuperClass suc = new LeafClass();
  suc.PublicM();
  //suc.PrivateM();
  //suc.ProtectedM();
}

Note that the Main method can still access all the members of the LeafClass from the SubClass. It cannot, however, access the private and the protected members of its superclass SuperClass. The output of the preceding program is as follows:

1. Public Leaf Class
2. Private Sub Class
3. Protected Sub Class
4. Public Sub Class
5. Private Sub Class
6. Protected Sub Class
7. Public Super Class

The explanation for line 1 of the output is obvious from the fact that the method call was made on a Leaf reference, and a LeafClass object was assigned to that reference. Lines 2 and 3 are interesting because even though the object assigned and the reference used are those of the LeafClass, the runtime decides to make the method call on SubClass (the class where the Main method is). It is almost as if the private and protected methods of LeafClass are not visible to the SubClass. The compiler does not complain about accessing these methods in the SubClass; however, the runtime ignores the fact that these are method calls on the LeafClass and instead makes those calls on the SubClass. Lines 4, 5, 6, and 7 exhibit normal behavior.

The following output shows what happens when we move the Main method to the SuperClass.

1. Public Leaf Class
2. Private Super Class
3. Protected Super Class
4. Public Sub Class
5. Private Super Class
6. Protected Super Class
7. Public Super Class
8. Private Super Class
9. Protected Super Class

Notice that except for the public methods, all private and protected method calls are made on the SuperClass even though the actual object instantiated and the reference assigned to are those of SubClass or LeafClass.

5.4.2 The base Keyword

A subclass can call a superclass's implementation using the base keyword. This is similar to Java's super keyword. The base keyword can be used anywhere inside the subclass to invoke a superclass's member. Only public and protected members can be accessed from the subclass. Listing 5.14 shows the use of the base keyword.

Listing 5.14 Using the base Keyword (C#)
using System;

public class SuperClass {

  public void PublicM () {
      Console.WriteLine("Public Super Class");
  }

  protected void ProtectedM () {
      Console.WriteLine("Protected Super Class");
  }

  private void PrivateM () {
      Console.WriteLine("Private Super Class");
  }
}

public class SubClass : SuperClass {

  public void PublicM () {
    base.PublicM();
    Console.WriteLine("Public Sub Class");
  }

  protected void ProtectedM () {
    base.ProtectedM();
    Console.WriteLine("Protected Sub Class");
  }

  private void PrivateM () {
    Console.WriteLine("Private Sub Class");
  }

  static void Main(string[] args) {
    SubClass sc = new SubClass();
    sc.PublicM();
    sc.ProtectedM();
    sc.PrivateM();
  }
}

The output of Listing 5.14 is as follows:

Public Super Class
Public Sub Class
Protected Super Class
Protected Sub Class
Private Sub Class

The output is fairly obvious. The first two lines are the result of the sc.PublicM() call. The next two lines result from the sc.ProtectedM() call, and the last line results from the sc.PrivateM() call. Note that the subclass cannot call the private method of its superclass using the base keyword.

Something interesting happens when we move the Main method (keeping everything else as is) from the subclass to the superclass (Listing 5.15).

Listing 5.15 The Main Method Moved to the Superclass (C#)
using System;

public class SuperClass {

  public void PublicM () {
      Console.WriteLine("Public Super Class");
  }

  protected void ProtectedM () {
      Console.WriteLine("Protected Super Class");
  }

  private void PrivateM () {
      Console.WriteLine("Private Super Class");
  }

  static void Main(string[] args) {
    SubClass sc = new SubClass();
    sc.PublicM();
    sc.ProtectedM();
    sc.PrivateM();
  }
}

public class SubClass : SuperClass {

  public void PublicM () {
    base.PublicM();
    Console.WriteLine("Public Sub Class");
  }

  protected void ProtectedM () {
    base.ProtectedM();
    Console.WriteLine("Protected Sub Class");
  }

  private void PrivateM () {
    Console.WriteLine("Private Sub Class");
  }

}

The output of Listing 5.15 is as follows:

Public Super Class
Public Sub Class
Protected Super Class
Private Super Class

Basically, except for public methods, all other method calls are made on the class in which the Main method is defined. Hence, in this output, in the last two lines the SuperClass protected and private methods get called even though the object instantiated and the references used are those of SubClass.

5.4.3 Static Methods and Inheritance

Static methods in C# cannot be marked virtual or override. As a result, C#'s virtual dispatching mechanism cannot be activated for static methods. This is similar to Java's treatment of static methods. The use of the base keyword in a static context is not allowed in C#. In Listing 5.16, the Main method calls the static method on the SubClass class.

Listing 5.16 Static Methods and Inheritance (C#)
using System;

public class SuperClass {

  public static void PublicTest() {
    Console.WriteLine("SuperClass PublicTest");
  }

  private static void PrivateTest() {
    Console.WriteLine("SuperClass PrivateTest");
  }

  protected static void ProtectedTest() {
    Console.WriteLine("SuperClass ProtectedTest");
  }

  static void Main(string[] args) {
    SubClass.PublicTest();
    SubClass.ProtectedTest();
    SubClass.PrivateTest();
  }
}

public class SubClass : SuperClass {

  public static void PublicTest() {
    Console.WriteLine("SubClass PublicTest");
  }
  private static void PrivateTest() {
    Console.WriteLine("SubClass PrivateTest");
  }

  protected static void ProtectedTest() {
    Console.WriteLine("SubClass ProtectedTest");
  }
}

The output of Listing 5.16 is as follows:

SubClass PublicTest
SuperClass ProtectedTest
SuperClass PrivateTest

Note that the Main method is in the SuperClass. The runtime makes the static method call on the SuperClass instead of the SubClass.

5.4.4 Abstract Methods and Inheritance

Abstract methods cannot be marked as static or virtual. Abstract methods must be in classes that are marked abstract. Listing 5.17 shows a minimal abstract class.

Listing 5.17 Abstract Methods (C#)
using System;

abstract public class SuperClass {
  public abstract void Test();
}

public class SubClass : SuperClass {
  public override void Test() {
    Console.WriteLine("SubClass method");
  }
  static void Main(string[] args) {
    SuperClass sc = new SubClass();
    sc.Test();
  }
}

Note that if we omit the override keyword in the subclass, the compiler will complain. Also, SubClass will have to be declared as abstract if it does not provide an implementation for the Test() method. Note that abstract methods must be either public or protected. Regardless of the position of the Main method in Listing 5.17, the SubClass implementation is called because there is no SuperClass implementation.


5.5 Casting

C#'s default nonvirtual methods require explicit activation of the virtual dispatching mechanism. There is a way, however, that you can force a method call on the actual object instantiated and not the reference without explicitly activating the virtual dispatching mechanism. Casting is very common in Java even though it sometimes is considered a performance bottleneck. Casting exists in C#, too, and it follows the same rules as in Java. Listing 5.18 shows an example of casting in action.

Listing 5.18 A Simple Casting Example (C#)
using System;

public class SuperClass {
  public void Test() {
    Console.WriteLine("Super class test");
  }
}
public class SubClass : SuperClass {
  public void Test() {
    Console.WriteLine("Sub class test");
  }
  static void Main(string[] args) {
    SuperClass sc = new SubClass();
    sc.Test();
    ((SubClass)sc).Test();
  }
}

The casting happens on the last line of the entry point (Main method):

((SubClass)sc).Test();

Listing 5.19 revisits casting using the SuperClass, SubClass, and LeafClass example.

Listing 5.19 Exploring Casting in C#
using System;

public class SuperClass {
  public void Test() {
    Console.WriteLine("Super class test");
  }
}
public class SubClass : SuperClass {
  public void Test() {
    Console.WriteLine("Sub class test");
  }
}
public class LeafClass : SubClass {
  public void Test() {
    Console.WriteLine("Leaf class test");
  }
  public static void Main(string[] args) {
    //Scenario 1
    LeafClass lc = new LeafClass();
    lc.Test();
    ((SubClass)lc).Test();
    ((SuperClass) lc).Test();

    //Scenario 2
    SubClass sc = new LeafClass();
    ((LeafClass)sc).Test();
    ((SubClass)sc).Test();
    ((SuperClass)sc).Test();

    //Scenario 3
    SubClass tc = new SubClass();
    ((SubClass)tc).Test();
    ((SuperClass)tc).Test();
    try {
         ((LeafClass)tc).Test();
    } catch (InvalidCastException e) {
        Console.WriteLine(e.StackTrace);
    }

  }
}

The output of scenario 1 of Listing 5.19 is as follows:

Leaf class test
Sub class test
Super class test

In this fairly obvious output, a LeafClass reference is assigned a LeafClass object. The method call is made on the LeafClass object the first time, and to get the SubClass and SuperClass implementation we simply cast the reference to the appropriate superclass. We created the most specific object and cast using the least specific classes. This is legal in both Java and C#.

Scenario 2 is similar to scenario 1 except that now the reference is that of the SubClass class; had we not cast the SubClass reference to a LeafClass, the LeafClass implementation would not have been called. We have a SubClass reference, and we are casting that to a more specific LeafClass. This works because the actual object instantiated was that of LeafClass. Scenario 2's output is as follows:

Leaf class test
Sub class test
Super class test

In the last scenario we create a SubClass object and assign it to a SubClass reference. The reference is then cast to SubClass and SuperClass, and the appropriate method gets called. However, when we try to cast to the LeafClass object we get an InvalidCastException. This is because we are trying to downcast to a more specific class (LeafClass) when the object created was that of SubClass. The compiler did not catch the problem because all the objects were in the same class hierarchy. However, the runtime throws the exception.

Because casting has a bad reputation in Java, it would be interesting to know how the performance of casting in C# matches up with virtual dispatching. Virtual dispatching would be slower than a simple method call on the reference, but would it be slower or faster than casting? Listing 5.20 explores the relative speed of using casting and the virtual dispatch mechanism to call the subclass method. In both cases the method is called on the object instantiated (SubClass). To put matters in perspective, we will also add a method call made only to the reference SuperClass.

Listing 5.20 Comparing the Speed of Casting a Simple Reference Method Call versus Virtual Dispatching (C#)
using System;

public class SuperClass {
  public virtual void Test() {
  }
  public void Test2() {
  }
  public void Test3() {
  }
}

public class SubClass : SuperClass {

  public override void Test() {
  }
  public void Test2() {
  }
  static void Main(string[] args) {
    int COUNT = 100000000;

    SuperClass sc = new SubClass();
    long 11 = Environment.TickCount;
    for (int i = 0; i < COUNT; ++i) {
      sc.Test3();
    }
    long 12 = Environment.TickCount;
    Console.WriteLine(12-11);

    11 = Environment.TickCount;
    for (int i = 0; i < COUNT; ++i) {
      sc.Test();
    }
    12 = Environment.TickCount;
    Console.WriteLine(12-11);

    11 = Environment.TickCount;
    for (int i = 0; i < COUNT; ++i) {
      ((SubClass)sc).Test2();
    }
    12 = Environment.TickCount;
    Console.WriteLine(12-11);
  }
}

A sample run of Listing 5.20 gives the following output:

2534
2664
3635

The reference-based method call is faster than virtual dispatching, which in turn is faster than casting. In deeper class hierarchies (where subclasses exist at several levels below the superclass), this difference would be accentuated.


5.6 Composition versus Inheritance

Although OOP languages offer code reuse through inheritance, inheritance somewhat reduces the flexibility of the designed classes. This is especially true in languages such as Java and C#, which allow only single root inheritance (classes can extend only one class). You must take care in deciding which class to inherit from. Also, inheritance does not easily lend itself to versioning. Composition is the technique of building on existing implementations rather than inheriting from them. To use composition, you place a reference to the implementation inside your implementation.

Suppose you want a special kind of data structure, such as an LRU (least recently used) cache. The System.Collections.ArrayList class comes to mind. But should you extend it? Or should you use it internally to achieve the list-based functionality? Extending the ArrayList through inheritance means that your cache must be a list-based structure. So in the future if you want to extend it to an Array or a Map, you will have to rewrite the cache class logic. However, wrapping the ArrayList and providing wrapper methods that in turn make method calls to the ArrayList would insulate the end user from the internal implementation of your cache class.

Using inheritance, you could create the LRU class as follows:

class LRUCache : ArrayList {
  public object Add(object o) {
     base.Add(o);
  }
}

Using composition, the LRU class looks like this:

class LRUCache {

  ArrayList list;

  public object Add(object o) {
    list.Add(o);
  }
}

Composition also gives you better control over the API you want exposed for your class. Extending an ArrayList would mean providing implementations for all the ArrayList methods. Instead, if you wrapped the ArrayList, you could expose the methods of your API in installments when the need arose. (For example, you don't provide a Remove method on your cache initially but instead provide it later when you work out the details of implementing it.)


5.7 Summary

Inheritance is a powerful concept and should be used whenever appropriate. However, you should design class hierarchies with caution because C# allows you to extend only one class. Prefer composition over inheritance because composition allows for greater flexibility in switching your actual implementation and insulating the end users of your class from changes to the class's internals.

Here are the key concepts you should take away from this chapter:

  • In C#, methods are nonvirtual by default. In Java, all methods are virtual by default. This means that in C#, virtual dispatching is not automatically turned on; therefore, method calls are always made on the reference instead of the object instantiated. To achieve Java-like virtual dispatching, you must use the virtual and override keywords in the right places.

  • The method parameters and their conversion rules also influence which method gets called at runtime.

  • Subclasses can access the protected and public members of their superclass. Subclasses cannot access the private members of the superclass.

  • A superclass can access all the members of a subclass; that is, this access won't cause a compile-time error. However, the runtime will invoke only the corresponding superclass methods even when the object instantiated and the reference used are those of the subclass.

  • Public and protected static methods can be inherited. Private static methods cannot be inherited.

  • The base keyword can be used to call a superclass method. Only protected and public members can be accessed using the base keyword. A static method cannot use the base keyword.

  • Abstract methods can be contained only in an abstract class. A nonabstract subclass that extends an abstract class must override the corresponding abstract method of the superclass.

  • A base-class reference can be cast to a derived class reference only when the object instantiated is that of the derived class:

    Base b = new Derived();
((Derived) b).Test();

  • A derived class reference can be cast to any of its superclass references:

    Derived d = new Derived();
((Base)d).Test()

  • In general, a direct nonvirtual method call on a reference is faster than a virtual call, which in turn is faster than casting. To achieve Java-like automatic virtual dispatching, you should use the virtual or override modifier in C#. Casting should not be used as a substitute for virtual dispatching.


Chapter 6. Implementing Interfaces

An interface defines a protocol of behavior that can be implemented by any class anywhere in the class hierarchy. An interface defines a set of methods but does not implement them. A class that implements the interface agrees to implement all the methods defined in the interface, thereby agreeing to certain behavior.

Because an interface is simply a list of unimplemented methods you might wonder how an interface differs from an abstract class. The differences are significant:

  • An interface cannot implement any methods, whereas an abstract class can.

  • A class can implement many interfaces but can have only one superclass.

  • An interface is not part of the class hierarchy.

  • Unrelated classes can implement the same interface.

Let's look at C# interfaces more closely.


6.1 Basic Interfaces

The word "interface" evokes the image of a boundary where two independent systems interact. In software systems, an interface usually means a contract or a set of rules that systems abide by in order to interact with each other. A person driving a car can be considered two systems (the person and the car) interacting with each other. The person driving the car knows that all cars have a common set of features (steering wheel, gear train, wheels, and so on), and to use the car the driver must interact with those features. Those basic features can be thought of as a contract every car abides by in order to interact with humans. All cars implement an interface that drivers are familiar with.

From an object-oriented programming perspective, an interface represents a collection of methods that an object implements so that other objects can call those methods without knowing their implementation details. A person driving a Volvo or a BMW does not need to know the inner workings of the gear train. The driver simply interacts with the standard gear interface provided by the two cars.

An interface is easy to define. The following ICar interface provides three methods:

interface ICar {
  string DriveTrain();
  string Style();
  string Engine();
}

The Volvo and BMW classes implement the ICar interface and therefore provide implementations for those three methods:

class Volvo : ICar {
  public string DriveTrain() { return Go2Database("DriveTrain"); }
  public string Style() { return Go2Database("Style"); }
  public string Engine() { return Go2Database("Engine"); }
  private string Go2Database (string property) {
  }
}

class BMW: ICar {
  public string DriveTrain() { return ReadFromFile ("DriveTrain"); }
  public string Style() { return ReadFromFile ("Style"); }
  public string Engine() { return ReadFromFile ("Engine"); }
  private string ReadFromFile (string property) {
  }
}

Objects can interact with the BMW and Volvo objects through the ICar interface without knowing what these objects do to retrieve the information returned by the three methods. BMW objects read their properties from a disk file, whereas Volvo objects read their properties from the database. But no matter what the individual objects do to retrieve the information, the caller of these three methods gets a string in return. The caller is aware only of the method signature and not its details. The ICar interface thus facilitates the hiding of implementation details of the Volvo and BMW objects.

An object can implement multiple interfaces and thereby adopt different behaviors. This ability to implement multiple interfaces makes up for the lack of multiple class inheritance in C# and Java (in these languages, a class can inherit from only one class). Although interfaces have only declarations associated with them and not actual code, their existence is critical to designing loosely coupled object-oriented systems.

To understand coupling, consider the following class:

class CarFactory {
  public static ICar createCar (string type) {
    if (type.Equals("Volvo") {
      return new Volvo();
    } else if (type.Equals("BMW") {
      return new BMW();
  }
}
  public static Volvo createVolvo() {
    return new Volvo();
  }
  public static BMW createBMW() {
    return new BMW();
  }
}

The CarFactory class creates cars based on a string literal passed to the createCar() method. The method instantiates an object and returns a reference to the ICar interface. The createVolvo() and createBMW() methods, on the other hand, return specific classes. The caller of the createCar() method does not need to know about the type of object returned. The caller of the createVolvo() and createBMW() methods must be aware of the Volvo and the BMW class, respectively. Changes to the Volvo and BMW classes cannot be made without taking into consideration what would happen to the callers of createVolvo() and createBMW().

What if the caller of these methods called nonexistent methods on the Volvo and BMW objects? By exposing the Volvo and BMW classes to the user of the CarFactory class, we have tightly coupled that user to the Volvo and BMW classes. On the other hand, the createCar() method returns an interface, and therefore the caller deals only with the interface. You can create cars of different makes and models without necessarily making the user of the CarFactory class aware of all these classes; you make the user deal only with the interface instead of the actual class. This decouples the user of the CarFactory class from the inner workings of the CarFactory.

6.1.1 Differences between C# and Java Interfaces

C# and Java don't differ much in their concepts of interfaces. In both languages, an object can implement multiple interfaces, and interfaces can extend each other. There are, however, some aspects of C# interfaces that are different from Java interfaces. This section explores those differences.

Listings 6.1 and 6.2 show a sample interface and its implementing class.

Listing 6.1 The ILife Interface (C#)
namespace Interfaces {
  interface ILife {
    void Birth();
    void Death();
    void Happy();
    void Sad();
  }
}
Listing 6.2 Person Implementing the ILife Interface (C#)
using System;
namespace Interfaces {
  class Person : ILife {
       string name;

       public Person(string name) {
         this.name = name;
         Birth();
         Happy();
       }

    public void Birth() {
      Console.WriteLine("Birth of {0}",this.name);
    }

  public void Death() {
    Sad();
      Console.WriteLine("Death of {0}",this.name);
    }

  public void Happy() {
      Console.WriteLine("{0} is happy",this.name);
    }
  public void Sad() {
      Console.WriteLine("{0} is sad",this.name);
    }

  static void Main(string[] args) {
         Person per = new Person("John");
         per.Death();
    }

  }
}

Following is the output of running Listings 6.1 and 6.2 in combination:

Birth of John
John is happy
John is sad
Death of John

Listing 6.3 shows the Java equivalent of these classes.

Listing 6.3 Java Equivalent of the ILife Interface
package test;
interface Life {
  void birth();
  void death();
  void happy();
  void sad();
}

package test;
public class Person implements Life{

  String name;

  public Person(String name) {
    this.name = name;
    birth();
    happy();
  }

  public void birth() {
    System.out.println("Birth of " ame);
  }
  public void death() {
    sad();
    System.out.println("Death of " ame);
  }
  public void happy() {
    System.out.println(name+" is happy");
  }
  public void sad() {
    System.out.println(name+" is sad");
  }
  static final void main(String[] args) {
    Person per = new Person("John");
    per.death();
  }
}

For simplicity, in C# we use the same name for the namespace of the interface and the implementing class. Similarly, in Java both the class and the interface are in the same default package. Table 6.1 compares the C# and Java code (keep in mind that the interface and the class are in the same namespace or package).

Let's test whether namespace changes in C# and package changes in Java affect the type of access modifiers allowed on the classes, the interfaces, and their methods. To do this in C#, you remove the Person.cs file from your current project. Click on the Project menu. Highlight the Person.cs file by clicking on its tab. Click on the Exclude From Project menu item. Now click on Add Component. Choose the Console File option from the pop-up. Name the file Alien.cs. Listing 6.4 shows the contents of this Alien.cs file.

Table 6.1. Code Comparison between C# and Java

C#

Java

The ILife interface and the Person class are in the same namespace. Note that the ILife interface must have either no access modifier or the public modifier.

The Life interface and the Person class are in the same package. Note that the Life interface must have either no access modifier or the public modifier.

The Birth() and Death() methods of the interface cannot have any access modifiers.

The birth() and death() methods of the interface are public and abstract.

The Birth() and Death() methods of the implementing class (Person) either must be public or must have explicit interface declaration (we discuss this feature of C# later in this chapter).

The birth() and death() methods of the implementing class (Person) must be public.

Listing 6.4 Alien.cs, a Class Implementing an Interface from a Different Namespace (C#)
using System;
using Interfaces;

namespace Interfaces1 {
  class Alien : ILife {
    string name;
    public Alien(string name){
      this.name = name;
      Birth();
    }

    public void Birth() {
      Console.WriteLine("Birth of {0}",this.name);
    }

    public void Death() {
      Console.WriteLine("Death of {0}",this.name);
    }
    public void Happy() {
      Console.WriteLine("{0} is happy",this.name);
    }
    public void Sad() {
      Console.WriteLine("{0} is sad",this.name);
    }

    static void Main(string[] args) {
      Alien per = new Alien("Theta-666");
      per.Death();
    }
  }
}

Running the combination of Listing 6.4 and Listing 6.1 results in the following output:

Birth of Theta-666
Death of Theta-666

Thus, in C# having the interface and the implementing class in different namespaces does not change the rules about access modifiers on the class and interface methods. In Java, however, having the interfaces and the implementing class in different packages requires that the interface be public if it is to be implemented by the class.


6.2 Multiple Interfaces and Explicit Interface Declaration

A class can implement multiple interfaces in C# and Java. For example, the Person class can also implement an IEmotion interface (Listing 6.5).

Listing 6.5 The IEmotion Interface (C#)
namespace Interfaces {
  interface IEmotion {
    void Happy();
    void Sad();
    void Angry();
    void Love();
  }
}

Let's create another class similar to the Person class (call it Man) and let it implement the two interfaces. Note that both IEmotion and ILife have a Happy() and a Sad() method. One way to implement the class is with C#, as in Listing 6.6.

Listing 6.6 Implementing Multiple Interfaces with Method Name Conflicts (C#)
using System;

namespace Interfaces {
  class Man : ILife, IEmotion {
    string name;

    public Man(string name) {
      this.name = name;
    }

    public void Birth() {
      Console.WriteLine("Birth of {0}",this.name);
    }

    public void Death() {
      Console.WriteLine("Death of {0}",this.name);
    }
    public void Happy() {
      Console.WriteLine("{0} is happy",this.name);
    }
    public void Sad() {
      Console.WriteLine("{0} is sad",this.name);
    }
    public void Angry() {
      Console.WriteLine("{0} is angry",this.name);
    }
    static void Main(string[] args) {

      Man per = new Man("John");
      per.Happy();
      ILife life = (ILife)per;
      life.Happy();
      IEmotion emo = (IEmotion)per;
      emo.Happy();
    }
  }
}

Listing 6.6 prints John is happy three times on the console. This is fine as long as you want the same implementation for the Happy method of the ILife interface and the IEmotion interface. But if you want different implementations for the Happy and Sad methods of these two interfaces, you must explicitly specify the interface in the Man class (Listing 6.7).

Listing 6.7 Explicit Interface Implementation (C#)
using System;
namespace Interfaces {
  class Man : ILife, IEmotion {

    protected string name;

    public Man(string name) {
      this.name = name;
    }
    public Man() {
    }

    public void Birth() {
      Console.WriteLine("Birth of {0}",this.name);
    }

    public void Death() {
      Console.WriteLine("Death of {0}",this.name);
    }
    void ILife.Happy() {
      Console.WriteLine("{0} is happy",this.name);
    }
    void ILife.Sad() {
      Console.WriteLine("{0} is sad",this.name);
    }
    void IEmotion.Happy() {
      Console.WriteLine("{0} has happy emotions",this.name);
    }
    void IEmotion.Sad() {
      Console.WriteLine("{0} has sad emotions",this.name);
    }
    public void Angry() {
      Console.WriteLine("{0} is angry",this.name);
    }

    static void Main(string[] args) {

      Man per = new Man("John");
      ILife life = (ILife)per;
      life.Happy();
      IEmotion emo = (IEmotion)per;
      emo.Happy();
    }

  }
}

This technique of stating the interface name before the method name is called explicit interface declaration. There are no access modifiers on a method declared with explicit interfaces. Also, in the Main method there is no calling of the Happy method from the class itself (per.Happy() is missing). This is because of the explicit interface implementation; calling the Happy() or Sad() method on the object without casting the object to the relevant interfaces is no longer valid. For the per.Happy() call to be valid, there would have to be a separate public void Happy() method on the Man class, which is different from the one defined in the two interfaces. The fact that the two Happy and Sad methods are prefixed by their interface names indicates that these methods are obtained by implementing the interfaces. If we remove these prefixes and remove the interfaces, the Happy() and Sad() methods would be treated like any other method of the Man class (that is, specific to the Man class).

The output of Listing 6.7 is as follows:

John is happy
John has happy emotions

Java does not support explicit interface declaration. Therefore, calling the happy() method returns the same result whether you call it on the class, the Life interface, or the Emotion interface (Listing 6.8). And it is not possible to have different implementations of the three method calls. In contrast, with C#, calling the Happy() method on the class, the ILife interface, and the IEmotion interface returns three different results. Listing 6.7 has two Happy() methods: one defined for the ILife interface, and another for the IEmotion interface. No method is defined on the class itself. Such a method would be easily defined by declaring it as public void Happy(). Then calls to the three Happy() methods would all give a different result. Whether this flexibility is confusing, we leave to you to decide.

Listing 6.8 Java Equivalent of the Multiple Interface Implementation
interface Life {
  void birth();
  void death();
  void happy();
  void sad();

}

interface Emotion {
  void happy();
  void sad();
}
public class Man implements Life, Emotion{

  String name;
  public Man(String name) {
    this.name = name;
  }
  public Man() {}
  public void birth() {
    System.out.println("Birth of " ame);
  }
  public void death() {
    System.out.println("Death of " ame);
  }
  public void happy() {
    System.out.println(name+ " is happy");
  }
  public void sad() {
    System.out.println(name+ " is sad");
  }

  static final void main(String[] args) {

    Man per = new Man ("John");
    per.happy();
    Life life = (Life)per;
    life.happy();
    Emotion emo = (Emotion)per;
    emo.happy();
  }
}


6.3 Inheritance in Interfaces

In both C# and Java, a subclass inherits all the implemented interfaces and the corresponding implementation of its superclass. Listing 6.9 produces the same output as Listing 6.7. The BusyMan subclasses the Man class and inherits all the interfaces of the Man class.

Listing 6.9 Subclass BusyMan of Class Man (C#)
using System;
namespace Interfaces {
  class BusyMan : Man {

    public BusyMan (string name) {
      this.name = name;
    }

    static void Main(string[] args) {
      BusyMan per = new BusyMan("John");
      ILife life = (ILife)per;
      life.Happy();
      IEmotion emo = (IEmotion)per;
      emo.Happy();
    }

  }
}

To make the subclass implement specialized behavior for the ILife.Happy() and ILife.Sad() methods, it would have to implement ILife and have those implementations in those methods, as shown in Listing 6.10.

Listing 6.10 ILife Interface Implementation (C#)
using System;
namespace Interfaces {
  class BusyMan : Man, ILife {

    public BusyMan (string name) {
      this.name = name;
    }
    void ILife.Happy() {
      Console.WriteLine("Happy {0} ",name);
    }
    void ILife.Sad() {
      Console.WriteLine("Sad {0} ",name);
    }
    static void Main(string[] args) {
      BusyMan per = new BusyMan("John");
      ILife life = (ILife)per;
      life.Happy();
      IEmotion emo = (IEmotion)per;
      emo.Happy();
    }

  }
}

As in C#, in Java to get specific behavior for the happy() and sad() methods for the BusyMan class, BusyMan would have to implement the Life interface and implement those methods (see Listing 6.11).

Listing 6.11 BusyMan Extending the Man Class in Java
public class BusyMan extends Man {
  public BusyMan(String name) {
    this.name = name;
  }
  static final void main(String[] args) {
    BusyMan per = new BusyMan("John");
    per.happy();
    Life life = (Life)per;
    life.happy();
    Emotion emo = (Emotion)per;
    emo.happy();
  }

}

In C#, just as in Java, interfaces can extend other interfaces. Listing 6.12 shows an IRichLife interface that extends the ILife interface.

Listing 6.12 IRichLife Extending the ILife Interface (C#)
namespace Interfaces {
  interface IRichLife : ILife {
    void BuyMansion();
  }
}

Let's write a new class called RichMan that implements IRichLife. Listing 6.13 shows the code for the RichMan class. Note that all the methods are prefixed with the interface name. This is a good C# coding practice because it eliminates any ambiguity regarding which interface the method came from.

Listing 6.13 RichMan Implementing the IRichLife Interface (C#)
using System;
namespace Interfaces {
  class RichMan : IRichLife {

    protected string name;
    public RichMan(string name) {
      this.name = name;
    }

    void ILife.Birth() {
      Console.WriteLine("Birth of {0}",this.name);
    }

    void ILife.Death() {
      Console.WriteLine("Death of {0}",this.name);
    }
    void ILife.Happy() {
      Console.WriteLine("{0} is happy",this.name);
    }
    void ILife.Sad() {
      Console.WriteLine("{0} is sad",this.name);
    }
    void IRichLife.BuyMansion() {
      Console.WriteLine("{0} buys a mansion",this.name);
    }

    static void Main(string[] args) {

      RichMan per = new RichMan("John");
      ILife life = (ILife)per;
      life.Happy();
      IRichLife richlife = (IRichLife)per;
                              richlife.Happy();
      richlife.BuyMansion();
    }
  }
}

The output of Listing 6.13 is as follows:

John is happy
John is happy
John buys a mansion

When you specify an explicit interface declaration (such as the Interface.Method name in the method signature), the method must be a member of the interface. For the interface IRichLife, the only member is the BuyMansion() method. The Sad(), Happy(), Birth(), and Death() methods are members of the ILife interface and not the IRichLife interface. Hence, a declaration such as IRichLife.Happy() would not be compiled.

Note that although you cannot explicitly declare the IRichLife.Happy() method, the IRichLife interface does inherit that method from the ILife interface. In fact, in the Main method the richlife.Happy() method is as valid as the life.Happy() call, and both print output.


6.4 The as Operator

Listing 6.14 was created from Listing 6.13. The main() method has been changed to demonstrate casting in interfaces. You will notice that for each iteration the array object is checked to determine whether it's of type IRichLife. If it is of type IRichLife, it is then cast to that interface.

As you can see, the code checks the type of the object twice. The first time is in the operator, and the second is during the cast. Doing this several times in a loop is wasteful. C# has the as operator, which lets you reduce the number of type checks.

Listing 6.14 Casting in Interfaces (C#)
using System;
namespace Interfaces {
  class RichMan : IRichLife {
    protected string name;

    public RichMan(string name) {
      this.name = name;
    }
    public void Birth() {
      Console.WriteLine("Birth of {0}",this.name);
    }
    public void Death() {
      Console.WriteLine("Death of {0}",this.name);
    }
    public void Happy() {
      Console.WriteLine("{0} is happy",this.name);
    }
    public void Sad() {
      Console.WriteLine("{0} is sad",this.name);
    }
    public void BuyMansion() {
      Console.WriteLine("{0} buys a mansion",this.name);
    }

    static void Main(string[] args) {
      RichMan[] riches = new RichMan[3];
      riches[0] = new RichMan("Bill Gates");
      riches[1] = new RichMan("Larry Ellison");
      riches[2] = new RichMan("Scott McNealy");
      foreach (RichMan d in riches) {
        if (d is ILife) {
          ((IRichLife)d).Happy();
        }
      }
    }

  }
}

Using the as operator, Listing 6.15 shows that the type checking has been reduced to only once instead of twice.

Listing 6.15 Using the as Operator (C#)
using System;
namespace Interfaces {
  class RichMan : IRichLife {
    protected string name;

    public RichMan(string name) {
      this.name = name;
    }
    public void Birth() {
      Console.WriteLine("Birth of {0}",this.name);
    }
    public void Death() {
      Console.WriteLine("Death of {0}",this.name);
    }
    public void Happy() {
      Console.WriteLine("{0} is happy",this.name);
    }
    public void Sad() {
      Console.WriteLine("{0} is sad",this.name);
    }
    public void BuyMansion() {
      Console.WriteLine("{0} buys a mansion",this.name);
    }

    static void Main(string[] args) {
      RichMan[] riches = new RichMan[3];
      riches[0] = new RichMan("Bill Gates");
      riches[1] = new RichMan("Larry Ellison");
      riches[2] = new RichMan("Scott McNealy");
      foreach (RichMan d in riches) {
          IRichLife life = d as IRichLife;
        if (d != null ) {
          life.Happy();
        }
      }
    }

  }
}

There is no as operator in Java, and therefore you must do the type checking twice in the for loop.


6.5 Summary

As with Java interfaces, a C# class can implement multiple interfaces, and C# interfaces can extend each other. However, there are some differences in how the two languages treat the implementation of interfaces. Here are the key points to remember from this chapter:

  • C# does not care which access modifiers are on an interface, even if the interface and the implementing class are in different namespaces. In Java, the interface must be public if it is to be implemented by a class in a different package.

  • A C# class implementing an interface method can have the method declared as public or explicitly declared using C# explicit interface declaration.

  • Through C#'s explicit interface declaration, a class can implement interfaces having common method names, and there can be different implementations for each of the same-named methods. It is the responsibility of the method's caller to indicate the desired implementation by casting the object to the appropriate interface.

  • Explicit interface declaration works only if the method is a member of the interface (that is, the method is directly defined in that interface and not obtained through inheritance of a parent interface).

  • C# provides the as operator to avoid casting.


Chapter 7. Data Types

Data types constitute the building blocks of any programming language. This chapter discusses the basic C# data types in detail and compares them to Java data types.

Recall that both C# and Java are strongly typed languages, meaning that all the variable declarations should identify the data type of the variable. C# has three data types: value types, reference types, and pointer types. Value types store data, whereas reference types store references to the actual data, and the pointer types can be used only in unsafe mode (more about unsafe mode later in the chapter). In Java, there is a similar demarcation between the data types; the value types are referred to as primitives, and reference types are references to arrays, classes, or interfaces. There is no unsafe mode of operation in Java, and hence there is no pointer type.

We will briefly introduce the two data types for both languages and then discuss the C# data types in detail.


7.1 The Java Value Type

The Java value type is also known as the primitive data type. Table 7.1 shows the Java primitives.

Variables can be declared as a primitive type. Depending on the scope where they are declared, they must be initialized. For example, you must initialize local method variables as shown here:

int age = 10;
long population = 100000L;
float price = 12.45f;
double count = 6.45d;

Table 7.1. Java Value Types (Primitives)

Keyword

Description

Size/Format

Byte

Byte-length integer

8 bits

Short

Short integer

16 bits

Int

Integer

32 bits

Long

Long integer

64 bits

Float

Single-precision floating point

32-bit IEEE 754

Double

Double-precision floating point

64-bit IEEE 754

Char

A single character

16-bit Unicode character

Boolean

A Boolean value

True or false

Suffixing a float literal with f or F or a long literal with l or L improves the clarity of the code, although it is not required. Primitive data types are passed by value, so changes made to the primitive's value inside a method are local to that method and are not retained after the method call completes.


7.2 The Java Reference Type

The value of a reference type variable, in contrast to that of a primitive type, is a reference to (an address of) the value or set of values represented by the variable. In other languages, a reference is called a pointer or a memory address. Unlike other languages, Java does not support the explicit use of addresses; instead, you use the variable's name. This pointer can point to an array, a class, or an interface. Reference variables passed as method parameters are passed by value; that is, a copy of the reference parameter is made and then passed to the method. Both the original reference and the copied reference point to the same object in memory, and therefore changes made to that object through the reference are retained even after the method call completes.


7.3 The C# Value Type

The C# value type stores data. The assignment to a variable of a value type creates a copy of the assigned value. Following are some of the common features of C# value types:

  • Unlike the Java primitive, all boxed C# value types are derived from the System.Object class.

  • A value type cannot contain the null value.

  • Each value type has a default constructor that initializes the default value of the type.

  • A value type cannot inherit from another value type.

  • The memory allocated for value types is usually allocated on the stack; this contrasts with reference types, for which the memory is allocated on the heap.

  • You can create new C# value types. In Java, the primitives are a fixed set.

C# defines two key-value types: struct types and enum types.

7.3.1 The struct Type

C and C++ programmers will be pleased to know that unlike Java, which got rid of structs, C# has chosen to use them. A struct type offers a lightweight construct for representing simple concepts that do not require a class representation. A struct type is a value type that can contain constructors, constants, fields, methods, properties, indexers, operators, and nested types. The following rules are worth noting about a C# struct type:

  • Unlike C++, C# does not let you declare a class using the keyword struct. In C#, classes are semantically different from structs. A struct is a value type, whereas a class is a reference type.

  • A struct type can implement an interface.

  • A struct type comes with a default constructor, and therefore you should not provide one for it.

  • You should not initialize the instance fields of a struct type.

  • A struct is usually created using the new operator. Unlike classes, struct types can be instantiated without the new operator. If you do not use new, the fields will remain unassigned, and the object cannot be used until all the fields are initialized.

  • A struct type is more lightweight (in terms of memory consumption) than a class and so should be used to represent simple objects.

Listing 7.1 shows a simple struct type.

Listing 7.1 A Simple struct Type (C#)
using System;
public struct Dimension {
  public int length,width,height;

  public Dimension (int length, int width, int height) {
    this.length = length;
    this.width = width;
    this.height = height;
  }
  public override string ToString() {
      return "L ="+length+" W = "+width+" H="+height;
  }
  public int Volume() { return (length * width * height); }
}

class MainClass {
  public static void Main() {

    Dimension dim = new Dimension();
    Dimension dim1 = new Dimension (10,10, 10);
    Dimension dim2 = new Dimension (10,10,10);

    Console.WriteLine (dim);
    Console.WriteLine ("dim1.Equals(dim2) "+dim1.Equals(dim2));

    Dimension dim3;
    dim3.height = 10;
    dim3.width = 10;
    dim3.length = 10;
    Console.WriteLine (dim3.Volume());
  }
}

Here is the output of Listing 7.1:

L=0 W = 0 H=0
dim1.Equals(dim2) True
1000

Note that the == operator cannot be used for comparing two structs.

Listing 7.1 shows a custom struct type. The C# language itself uses several built-in structs to represent the simple value types (which you would recognize as primitives in Java). Section 7.4 discusses the built-in simple types.

Structs may seem similar to classes, but there are important differences that you should be aware of. First, classes are reference types, and structs are value types. By using structs, you can create objects that behave like the built-in types and also enjoy their benefits.

When you call the new operator on a class, it will be allocated on the heap. However, when you instantiate a struct, it gets created on the stack. This yields performance gains. For example, the use of a struct rather than a class for a Dimension can make a large difference in the number of memory allocations performed at runtime. The following program creates and initializes an array of 1,000 dimensions. With Dimension implemented as a class, 1,001 separate objects are instantiated—one for the array, and one each for the 1,000 elements.

class Dimension
{
  public int length, width, height;
  public Point(int length, int width, int height) {
    this.length = length;
    this.width = width;
    this.height = height;
  }
}

class Test
{
  static void Main() {
    Dimension[] dimensions = new Dimension [1000];
    for (int i = 0; i < 1000; i++)
      dimension[i] = new Dimension (i, i*i);
  }
}

If Dimension is instead implemented as a struct then only one object is instantiated: the one for the array. The Dimension instances are allocated in-line within the array.

Using structs instead of classes can also make an application run slower or take up more memory. Passing a struct instance as a value parameter creates a copy of the struct; and because structs are passed by value, you could be making copies of 1,000 structs every time you call a method that takes an array of structs.

Unless you need reference-type semantics, the system may more efficiently handle a class that is smaller than 16 bytes as a struct.

7.3.2 The enum Type

Again, C and C++ programmers will note that C#, unlike Java, has retained the concept of an enum. The enum keyword is used to declare an enumeration, a distinct type consisting of a set of named constants called the enumerator list. Every enumeration type has an underlying type, which can be any integral type except char. The default underlying type of the enumeration elements is int. By default, the first enumerator has the value 0, and the value of each successive enumerator is increased by 1. For example:

enum Directions {North, South, East, West };

In this enumeration, North is 0, South is 1, East is 2, and so forth. Enumerators can have initializers to override the default values. For example:

enum Directions {North = 2, South, East, West };

In this enumeration, the sequence of elements is forced to start from 2 instead of 0.

An explicit cast is needed to convert from the enum type to an integral type. For example, the following statement assigns the enumerator Sun to a variable of the type int using a cast to convert from enum to int:

int x = (int) Directions.South;

Listing 7.2 shows a simple enum example.

Listing 7.2 An enum Example (C#)
using System;
public class EnumTest {
  enum Directions {North, South, West, East};
  enum Toxicity :long {Max = 1345456L, Min = 346664L};
  public static void Main() {
    int a = (int) Directions.North;
    int b = (int) Directions.South;
    Console.WriteLine("North= {0}", a);
    Console.WriteLine("South = {0}", b);

    //Explicit cast
    long x = (long) Toxicity.Max;
    long y = (long) Toxicity.Min;
    Console.WriteLine("Max = {0}", x);
    Console.WriteLine("Min = {0}", y);
  }
}

Listing 7.2 shows an enumeration Toxicity whose base type is a long. In both cases—when the enum uses the default int base type and when it uses the custom base type—an explicit cast is required when the enum assigns a value to a variable.

Here is the output of Listing 7.2:

North = 0
South = 1
Max = 1345456
Min = 346664


7.4 The Built-in Value Types

As mentioned earlier, all C# types inherit from the System.Object class, and therefore most of the built-in types that we will discuss have a representation in the System namespace. For example, int is a keyword that is used to represent an integer value type, and it is also represented by the System.Int32 class. The simple built-in types can be further classified as integral types, floating-point types, decimal types, and Boolean types.

Table 7.2. Built-in Integral Types (C#)

Type (Keyword)

Size

.NET Framework Type

sbyte

Signed 8-bit integer

System.Sbyte

byte

Unsigned 8-bit integer

System.Byte

char

Unicode 16-bit character

System.Char

short

Signed 16-bit integer

System.Int16

ushort

Unsigned 16-bit integer

System.Uint16

int

Signed 32-bit integer

System.Int32

uint

Unsigned 32-bit integer

System.Uint32

Long

Signed 64-bit integer

System.Int64

ulong

Unsigned 64-bit integer

System.Uint64

7.4.1 Built-in Integral Value Types

Table 7.2 shows the built-in integral types. The keyword is given along with the corresponding .NET type.

You can get the range of each of these integral types by using the MaxValue and MinValue properties on their corresponding .NET Framework class types. Consider this code:

Console.WriteLine(System.Int32.MaxValue);
Console.WriteLine(System.Int32.MinValue);

It yields the following output:

2147483647
-22147483648

Because each of the integral data type keywords has a representation as a class in the System namespace, you can actually use the new operator on these built-in types. Therefore, the following two assignments are equivalent:

int i = 0;
int i = new int();

The value assigned to the data type in the second statement is the default value of the int data type, which is 0. The next few sections explore each of the keywords shown in Table 7.2.

The sbyte and byte Keywords

The range of byte is 0 through 255, and the range of sbyte is –128 through 127. The default value for both sbyte and byte is 0. You can declare an sbyte and a byte variable this way:

byte b = new byte();
sbyte s = new sbyte();

Here, we use the default constructor of the byte and sbyte keywords. The value assigned to b and s will be the default value, which is 0. You can also use the traditional approach to assign literals to the integral variables:

sbyte mySbyte = 127;
byte mybyte = 8;

Here, the integer literal on the right will be implicitly converted to the appropriate data type on the left as long as the literal is within the (min, max) range of the data type of the left side. Because both 127 and 8 are within the (min, max) range of sbyte and byte, respectively, the implicit conversion will be successful; otherwise, a compilation error would occur. This simplifies declaring variables of type sbyte and byte because you need not explicitly cast the literals to the data type on the left. In other words, the following two statements are redundant:

sbyte mySbyte = (sbyte) 127;
byte mybyte = (byte) 8;

However, if you are passing an int literal to a method whose signature requires an sbyte or a byte, an explicit cast is required. For example, the following two overloaded methods differ only in the arguments they take:

public static void MyMethod (byte b);
public static void MyMethod (sbyte b);

To make sure the appropriate method gets called, an explicit cast of the literal is required:

MyMethod ((byte) 5);
MyMethod ((sbyte) 5);

Table 7.3. Valid Implicit Conversions for the sbyte and byte Data Types (C#)

Conversion From

Conversion To

byte

short, int, long, float, double, or decimal

sbyte

short, ushort, int, uint, long, ulong, float, double, or decimal

If the explicit cast is not done, then rules of implicit conversion will take precedence. In this case, calling MyMethod(5) will call the method that takes the sbyte method argument.

Table 7.3 shows some valid implicit conversions.

You cannot implicitly convert nonliteral numeric types of larger storage size to sbyte or byte. For example:

byte x = 10, y = 10;
byte z = x + y; // Error: conversion from int to byte

The assignment statement will produce a compilation error because the arithmetic expression on the right side of the assignment operator evaluates to int by default.

The short and ushort Keywords

The C# short ranges from –32,768 through 32,767, and ushort ranges from 0 through 65,535. The default value for both short and ushort is 0. You can declare these types this way:

short x = 334;
ushort y = 10;
short s = new short();
ushort s = new ushort();

Again, there is an implicit conversion of the integer literal on the right to the short and ushort because they are within the range of the data types. When you create a short or ushort using the default constructor, the default value is assigned to the variable. You cannot implicitly convert a nonliteral data type with larger storage type to short or ushort. Therefore, the following assignment statement will produce a compilation error because the arithmetic expression on the right side of the assignment operator evaluates to int by default:

short x =5;
short y = 6;
short z = x + y; //Errors on compiling.

It is possible, though, to use the following statements, where the destination variable has the same or a larger storage size:

int m = x + y;
long n = x + y;

Table 7.4 lists the implicit conversion rules for the data types short and ushort.

Implicit conversions might occur in many situations, including method invocation and assignment statements.

The int and uint Keywords

The C# int ranges from –2,147,483,648 through 2,147,483,647, and uint ranges from 0 through 4,294,967,295. The default value for both int and uint types is 0. You can declare these types this way:

int x = 334;
uint y = 10;
int m = new int();
uint n = new uint();

Table 7.5 lists the implicit conversion rules for the data types int and uint.

To assign data types larger than an int or uint to int or uint, you must downcast the variable. For example:

Table 7.4. Valid Implicit Conversions for the ushort and short Data Types (C#)

Conversion From

Conversion To

short

short, int, long, float, double, or decimal

ushort

short, ushort, int, uint, long, ulong, float, double, or decimal

Table 7.5. Valid Implicit Conversions for the uint and int Data Types (C#)

Conversion From

Conversion To

int

long, float, double, or decimal

uint

long, ulong, float, double, or decimal

sbyte, byte, short, ushort, or char

int

byte, ushort, or char

uint

long l = 22;
uint ui = (uint) l;

When an integer literal has no suffix, its type is the first of these types in which its value can be represented: int, uint, long, ulong.

You can also use the suffix u or U:

uint myUint = 123U;

When you use the suffix U or u, the literal type is determined to be either uint or ulong according to its size. In this example, it is uint.

The long and ulong Keywords

The C# long ranges from –9,223,372,036,854,775,808 through 9,223,372,036,854,775,807, and the ulong ranges from 0 through 18,446,744,073,709,551,615. The default value for long and ulong is 0. You can declare this type this way:

long l = 234;
ulong m = 8676;
long j = new long();
ulong k = new ulong();

When an integer literal has no suffix, its type is the first of these types in which its value can be represented: int, uint, long, ulong. You can also use suffixes to specify the type of the literal according to the following rules:

  • If you use L or l, the type of the literal integer will be either long or ulong according to its size.

  • If you use U or u, the type of the literal integer will be either uint or ulong according to its size.

  • If you use UL, ul, Ul, uL, LU, lu, Lu, or lU, the type of the literal integer will be ulong.

Suffixes are used for clarity when you're passing parameters to overloaded methods. Adding the suffix L to the integer literal 5 calls the right method:

public void method1(long i) {}
public void method1(int i) {}
method1(5L); // will call method1(long i)

Table 7.6 lists the implicit conversion rules for the data types long and ulong.

You can use the long type with other numeric integral types in the same expression, in which case the expression is evaluated as long. For example, the following expression evaluates as long:

8809L + 876;
The char Keyword

The char keyword is used to declare a Unicode character. Unicode characters are 16-bit characters used to represent most of the known written languages in the world. A char can be implicitly converted to ushort, int, uint, long, ulong, float, double, or decimal. However, there are no implicit conversions from other types to the char type. The default value of char type is '\0'.

Table 7.6. Valid Implicit Conversions for the ulong and long Data Types (C#)

Conversion From

Conversion To

long

float, double, or decimal

ulong

float, double, or decimal

sbyte, byte, short, ushort, int, uint, or char

long

byte, ushort, uint, or char to ulong

ulong

Table 7.7. Built-in Floating-Point Types (C#)

Type (Keyword)

Range

.NET Framework Type

float

±1.5 x 10-45 to ±3.4 x 1038

System.Single

double

±5.0 x 10-324 to ±1.7 x 10308

System.Double

To summarize, all integral types share some common rules. Large data types cannot be assigned to a smaller data type without explicit casting. The implicit rules stated for each of the data types are used at the time the assignment is made and parameters are passed to a method call. Certain data type literals can be suffixed with an appropriate alphabet, and this practice is highly encouraged for the sake of clarity. Data types can be constructed like regular objects.

7.4.2 The Built-in Floating-Point Types

Table 7.7 shows the built-in floating-point types. The keyword is given along with the corresponding .NET class, which is found in the System namespace.

The float Keyword

The float keyword denotes a simple type that stores 32-bit floating-point values.

By default, a real numeric literal on the right side of the assignment operator is treated as double. Therefore, to initialize a float variable, you use the suffix f or F. For example:

float x = 6.78F;

If you don't use the suffix in the preceding declaration, you will get a compilation error because you are attempting to store a double value in a float variable. You can mix numeric integral types and floating-point types in an expression. The expression is evaluated according to the following rules:

  • If one of the floating-point types is double, the expression evaluates to double.

  • If there is no double type in the expression, the expression evaluates to float.

Listing 7.3 shows how various data types are mixed with floats to yield a float result.

Listing 7.3 Mixing float Types with Other Data Types (C#)
using System;
class FloatTest {
  public static void Main() {
    int x = 3;
    float y = 10.5f;
    short z = 5;
    Console.WriteLine("Result is "+ 2.7f*(x*y/z));
  }
}

The output of Listing 7.3 is

Result is 17.01

Listing 7.4 shows some useful properties of the System.Single class.

Listing 7.4 System.Single Class Properties (C#)
using System;
class FloatTest {
  public static void Main() {
    Console.WriteLine(System.Single.Epsilon);
    Console.WriteLine(System.Single.MaxValue);
    Console.WriteLine(System.Single.MinValue);
    Console.WriteLine(System.Single.NaN);
    Console.WriteLine(System.Single.NegativeInfinity);
    Console.WriteLine(System.Single.PositiveInfinity);
  }
}

The output of Listing 7.4 is as follows:

1.401298E45
3.402823E+38
–3.402823E+38
NaN
–Infinity
Infinity

You can also instantiate a float type using the default constructor, in which case the variable takes the default value of 0.0f.

The double Keyword

The double keyword denotes a simple type that stores 64-bit floating-point values. By default, a real numeric literal on the right side of the assignment operator is treated as double. However, if you want an integer number to be treated as double, you use the suffix d or D. For example:

double x = 3d;

You can mix numeric integral types and floating-point types in an expression. The expression is evaluated such that if one of the floating-point types is double, the expression evaluates to double. You cannot assign a double to a float data type without explicitly casting.

7.4.3 The Built-in Decimal Value Types

This category has only one data type (keyword decimal). The decimal keyword denotes a 128-bit data type. Compared with floating-point types, the decimal type has a greater precision and a smaller range, and that makes it suitable for financial and monetary calculations. If you want a numeric real literal to be treated as decimal, you use the suffix m or M. For example:

decimal money = 50.5m;

Without the suffix m, the number is treated as a double, thus generating a compiler error. The decimal type is also represented by the System.Decimal class. The integral types are implicitly converted to decimal, and the result evaluates to decimal. Therefore, you can initialize a decimal variable using an integer literal without the suffix. For example:

decimal myMoney = 300;

There is no implicit conversion between floating-point types and the decimal type; therefore, a cast must be used to convert between these two types. For example:

decimal money = 109.9m;
double x = (double) money;
money = (decimal) x;

You can also mix decimal and numeric integral types in the same expression. However, mixing decimal and floating-point types without a cast results in a compilation error.

7.4.4 The Built-in Boolean Value Types

The bool keyword is an alias of System.Boolean. It is used to declare variables to store the Boolean values: true and false. You can assign a Boolean value to a bool variable. For example:

bool var = true;

You can also assign to a bool variable an expression that evaluates to bool. For example:

bool isTrue = (c >= 20);

In C++, a value of type bool can be converted to a value of type int; in other words, false is equivalent to zero, and true is equivalent to nonzero values. In C#, there is no conversion between the bool type and other types.

7.4.5 Explicit Conversions

We have described the built-in C# data types and have explained the implicit conversion rules that apply to them. Implicit conversions occur during assignment or when the literal is passed a method parameter. By contrast, explicit numeric conversion is used to convert any numeric type to any other numeric type for which there is no implicit conversion; explicit numeric conversion uses a cast expression. Table 7.8 shows the explicit conversion rules for the various data types.

Table 7.8. Explicit Numeric Conversion Rules (C#)

Converted From

Converted To

sbyte

byte, ushort, uint, ulong, or char

byte

sbyte or char

short

sbyte, byte, ushort, uint, ulong, or char

ushort

sbyte, byte, short, or char

int

sbyte, byte, short, ushort, uint, ulong, or char

uint

sbyte, byte, short, ushort, int, or char

long

sbyte, byte, short, ushort, int, uint, ulong, or char

ulong

sbyte, byte, short, ushort, int, uint, long, or char

char

sbyte, byte, or short

float

sbyte, byte, short, ushort, int, uint, long, ulong, char, or decimal

double

sbyte, byte, short, ushort, int, uint, long, ulong, char, float, or decimal

decimal

sbyte, byte, short, ushort, int, uint, long, ulong, char, float, or double

The next section looks at the reference types in C#.


7.5 The C# Reference Types

Reference types store references to actual data. In C#, reference types fall into the following categories:

  • class

  • interface

  • delegate

The class and interface keywords should be intuitive. They are used to define classes and interfaces, respectively. The delegate keyword is used to define references to a function (see Chapter 18). When you instantiate a delegate with parameters passed to it, that calls the underlying function to which the delegate is pointing. The two commonly used classes in the .NET Framework are System.Object and System.String. Their aliases are object and string, respectively. All data types inherit from System.Object. This inheritance enables value types (known as primitives in Java) to be treated as objects in C#. Value types—including both struct types and built-in types such as int—can be converted to and from the type object. As mentioned in Chapter 3, this conversion is transparent to the programmer and is called boxing and unboxing. The following section discusses this process in detail.


7.6 Boxing and Unboxing

In the Java programming language, primitives and objects are not interchangeable. Although there are methods to convert a primitive to its wrapper object and vice versa, you cannot pass a primitive to a method whose signature expects the wrapper object and vice versa. For example, in the following method call, the age parameter literal must be an int primitive; similarly, the result of this method must be assigned to the wrapper object for int, which is

java.lang.Integer.
public Integer setAge(int age);

C# addresses this problem via boxing and unboxing, wherein the alias of the value type is interchangeable with its System namespace class. Boxing and unboxing enable value types to be treated as objects.

7.6.1 Boxing

Boxing is an implicit conversion of a value type to the type object or to any interface type implemented by this value type. Boxing the value of a value type allocates an object instance and copies the value into the new object.

Consider the following declaration of a value-type variable:

int i = 123;

The following statement implicitly applies the boxing operation on the variable i:

object o = i;

This statement creates a reference to an object o (on the stack) that references a value of the type int (on the heap). This value is a copy of the value-type value assigned to the variable i. It is also possible, but not necessary, to perform the boxing explicitly, as in the following example:

int i = 123;
object o = (object) i;

Boxing allows you to pass primitives as parameters to the methods of the System.Collections class (see Chapter 14). Listing 7.5 shows how boxing allows you to pass value-type arguments to the classes of the System.Collections namespace.

Listing 7.5 Boxing Test (C#)
using System;
using System.Collections;
class BoxingTest {

  public static void Main() {
    ArrayList list = new ArrayList();
    for (int i = 0; i < 10; ++i) list.Add(i);
    for (int i = 0; i < 10; ++i) {
      Console.Write(" "+list[i]);
    }
  }
}

Here is the output of Listing 7.5:

0 1 2 3 4 5 6 7 8 9

The runtime automatically converts the value type i to its equivalent System.Int32 and adds it to ArrayList.

7.6.2 Unboxing

Unboxing is an explicit conversion from the type object to a value type or from an interface type to a value type that implements the interface. An unboxing operation consists of two actions:

  1. Checking the object instance to make sure it is a boxed value of the given value type

  2. Copying the value from the instance into the value-type variable

The following statements demonstrate both boxing and unboxing operations:

int i = 123;      // A value type
object box = i;   // Boxing
int j = (int)box; // Unboxing

For an unboxing conversion to a given value type to succeed at runtime, the value of the source argument must be a reference to an object that was previously created by boxing a value of that value type. If the source argument is null or is a reference to an incompatible object, an InvalidCast Exception is thrown. Listing 7.6 illustrates invalid unboxing.

Listing 7.6 Invalid Unboxing (C#)
using System;
public class UnboxingTest {
  public static void Main() {
    int i = 123;
    // Boxing
    object o = i;
    // Reference to incompatible object produces
    InvalidCastException
    try {
      int j = (short) o;
      Console.WriteLine("Unboxing OK.");
    }
    catch (InvalidCastException e) {
      Console.WriteLine(e.Message);
    }
  }
}

The output of Listing 7.6 is as follows:

Specified cast is not valid.

7.6.3 The typeof Operator

Operators are discussed in Chapter 8, but we introduce the typeof operator here because it deals with data types. The typeof operator is used to obtain the System.Type object for a type. To obtain the runtime type of an expression, you can use the .NET Framework method GetType. A close equivalent of the typeof operator in Java is the class operator. For example, in Java you typically write the following:

String name = MyClass.class.getName();

Here, MyClass.class gives a reference to the java.lang.Class object.

Similarly, the typeof operator gives a reference to a System.Type object, as illustrated in Listing 7.7.

Listing 7.7 Using the typeof Operator (C#)
using System;
public class TypeOfTest {
  public static void Main() {

    Type t = typeof(TypeOfTest);
    Console.WriteLine("The type of this class is "+t);

    TypeOfTest tObj = new TypeOfTest();
    Console.WriteLine("The type of this class is
"+tObj.GetType());

  }
}

The output of Listing 7.7 is as follows:

The type of this class is TypeOfTest
The type of this class is TypeOfTest

Notice that the typeof operator works on the literal name of the type, whereas the GetType method must be applied to an instantiated object of the type.

To understand the pointer data type, you need to first understand unsafe code in C#. That's what we discuss next.


7.7 Unsafe Code

The core C# language differs notably from C and C++ in its omission of pointers as a first-class data type. Instead, C# provides references and the ability to create objects that are managed by a garbage collector. This design, coupled with other features, makes C# a much safer language than C or C++. In the core C# language it is simply not possible to have an uninitialized variable, a "dangling" pointer, or an expression that indexes an array beyond its bounds.

However, even though it is possible to model everything as references, sometimes you need pointer operations. For example, interfacing with the underlying operating system and coding certain performance-critical operations require raw pointer manipulation. Java does provide a semblance of this ability to deal with pointers through the Java Native Interface (JNI) API, which is basically a Java wrapper over C/C++ method calls. C#, on the other hand, provides the unsafe mode of operation.

In unsafe code you can declare and operate on pointers, perform conversions between pointers and integral types, take the address of variables, and so forth. In a sense, writing unsafe code is much like writing C code within a C# program.

The unsafe keyword denotes an unsafe context. You apply this keyword as a modifier in the declaration of callable members such as methods, properties, and constructors. The scope of the unsafe context extends from the parameter list to the end of the function, so pointers can also be used in the parameter list. To compile unmanaged code, you must specify the /unsafe compiler option. Unmanaged code is not verifiable by the Common Language Runtime.

Now let's look at the pointer data type.

7.7.1 The Pointer Data Type

A pointer type is written as an unmanaged type or the keyword void; this is followed by a * token. The type specified before the * in a pointer type is called the referent type of the pointer type. It represents the type of the variable to which a value of the pointer type points. A pointer is not permitted to point to a reference or to a structure that contains references, and the referent type of a pointer must be an unmanaged type. An unmanaged type is any type that isn't a reference type and doesn't contain reference type fields at any level of nesting. In other words, an unmanaged type is one of the following:

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, or bool

  • Any enum

  • Any pointer

  • Any user-defined struct that contains fields of unmanaged types only

The intuitive rule for mixing pointers and references is that referents of references (objects) are permitted to contain pointers, but referents of pointers are not permitted to contain references.

Table 7.9 shows the common pointer types.

The value of a pointer having type T* represents the address of a variable of type T. The pointer indirection operator * can be used to access this variable. For example, given a variable P of type int*, the expression *P denotes the int variable found at the address contained in P. Like an object reference, a pointer can be null. Applying the indirection operator to a null pointer results in implementation-defined behavior. A pointer with the value null is represented by all bits zero.

The void* type represents a pointer to an unknown type. Because the referent type is unknown, the indirection operator cannot be applied to a pointer of type void*, nor can any arithmetic be performed on such a pointer. However, a pointer of type void* can be cast to any other pointer type (and vice versa). Listing 7.8 shows the use of pointers in C#.

Listing 7.8 Simple Pointer Data Types (C#)
using System;
public class PointerTest {
  unsafe static void Main() {
    int i = 10;
    int* px2 = &i;
    Console.WriteLine(*px2*2);
  }
}

The output of Listing 7.8 is

20

Table 7.9. Common Pointer Types (C#)

Type (Keyword)

Description

byte*

Pointer to byte

char*

Pointer to char

int**

Pointer to pointer to int

int*[]

An array of pointers to int

void*

Pointer to unknown type

Because writing unsafe code is the exception rather than the norm, we will not explore details about pointer arithmetic and how pointers behave when passed as method parameters.


7.8 Summary

Data types form a fundamental building block of a programming language. Whereas Java provides only a handful of data types (classes, interfaces, and primitives), C# has a rich set of value types and reference types. Both should be used in C# programming. You are encouraged not to try to model everything as a class and thereby miss out on some of the optimizations, such as structs, that are built into C# data types. The following key conclusions can be drawn from this chapter:

  • C# data types can be value types, reference types, or pointer types. Value types can be struct or enum. A struct can be user-defined or built-in. Both structs and enums are lightweight constructs that do not have an equivalent in Java.

  • C# built-in value types can be integral, floating, decimal, or Boolean. Each built-in type has an alias and a .NET class found in the System namespace.

  • Value types in C# are like Java primitives, but unlike Java primitives, they inherit from System.Object and can be treated as objects. Therefore, C# value types can be assigned literals (such as ordinary variables), or they can be constructed using the new operator.

  • In general, a built-in value type can be implicitly converted to another value type if the original data type is smaller in size than the converted data type. For example, you can assign an int literal to a long variable. Implicit conversion of a literal happens during assignment of the literal to the variable. However, an explicit cast is required when you pass the literal to a method parameter. For example, suppose you have a method that takes an sbyte. When this method is passed an integer literal, the literal will have to be cast to sbyte. Explicit casting is also required when a larger data type is being assigned to a smaller data type.

  • C# has built-in boxing and unboxing, which allows you to interchange the built-in value type aliases with their .NET class equivalents. This simplifies code, and you can simply pass primitives (in Javaspeak) to methods that require objects.

  • C# reference types are classes, interfaces, and delegates.

  • C# pointer types allow for direct pointer manipulation through the pointer data type. These data types can be used only in an unsafe context, and their use is usually discouraged unless it is absolutely required.


Chapter 8. Operators

Any programming language provides a base set of operators and expressions. Depending on the richness of the set of operators and expressions, your code can end up looking either verbose or terse. Associated with operators is also the context in which the operator is being used. This context is usually supplied by the operand. For example, an operator can behave differently depending on whether the operand is numeric or nonnumeric. Many languages allow an operator to behave differently depending on the context in which it is being used, a behavior called operator overloading. Although Java does not allow the programmer to overload operators, C# does, and you will see how you can use this feature to your advantage in writing clean code. Table 8.1 lists the C# operator categories and the operators associated with each category.

C# operators can be unary (requiring one operand), binary (requiring two operands), or ternary (requiring three operands). The latter category includes only one operator, ?:.


8.1 Arithmetic Operators

Although arithmetic operators are traditionally used on numeric data types, they can also be used in the context of other nonnumeric data types. The following discussion assumes that we are dealing with numeric data types. We discuss the meaning of these operators in nonnumeric contexts in Section 8.18.

The +, -, /, *, and % operators have the same meaning in C# as they have in Java. In the context of two numeric operands the +, -, /, *, and % operators add, subtract (the right-hand side [RHS] operand from the left-hand side [LHS] operand), divide (the LHS operand by the RHS), multiply the two operands, and calculate the remainder of dividing the LHS operand by the RHS. Listing 8.1 shows these operators in action.

Table 8.1. C# Operators

Operator Category

Operators

Arithmetic

+, –, *, /, %

Logical

&&, ||, true, false, !

Bitwise

&, |, ^, ~

String concatenation

+

Increment, decrement

++, --

Shift

>>, <<

Relational

==, <=, >=, <, >, !=

Assignment

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

Member access

.

Casting

()

Indexing

[]

Conditional

?:

Object creation

new

Type information

is, as, sizeof, typeof

Overflow exception control

checked, unchecked

Pointer-based

*, &, - >, []

Listing 8.1 C# Math Operators
using System;
public class MathOperators {

  public static void Main(string[] args)!! {

    int x = 4;
    int y = 2;
    float z = 4.0f;

    //Int and Int
    Console.WriteLine(" x = 4, y = 4");
    Console.WriteLine("x+y {0}", x+y);
    Console.WriteLine("x-y {0}", x-y);
    Console.WriteLine("x*y {0}", x*y);
    Console.WriteLine("x/y {0}", x/y);
    Console.WriteLine("x%y {0}", x%y);

    //Int and Float
    Console.WriteLine("\n y = 2, z = 4.0f");
    Console.WriteLine("z+y "+(z+y)+" Type="+(z+y).GetType());
    Console.WriteLine("z-y "+(z-y)+" Type="+(z-y).GetType());
    Console.WriteLine("z*y "+(z*y)+" Type="+(z*y).GetType());
    Console.WriteLine("z/y "+(z/y)+" Type="+(z/y).GetType());
    Console.WriteLine("z%y "+(z%y)+" Type="+(z%y).GetType());
    }

}

The output of Listing 8.1 is as follows:

x = 4, y = 4
x+y 6
x-y 2
x*y 8
x/y 2
x%y 0

y = 2, z = 4.0fz+y 6 Type=System.Single
z-y 2 Type=System.Single
z*y 8 Type=System.Single
z/y 2 Type=System.Single
z%y 0 Type=System.Single

Listing 8.1 uses the arithmetic operators on mixed data types (int and float). The results look identical to those of conducting the operations with two integers. To differentiate between the results, we print the Type of the result in the second batch. Note that when you mix types, the expression is evaluated as the larger data type.


8.2 Logical Operators

Logical operators are used along with control flow statements to branch the execution of the program logic based on whether a condition is true or false. The &&, ||, and ! operators perform a logical AND, a logical OR, and a logical negation operation, respectively, on their operands. The && and || operators are binary operators and require two operands, which can be any two expressions that evaluate to true or false boolean values. The ! operator is the negation operator, and it negates the expression it is attached to. The true and false operators can be used for checking whether a particular condition is true or false. To simulate an infinite while loop, you can use the true operator:

while (true);

Listing 8.2 shows the C# logical operators. These have the same meaning in Java.

Listing 8.2 C# Logical Operators
using System;

public class LogicalOperators {

  public static void Main(string[] args) {

    int x = 4;
    int y = 2;
    Console.WriteLine("x = "+x+";y = "+y);
    if ((x == 4) && (y == 2)) {
      Console.WriteLine("Executing (x == 4) && (y == 2))");
    }
    if ((x == 4) || (y == 2)) {
      Console.WriteLine("Executing (x == 4) || (y == 2))");
    }
    if ((x == 4) || (y != 2)) {
      Console.WriteLine("Executing (x == 4) || (y != 2))");
    }
    if (!((y != 2) && (x != 4))) {
      Console.WriteLine("Executing !((y != 2) && (x != 4))");
    }
    if (!(y != 2) || (x == 4)) {
      Console.WriteLine("Executing !((y != 2) || (x == 4))");
    }
    if ((y != 2) && (x == 4)) {
      Console.WriteLine("Executing (y != 2) && (x == 4)");
    }

    if (true) {
      Console.WriteLine("Always printed");
    }

    if (false) {
      Console.WriteLine("Never printed");
    }

  }

}

The output of Listing 8.2 is as follows:

x = 4;y = 2
Executing (x == 4) && (y == 2))
Executing (x == 4) || (y == 2))
Executing (x == 4) || (y != 2))
Executing !((y != 2) && (x != 4))
Executing !((y != 2) || (x == 4))
Always printed

In Listing 8.2 the != and == operands get precedence over the && and || logical operators. (Operator precedence is discussed in Section 8.17.) For now, if you are familiar with Java it should be clear that the results are very much the same as you would expect in Java. Note that when evaluating the following line, the CLR uses a technique called short-circuiting (similar to the one in Java):

if ((y != 2) && (x == 4)) {
  Console.WriteLine("Executing (y != 2) && (x == 4)");
}

Basically, y != 2 evaluates to false; and because the LHS operand of a logical AND (&&) operator evaluates to false, there is no point in evaluating the RHS operand (a logical AND with false will always yield a false result). As you will see later, except for the true and false operators, the &&, ||, and ! operators cannot be overloaded.


8.3 Bitwise Operators

Bitwise operators are used for conducting bit operations. C# and Java provide a common set of bit operators, which are discussed in the following sections.

8.3.1 The & Operator

Binary & operators are predefined for the integral types and bool. For integral types, & computes the bitwise AND of its operands. For bool operands, & computes the logical AND of its operands; that is, the result is true if and only if both its operands are true. This operator can be overloaded.

8.3.2 The | Operator

Binary | operators are predefined for the integral types and bool. For integral types, | computes the bitwise OR of its operands. For bool operands, | computes the logical OR of its operands; that is, the result is false if and only if both its operands are false. This operator can be overloaded.

8.3.3 The ^ Operator

Binary ^ operators are predefined for the integral types and bool. For integral types, ^ computes the bitwise exclusive-OR of its operands. For bool operands, ^ computes the logical exclusive-OR of its operands; that is, the result is true if and only if exactly one of its operands is true. This operator can be overloaded.

8.3.4 The ~ Operator

The ~ operator performs a bitwise complement operation on its operand. Bitwise complement operators are predefined for int, uint, long, and ulong. This operator can be overloaded.

Listing 8.3 shows the bitwise operations.

Listing 8.3 Bitwise Operations (C#)
using System;
public class BitWise {
  public static void Main(string[] args) {

    int a = 2;
    int b = 3;
    //Integral types
    Console.WriteLine("a = 2, b= 3 and a | b ="+(a| b));
    Console.WriteLine("a = 2, b= 3 and a & b ="+( a & b));
    Console.WriteLine("a = 2, b= 3 and a ^ b ="+(a ^ b));
    Console.WriteLine("a = 2 and ~a ="+ (a));

    bool x = true;
    bool y = false;
    //Bool types
    Console.WriteLine("x = true, y= false and x | y ="+(x | y));
    Console.WriteLine("x = true, y= false and x & y ="+(x & y));
    Console.WriteLine("x = true, y= false and x ^ y ="+(x ^ y));

  }
}

The output of Listing 8.3 is as follows:

a = 2, b= 3 and a | b =3
a = 2, b= 3 and a & b =2
a = 2, b= 3 and a ^ b =1
a = 2 and ~ a =-3
x = true, y= false and x | y True
x = true, y= false and x & y =False
x = true, y= false and x ^ y True

The C# bitwise operators function similarly to their Java equivalents.


8.4 String Concatenation Operators

As mentioned earlier, the + operator can be overloaded for different data types. In the case of C# strings, the + operator is overloaded to concatenate two strings. Listing 8.4 shows a simple concatenation example.

Listing 8.4 Using the + Operator on Strings (C#)
using System;

public class StringTest {

  public static void Main(string[] args) {
    string s = "Test";
    string t = " Case";
    int x = 10;
    Console.WriteLine(s+t);
    Console.WriteLine(s+10);
  }
}

The output of Listing 8.4 is as follows:

Test Case
Test10

Note that to use the + operator with a string as one operand and a numeric data type as the other operand, you convert the numeric data type to a string and then concatenate the resulting two strings.


8.5 The Increment and Decrement Operators

The ++ and -- operators increment and decrement their operands, respectively, by 1. These are unary operators. They can be used in both prefix and postfix mode. In prefix mode, the result of the operation is the value of the operand after it has been incremented or decremented; in postfix mode, the result of the operation is the value of the operand before it has been incremented or decremented. Listing 8.5 illustrates this point.

Listing 8.5 Using the ++ and -- Operators (C#)
using System;

class Test {
  public static void Main() {
      double x;
      x = 1.5;
      Console.WriteLine(++x);
      Console.WriteLine(--x);
      Console.WriteLine(x++);
      Console.WriteLine(x--);
  Console.WriteLine(x);
    }
}

The output of Listing 8.5 is as follows:

2.5
1.5
1.5
2.5
1.5

Initially the value of x is 1.5. The ++x increments the value first and then assigns it to the variable x, and hence 2.5 is printed as the first line. Similarly, --x decrements the value back to 1.5 and then assigns it to x, and hence 1.5 is printed. Then x++ increments the value of x after it is printed, and hence 1.5 is printed. Before x-- is executed, the value of x is 2.5. Executing x-- prints the value of x first and then decrements it by 1. The last line prints the value of x, which is now 1.5 (because of the preceding decrement, which took place after the value of x was printed).


8.6 Shift Operators

8.6.1 The << Operator

The left-shift operator (<<) shifts its LHS operand left by the number of bits specified by its RHS operand:

expr << count

Here, expr is the expression of type int, uint, long, or ulong and is the value to be shifted; count is the shift count and must be an int. If expr is an int or uint (a 32-bit quantity), the shift count is given by the low-order five bits of count (count & 0x1f). If expr is a long or ulong (a 64-bit quantity), the shift count is given by the low-order six bits of count (count & 0x3f). The high-order bits of expr are discarded, and the low-order empty bits are zero-filled.

8.6.2 The >> Operator

The right-shift operator (>>) shifts its LHS operand right by the number of bits specified by its RHS operand:

expr >> count

Here, expr is an expression of type int, uint, long, or ulong and is the value to be shifted; count is the shift count and must be an int. If expr is an int or uint (a 32-bit quantity), the shift count is given by the low-order five bits of count (count & 0x1f). If expr is a long or ulong (a 64-bit quantity), the shift count is given by the low-order six bits of count (count & 0x3f). If expr is an int or long, the right-shift is an arithmetic shift (high-order empty bits are set to the sign bit). If expr is of type uint or ulong, the right-shift is a logical shift (high-order bits are zero-filled). Listing 8.6 shows shift operations.

Listing 8.6 Shift Operations (C#)
using System;

public class ShiftTest {

  public static void Main(string[] args) {
    int i = 1;
    int j = 33;
    Console.WriteLine(i << j);
    Console.WriteLine(i >> j);
  }
}

Here is the output of Listing 8.6:

2
0

Shift operations never cause overflows. The << and >> operators can be overloaded.


8.7 Relational Operators

Relational operators are used mostly in the condition part of if-else and while constructs.

8.7.1 The == Operator

The equality operator (==) is a binary operator and compares the two operands on each side of the operator for equality. For value types, the == operator returns true if the value of the operands is equal and false otherwise. For reference data types, the == operator returns true if the two references refer to the same object. For string objects, the == operator returns true if the contents of the two strings are equal.

Java programmers should be careful of this feature of C#. In C#, the == operator on strings does what the equals() method of Java strings does. The == operator can be overloaded only if the != is overloaded along with it. Listing 8.7 demonstrates some important aspects of the == operator.

Listing 8.7 The == Operator (C#)
using System;

public class Test {

  public static void Main(string[] args) {

    int ii = 1;
    int jj = 1;
    //Compares the value types : True
    Console.WriteLine("1) "+(ii ==jj));
    object i = 1;
    object j = 1;
    //Compares the boxed references : False
    Console.WriteLine("2) "+(i ==j));
    string a = "test";
    string b = String.Copy(a);
    string c = "test";
    //Compares the string values: True
    Console.WriteLine("3) "+(a == b));
    //Compares the objects the individual strings point to:
    //False
    Console.WriteLine("4) "+((object)a == (object)b));
    //Compares the objects the individual strings point to:
    //True (because of string interning)
    Console.WriteLine("5) "+((object)a == (object)c));
  }
}

The output of Listing 8.7 is as follows:

1) True
2) False
3) True
4) False
5) True

Line 1 prints True because we are comparing simple value types for equality. Line 2 prints False because we are actually comparing the reference types. Although both i and j have the same integral value, when assigned to an object they are boxed to different object references; and we know that the == operator will return false when comparing references pointing to different objects. Line 3 prints True because, unlike Java, the == operator in C# compares the value of the strings. Line 4 shows False because b is a copy of the string and hence a brand new object, although its value is the same as that of a.

Line 5 returns true because of string interning. To conserve memory, the runtime interns strings, meaning that string literals of the same value point to the same object in memory.

8.7.2 The != Operator

This binary operator compares its operands for inequality. It returns true if the operands are unequal, and false otherwise.

8.7.3 The < Operator

This binary operator compares the LHS to the RHS and returns true if the LHS is less than the RHS.

8.7.4 The > Operator

This binary operator compares the LHS to the RHS and returns true if the LHS is greater than the RHS.

8.7.5 The <= Operator

This binary operator compares the LHS to the RHS and returns true if the LHS is less than or equal to the RHS.

8.7.6 The >= Operator

This binary operator compares the LHS to the RHS and returns true if the LHS is greater than or equal to the RHS.

Listing 8.8 shows the rest of the relational operators in action.

Listing 8.8 The Other Relational Operators (C#)
using System;

public class Test {

  public static void Main(string[] args) {

    int a = 4;
    int b = 5;
    //Comparing value types
    Console.WriteLine("4 < 5 "+(a < b));
    Console.WriteLine("4 > 5 "+(a > b));
    Console.WriteLine("4 <= 5 "+(a <= b));
    Console.WriteLine("4 >= 5 "+(a >= b));
    Console.WriteLine("4 != 5 "+(a != b));

    string s = "abc";
    string t = "def";
    //Comparing value types
    Console.WriteLine(s != t);

  }

}

The output of Listing 8.8 is as follows:

4 < 5 True
4 > 5 False
4 <= 5 True
4 >= 5 False
4 != 5 True
True

Note that the <, >, <=, and >= operators cannot be applied to strings. All relational operators can be overloaded. If < is overloaded, then > must be overloaded and vice versa. Similarly, if <= is overloaded, then >= must be overloaded and vice versa.


8.8 Assignment Operators

C# and Java share a common set of assignment operators, which are explained in the following sections.

8.8.1 The = Operator

This binary operator stores the value of the operand on the RHS in the indexer, property, or variable defined on the LHS. For this assignment to be successful, either the two operands should be of the same data type or the LHS operand should have an implicit conversion to the RHS operand. This operator cannot be overloaded.

8.8.2 The += Operator

The addition assignment operator is a binary operator that increments the operand on the LHS by an amount indicated by the operand on the RHS and then assigns the result to the LHS. This operator cannot be overloaded.

x += y implies x = x + y

8.8.3 The -= Operator

The subtraction assignment operator is a binary operator that decrements the operand on the LHS by an amount indicated by the operand on the RHS and then assigns the result to the LHS. This operator cannot be overloaded.

x -= y implies x = x - y

8.8.4 The *= Operator

The multiplication assignment operator is a binary operator that multiplies the operand on the LHS by an amount indicated by the operand on the RHS and then assigns the result to the LHS. This operator cannot be overloaded.

x *= y implies x = x * y

8.8.5 The /= Operator

The division assignment operator is a binary operator that divides the operand on the LHS by an amount indicated by the operand on the RHS and then assigns the result to the LHS. This operator cannot be overloaded.

x /= y implies x = x / y

8.8.6 The %= Operator

The modulus assignment operator is a binary operator that performs a modulus operation on the operand on the LHS by an amount indicated by the operand on the RHS and then assigns the result to the LHS. This operator cannot be overloaded.

x %= y implies x = x % y

8.8.7 The &= Operator

The logical AND assignment operator is a binary operator that performs a logical AND of the operand on the LHS with the RHS operand and then assigns the result to the LHS operand. This operator cannot be overloaded.

x &= y implies x = x & y

8.8.8 The |= Operator

The logical OR assignment operator is a binary operator that performs a logical OR of the operand on the LHS with the RHS operand and then assigns the result to the LHS operand. This operator cannot be overloaded.

x |= y implies x = x | y

8.8.9 The ^= Operator

The logical exclusive-OR assignment operator is a binary operator that performs a logical exclusive-OR of the operand on the LHS with the RHS operand and then assigns the result to the LHS operand. This operator cannot be overloaded.

x ^= y implies x = x ^ y

8.8.10 The >>= Operator

The shift assignment operator is a binary operator that shifts the LHS operand right by an amount indicated by the RHS operand and then assigns the result to the LHS operand. This operator cannot be overloaded.

x >>= y implies x = x >> y

8.8.11 The <<= Operator

The shift assignment operator is a binary operator that shifts the LHS operand left by an amount indicated by the RHS operand and then assigns the result to the LHS operand. This operator cannot be overloaded.

x <<= y implies x = x << y

Listing 8.9 shows the assignment operators.

Listing 8.9 Assignment Operators (C#)
using System;

class Test {
  public static void Main(string[] args) {

    int a = 8;
    Console.WriteLine(a);
      Console.WriteLine(a += 2);
      Console.WriteLine(a -= 2);
    Console.WriteLine(a *= 2);
    Console.WriteLine(a /= 2);
    Console.WriteLine(a %= 2);
    Console.WriteLine(a &= 2);
    Console.WriteLine(a |= 2);
    Console.WriteLine(a ^= 2);
    Console.WriteLine(a >>= 2);
    Console.WriteLine(a <<= 2);
  }
}

The output of Listing 8.9 is as follows:

8
10
8
16
8
0
0
2
0
0
0


8.9 The Member Access (Dot) Operator

The member access (dot) operator (".") is used frequently to access a field or to call a method on an object.

object a = new object();
a.ToString();

The dot operator is also used to form qualified names: names that specify the namespace or interface (for example) to which they belong.

System.Console.WriteLine("hello"); // class Console in namespace System


8.10 The Casting Operator

Chapter 7 notes that explicit conversions of a larger data type to a smaller data type require casting. Casting is done using the () operator. For example:

long a = 1000L;
int b = (int) a;

Here, the casting operator is used to cast a long a to an int b.


8.11 The Indexing Operator

Square brackets ([]) are used for arrays, indexers, and attributes. They can also be used with pointers. An array type is a type followed by []:

int[]golonghorns; // golonghorns is of type int[], "array of int"
golonghorns = new int[100];  // create a 100-element int array

An array of golonghorns is accessed using the [] operator. The array indexing operator cannot be overloaded; however, types can define indexers: properties that take one or more parameters. Indexer parameters are enclosed in square brackets, just like array indices, but indexer parameters can be declared to be of any type. For example, here is a Hashtable type, which associates keys and values of arbitrary type:

Collections.Hashtable h = new Collections.Hashtable();
h["a"] = 123; // note: using a string as the index

The [] operator is also used to define attributes:

[attribute(AllowMultiple=true)]
public class Attr {
}


8.12 The Conditional Operator

The conditional operator (?:) returns one of two values depending on a third value. The operator is used in an expression of the following form:

expr1 ? expr2 :expr3

This expression is equivalent to the following:

If (expr1) {
  expr2;
} else {
  expr3;
}

expr1 must evaluate to a bool. Unlike C and C++, in C# expr1 cannot evaluate to an int and expect the runtime to interpret a value of 1 as true and 0 as false.


8.13 Object Creation Operator

The new operator is used to create new objects, value types, or structs. This operator cannot be overloaded.

object o = new object();
int ii = new int();


8.14 Type Information Operators

The type information operators are used to get the type of the object at runtime. Because all objects inherit from the class System.Object, they all inherit the GetType method, which returns the type of the object. Java has one instanceof operator that you can use to check whether a particular object is an instance of a particular class. C# has operators that are the equivalent of Java's instanceof.

8.14.1 The is Operator

The is operator is used to check whether the runtime type of an object is compatible with a given type. The is operator is used in an expression of the following form:

expr is type

Here, expr is an expression of reference type. The is operator evaluates to true if both of the following conditions are met:

  • expr is not null.

  • expr can be cast to type. That is, a cast expression such as (type) expr does not throw an exception.

Listing 8.10 shows the use of the is operator.

Listing 8.10 Using the is Operator (C#)
using System;

class Test {

  public static void Main(string[] args) {
    object i = 3;

    if (i is Int32) {
      Console.WriteLine("It's an int");
    } else {
      Console.WriteLine("It's an "+i.GetType());
    }
  }
}

The output of Listing 8.10 is as follows:

It's an int

The is operator cannot be overloaded.

8.14.2 The as Operator

The as operator is used to perform conversions between compatible types. The as operator is used in an expression of the following form:

expr as type

The as operator is like a cast except that it yields null on conversion failure instead of raising an exception. More formally, an expression of the form

expr as type

is equivalent to

expr is type ? (type)expr : (type)null

except that expr is evaluated only once.

Listing 8.11 shows the use of the as operator.

Listing 8.11 Using the as Operator (C#)
using System;

using System.Collections;

class Test {

  public static void Main(string[] args) {

    object s = "Test";
    string t = s as string;
      ArrayList l = s as ArrayList;
      Console.WriteLine(t);
    Console.WriteLine((l==null));
  }
}

The output of Listing 8.11 is as follows:

Test
True

Because s is not obviously an ArrayList, the second line prints True because the ArrayList reference is null. The as operator cannot be overloaded.

8.14.3 The sizeof Operator

The sizeof operator is used to obtain the size (in bytes) of a value type. The sizeof operator can be applied only to value types, not reference types. The sizeof operator can be used only in the unsafe mode. The sizeof operator cannot be overloaded. Listing 8.12 shows the use of the sizeof operator.

Listing 8.12 Using the sizeof Operator (C#)
using System;

class Test {

  unsafe public static void Main(string[] args) {
    Console.WriteLine(sizeof(int));
    Console.WriteLine(sizeof(long));
  }

}

Here is the output of Listing 8.12:

4
8

Note that you must compile Listing 8.12 using the /unsafe option of the compiler.

8.14.4 The typeof Operator

The typeof operator is used to obtain the System.Type object for a type. A typeof expression takes this form:

typeof (type)

The typeof operator cannot be overloaded. Chapter 7 discusses the typeof operator in detail.


8.15 Overflow Exception Control

Unlike Java, C# provides explicit operators for checking arithmetic overflow conditions. These conditions can occur when a variable of a larger data type is assigned to a variable of a smaller data type.

8.15.1 The checked Operator

The checked keyword is used to control the overflow-checking context for integral-type arithmetic operations and conversions. It can be used as an operator or as a statement according to the following forms:

checked block
checked (expression)

Here, block is the statement block that contains expressions to be evaluated in a checked context, and expression is the expression that needs to be evaluated in a checked context.

In a checked context, if an expression produces a value that is outside the range of the destination type, the result depends on whether the expression is constant or nonconstant. Constant expressions cause compile-time errors, whereas nonconstant expressions are evaluated at runtime and raise exceptions. If neither checked nor unchecked is used, a constant expression uses the default overflow checking at compile time, which is checked. Otherwise, if the expression is nonconstant, the runtime overflow checking depends on other factors, such as compiler options and environment configuration.

Listing 8.13 shows how using the checked context can change the output of the program.

Listing 8.13 Using the checked Operator (C#)
using System;

class Test {

  public static void Main(string[] args) {
    int x = System.Int32.MaxValue;
    int y = System.Int32.MaxValue;
    PlainTest(x, y);
    CheckedTest(x, y);
  }
  static void PlainTest(int x, int y) {
    Console.WriteLine(x+y);
  }
  static void CheckedTest(int x, int y) {
    try {
      Console.WriteLine(checked(x+y));
    } catch (Exception e) {
      Console.WriteLine(e.Message);
    }
  }
}

The output of Listing 8.13 is as follows:

2
Arithmetic operation resulted in an overflow.

Both x and y are assigned Int32.MaxValue, which is the maximum value an int can be. Adding these two integers is obviously going to result in an int having a value greater than In32.MaxValue. In the checked context, this operation throws a System.OverflowException, which is caught by the try-catch block. In the normal context, the result of the operation depends on the default setting for the runtime environment.

8.15.2 The unchecked Operator

The unchecked keyword is used to control the overflow-checking context for integral-type arithmetic operations and conversions. Like the checked operator, the unchecked operator can be used both as a statement and as an operator. In an unchecked context, if an expression produces a value that is outside the range of the destination type, the result is truncated, as illustrated in Listing 8.14.

Listing 8.14 Using the unchecked Operator
using System;

class Test {

  public static void Main(string[] args) {
    int x = System.Int32.MaxValue;
    int y = System.Int32.MaxValue;
    PlainTest(x, y);
    UnCheckedTest(x, y);
  }

  static void PlainTest(int x, int y) {
    Console.WriteLine(x+y);
  }
  static void UnCheckedTest(int x, int y) {
    try {
      Console.WriteLine(unchecked(x+y));
    } catch (Exception e) {
      Console.WriteLine(e.Message);
    }
  }
}

Here is the output of Listing 8.14:

-2
-2

Note that the result of the unchecked option is truncated.


8.16 Pointer Type Operators

Pointer type operators are used only in unsafe mode (see Chapter 7). The operators have the following meaning:

  • * operator: This operator is used to define pointer types and dereference pointer types.

  • & operator: This unary operator returns the address of its operand.

  • -> operator: The -> operator combines pointer dereferencing and member access.

  • [] operator: You can use square brackets to index off a pointer.


8.17 Operator Precedence

When an expression contains multiple operators, the precedence of the operators controls the order in which the operators are evaluated. For example, in a simple expression such as x + y * z, the * operator gets precedence, and hence the expression is evaluated as x + (y * z).

Except for the assignment operators, all binary operators are left-associative, meaning that operations are performed from left to right. For example, x + y + z is evaluated as (x + y) + z. The assignment operators and the conditional operator (?:) are right-associative, meaning that operations are performed from right to left. For example, x = y = z is evaluated as x = (y = z).

Precedence and associativity can be controlled using parentheses. For example, x + y * z first multiplies y by z and then adds the result to x, but (x + y) * z first adds x and y and then multiplies the result by z.


8.18 Operator Overloading

We have mentioned that certain operators can be overloaded and certain other operators cannot be overloaded. Overloading an operator means to customize its behavior depending on the context in which it is being used. For example, the additive (+) operator is used for adding numerical values when used in the numeric context (that is, both operands are numeric data types). However, when one or more operands of this operator are strings, the operation is treated as string concatenation. You can say that the + operator is overloaded for strings to concatenate them.

Overloading operators makes sense if the context in which they are used is intuitive to the end user. For example, strings do not overload the multiplicative * operator because it does not make sense to multiply two strings. Operator overloading is a powerful tool that can be used to write terse, intuitive code.

Before we look at an example of operator overloading, it's worth noting some facts about C#'s operator overloading:

  • Overloadable unary operators are +, -, !, ~, ++, ==, true, and false.

  • Overloadable binary operators are +, -, *, /, %, &, |, ^, <<, >>, ==, !=, >, <, >=, and <=.

  • It is not possible to overload member access, method invocation, or the =, &&, ||, ?:, checked, unchecked, new, typeof, as, and is operators.

  • When a binary operator is overloaded, the corresponding assignment operator (if any) is also implicitly overloaded. For example, an overload of operator + is also an overload of operator +=.

  • Cast operations, such as (T)x, are overloaded.

  • Element access, such as a[x], is not considered an overloadable operator. Instead, user-defined indexing is supported through indexers.

  • A user-defined operator cannot have the same signature as a predefined operator.

  • User-defined operator declarations cannot modify the syntax, precedence, or associativity of an operator. For example, the * operator is always a binary operator and is always left-associative.

  • When applying operators, the CLR checks to see whether there is a custom overload of the operator. If one is defined, the CLR uses that; otherwise, it uses the default implementation of the operator.

Listing 8.15 shows a simple ComplexNumber class with the +, -, *, <, and > operators overloaded.

Listing 8.15 Operator Overloading (C#)
using System;

class ComplexNumber {

  private float real;
  private float imaginary;
  public ComplexNumber(float real, float imaginary) {
    this.real = real;
    this.imaginary = imaginary;
  }

  public float Real {
    get {
      return real;
    }
  }
  public float Imaginary {
    get {
      return imaginary ;
    }
  }

  public static ComplexNumber operator +(ComplexNumber c1,
                                      ComplexNumber c2) {
    return new ComplexNumber((c1.Real + c2.Real),(c1.Imaginary
                        + c2.Imaginary) );
  }
  public static ComplexNumber operator - (ComplexNumber c1,
    ComplexNumber c2) {
    return new ComplexNumber((c1.Real - c2.Real),
                             (c1.Imaginary - c2.Imaginary) );
  }
  public override string ToString() {
    return real + " + " + imaginary + "i";
  }

  public static void Main() {
    ComplexNumber num1 = new ComplexNumber(1.2f, 23.45f);
    Console.WriteLine("Complex Number #1: " + num1);
    ComplexNumber num2 = new ComplexNumber(2.4f, 46.57f);
    Console.WriteLine("Complex Number #2: " + num2);
    Console.WriteLine("Adding Complex Number (#1 + #2): " +
                                (num1 + num2));
    Console.WriteLine("Subtracting Complex Number (#1 - #2): "
                                + (num1 - num2));
  }
}

The output of Listing 8.15 is as follows:

Complex Number #1: 1.2 + 23.45i
Complex Number #2: 2.4 + 46.57i
Adding Complex Number (#1 + #2): 3.6 + 70.02i
Subtracting Complex Number (#1 - #2): -1.2 + -23.12i

Note that the methods that define the operator are static in nature, but the operator usage is on the instance of the ComplexNumber class. Again, you can implement whatever you want with your operators, but it is a bad idea to overload operators in a manner that is not intuitive to the end user.


8.19 Summary

C# provides a rich set of operators that fall under many categories. Except for a few C#-specific operators (pointer operators, checked, unchecked, is, as, sizeof, and typeof), most of the operators and their meanings are similar to their counterparts in Java. The operator precedence rules of C# are also similar to those of Java.

C# allows operator overloading of certain operators. When evaluating operators, the runtime first checks for any user-defined operator overloading and uses that definition (if any) instead of the predefined meaning of the operator. User-defined operator declarations cannot modify the syntax, precedence, or associativity of an operator. A user-defined operator cannot have the same signature as a predefined operator.


Chapter 9. Essential Control Flow

This chapter examines the control structures available in C#. Control structures control (not surprisingly) the direction of program execution. Understanding the behavior of the flow constructs is key to programming in any language. C# constructs are very similar to those of Java, so Java examples are not presented in this chapter.


9.1 The if Statement

The if statement is a control statement that executes a block of code if an expression evaluates to true. It takes this form:

if (expression)
  statement1
else
  statement2

where

  • expression is an expression that can be implicitly converted to bool or a type that contains overloading of the true and false operators

  • statement1 is the embedded statement(s) to be executed if expression is true

  • statement2 is the embedded statement(s) to be executed if expression is false

Note that unlike C++, C# and Java do not allow the expression to be evaluated to 1 or 0. That is, the integer 1 does not stand for true, and 0 does not stand for false.

If expression is true, then statement1 is executed. If the optional else clause exists and expression evaluates to false, then statement2 is executed. After the if statement is executed, control is transferred to the next statement.

You can conditionally execute multiple statements by including them in a block following an if expression. The statement(s) to be executed upon testing the condition can be of any kind, including another if statement nested into the original if statement. In nested if statements, the else clause belongs to the last if that does not have a corresponding else. Here is a nested if statement:

if (x > 10)
  if (y > 20)
    Console.Write("Statement_1");
  else
    Console.Write("Statement_2");

In this example, Statement_2 will be displayed if the condition (x > 10) evaluates to true and the condition (y > 20) evaluates to false. However, if you want to associate Statement_2 with the condition (x >10), you use braces:

if (x > 10)
{
  if (y > 20)
    Console.Write("Statement_1");
}
else
    Console.Write("Statement_2");

In this case, Statement_2 will be displayed if the condition (x > 10) evaluates to false.

Listing 9.1 shows the conditional if constructs in action. Like Java, C# supports the if, if-elseif, if-elseif-else, and if-else constructs. The condition that is to be evaluated must be of type bool, a requirement that is similar to Java's requirement of having the condition as a Boolean. C# also supports short-circuiting. Upon running Listing 9.1 and passing 10 as the command line argument, you get the following output:

First if
First else-if
Second else-if
No short circuit. I am 11
I did short circuit, I am still 11

In Listing 9.1 the value of i is not incremented the second time; it stays at 4 because C# short-circuits the evaluation of if (true || ++i > 0) and evaluates only if (true).

Listing 9.1 The if Statement Constructs (C#)
using System;

public class Test {

  public static void Main(string[] args) {

    //Ignore exceptions for now.
    int i = Int32.Parse(args[0]);
    //if-statement
    if (i > 0) {
      Console.WriteLine("First if");
    };
    //if-elseif statement
    if (i <=0 ) {
      Console.WriteLine("Second if");
    } else if (i > 0) {
      Console.WriteLine("First else-if");
    }
    //if-elseif-else
    if (i < 0 ) {
      Console.WriteLine("Third if");
    } else if (i > 0) {
      Console.WriteLine("Second else-if");
    } else {
      Console.WriteLine("First else-if");
    }
    //Short-circuiting in action
    if (++i > 0) {
      Console.WriteLine("No short circuit. I am "+i);
    }
    if (true || ++i > 0) {
      Console.WriteLine("I did short circuit, I am still "+i);
    }

    }
}

If the condition inside the if construct evaluates to true, then the statements corresponding to that construct are executed. Well, almost always—except for special cases such as shown in Listing 9.2.

Listing 9.2 Taxing the CLR (C#)
using System;

public class Test {

  static int limit = 0;
  public static void Main(string[] args) {
    try {
      recurse();
    } catch (StackOverflowException e) {
      Console.WriteLine("OK OK, the CLR has its limits");
    } finally {
      Console.WriteLine("Final limit "+limit);
    }
  }

  static public bool recurse() {
    ++limit;
    if (recurse()) {
      Console.WriteLine("true");
    }
    return true;
  }
}

Listing 9.2 is interesting because of the special case of recursion; we have a situation in which the statement true never gets printed during program execution. Running Listing 9.2 gives a StackOverflowException, which is an exception that is thrown when there are too many nested method calls. Ideally, you would wrap the call to recurse() in a try-catch-finally block (see Chapter 10) so as to end the program more elegantly.

Scoping of variables for the if construct follows the same rules as in Java. As shown in Listing 9.3, the variable methodlocal is available both in the if and in the else construct. The variable iflocal is available only in the if construct, and its nested constructs and the elselocal variable are available only in the else construct and its nested constructs.

Listing 9.3 Scoping of Variables in the if Constructs (C#)
using System;

public class Test {

  public static void Main(string[] args) {
    int methodlocal = 2;
    if (false) {
      int iflocal = 3;
      if (!false) {
        Console.WriteLine(methodlocal*iflocal);
      }
      Console.WriteLine(+  ethodlocal);
    } else {
      int elselocal = 3;
      if (!false) {
        Console.WriteLine(methodlocal*elselocal);
      }
      Console.WriteLine(—methodlocal);
    }
  }
}


9.2 The while Loop

The while statement executes a statement or a block of statements until a specified expression evaluates to false. It takes this form:

while (expression) statement

where

  • expression is an expression that can be implicitly converted to bool or a type that contains overloading of the true and false operators. The expression is used to test the loop-termination criteria.

  • statement is the embedded statement(s) to be executed.

Because the test of expression takes place before each execution of the loop, a while loop executes zero or more times. A while loop can be terminated when a break, goto, return, or throw statement transfers control outside the loop. To pass control to the next iteration without exiting the loop, you use the continue statement.

while loops can be nested. In nested while loops, a break or a goto statement in the innermost loop merely transfers control to the outer loop. A return statement exits from the outermost loop, and a throw statement exits from the outer loop depending on whether the thrown exception gets caught or ignored. Listing 9.4 shows the while loop in action.

Listing 9.4 C# while Loop
using System;

public class Test {

  public static void Main(string[] args) {
    int i = 0;
    int j = 0;
    while (j < 5) {
      Console.WriteLine("j "+j);
      while (i < 8) {
        Console.WriteLine("i "+i);
        ++i;
        j +=10;
      }
    }
  }
}


9.3 The do while Loop

The do while statement executes a statement or a block of statements repeatedly until a specified expression evaluates to false. It takes the following form:

do statement while (expression);

where

  • expression is an expression that can be implicitly converted to bool or a type that contains overloading of the true and false operators. The expression is used to test the loop-termination criteria.

  • statement is the embedded statement(s) to be executed.

Unlike the while statement, the body loop of the do statement is executed at least once regardless of the value of the expression.


9.4 The for Loop

The for loop executes a statement or a block of statements repeatedly until a specified expression evaluates to false. It takes the following form:

for ([initializers]; [expression]; [iterators]) statement

where

  • initializers is a comma-separated list of expressions or assignment statements to initialize the loop counters.

  • expression is an expression that can be implicitly converted to bool or a type that contains overloading of the true and false operators. The expression is used to test the loop-termination criteria.

  • iterators are expression statements to increment or decrement the loop counters.

  • statement is the embedded statement(s) to be executed.

The for statement executes the statement repeatedly as follows:

  1. The initializers are evaluated.

  2. While the expression evaluates to true, the statement(s) are executed and the iterators are evaluated.

  3. When the expression becomes false, control is transferred outside the loop.

Because the test of expression takes place before the execution of the loop, a for statement executes zero or more times. All the expressions of the for statement are optional. Listing 9.5 shows the C# for loop in action.

Listing 9.5 C# for Loops
using System;

public class Test {
  public static void Main(string[] args) {
    //Simple for loop
    for (int i = 0; i < 10; ++i) {
      Console.WriteLine("single");
    }
    //For loop with two indices
    for (int i = 10, j = 1; (i > 0 || j < 12); --i, ++j) {
      Console.WriteLine("multiple");
    }
  }
}


9.5 The switch Statement

The switch statement is a control statement that handles multiple selections by passing control to one of the case statements within its body. The switch statement takes this form:

switch (expression)
{
  case constant-expression:
    statement
    jump-statement
  default:
    statement
    jump-statement]
}

where

  • expression is an integral or string type expression.

  • statement is the embedded statement(s) to be executed if control is transferred to the case or the default.

  • jump-statement transfers control out of the case body.

  • constant-expression is the value that causes control to be transferred to a specific case.

Control is transferred to the case statement whose constant-expression matches expression. The switch statement can include any number of case instances, but no two case constants within the same switch statement can have the same value. Execution of the statement body begins at the selected statement and proceeds until the jump-statement transfers control out of the case body.

Notice that the jump-statement is required after each block, including the last block. C# does not support an explicit fall through from one case label to another. The jump-statement can be a goto, a switch-case, or a goto default.

If expression does not match any constant-expression, control is transferred to the statements that follow the optional default label. If there is no default label, control is transferred outside the switch.

Listing 9.6 shows how control can be transferred using the goto case jump statement. Jump statements are detailed in Section 9.7.

Listing 9.6 C# switch Statement
using System;

public class Test {

  public static void Main(string[] args) {

    Console.WriteLine("Coffee sizes: 1=Small 2=Medium
    3=Large");
    Console.Write("Please enter your selection: ");
    string s = Console.ReadLine();
    int n = int.Parse(s);
    int cost = 0;
    switch(n) {
      case 0:
      case 1:
        cost += 25;
        break;
      case 2:
        cost += 25;
        goto case 1;
      case 3:
        cost += 50;
        goto case 1;
      default:
        Console.WriteLine("Invalid selection. Please select 1,
              2, or 3.");
        break;
    }
    if (cost != 0)
      Console.WriteLine("Please insert {0} cents.", cost);
    Console.WriteLine("Thank you for your business.");
  }

}

Listing 9.7 shows how C# allows strings to be used in switch statements. This should come as a relief to Java programmers who have been bickering about the lack of string support in the switch statement. Note that this comparison is case-sensitive.

Listing 9.7 Using Strings in the C# switch Statement
using System;

public class Test {

  public static void Main(string[] args) {

    string s = args[0];
      switch(s) {
        case "Hi":
        case "Hello":
          Console.WriteLine("Greeting");
          goto case "Ouch";
        case "Ouch":
          Console.WriteLine("Pain ");
          break;
        case "Mmmm":
          Console.WriteLine("Gourmet Food ");
          break;
        default:
          Console.WriteLine("Invalid selection. Please select
                Hi, Hello, Mmmm or Ouch");
          break;
    }
  }
}


9.6 The foreach Statement

The foreach statement repeats a group of embedded statements for each element in an array or an object collection. The foreach statement is used to iterate through a collection to get the desired information, but, to avoid unpredictable side effects, you should not use it to change the contents of the collection. The statement takes the following form:

foreach (type identifier in expression) statement

where

  • type is the type of identifier.

  • identifier is the iteration variable that represents the collection element.

  • expression is the object collection or array expression. The type of the collection element must be convertible to the identifier type.

  • statement is the embedded statement(s) to be executed.

The embedded statements continue to execute for each element in the array or collection. After the iteration has been completed for all the elements in the collection, control is transferred to the next statement following the foreach block. The foreach statement can be used to iterate through an array and any of the collections of the System.Collections namespace.

To iterate through a collection, the collection must meet specific requirements. Let's use the following foreach statement as an example:

foreach (ItemType item in myCollection)

myCollection must meet the following requirements:

  • The collection type must be one of the types interface, class, or struct.

  • The collection type must include an instance method named GetEnumerator that returns a type—for example, Enumerator (explained next).

  • The type Enumerator (a class or struct) must contain a property named Current that returns ItemType or a type that can be converted to it. The property accessor returns the current element of the collection.

  • The type Enumerator must contain a bool method named MoveNext, which increments the item counter and returns true if there are more items in the collection.

Listing 9.8 is an example of the foreach statement in action.

Listing 9.8 The foreach Statement
using System;
using System.Collections;

public class Test {

  public static void Main(string[] args) {
    //foreach on an array
    int[] arr = new int[] {1,2,2,3};
    foreach (int i in arr) {
      Console.WriteLine(i);
    }
    //foreach on an array
    foreach (int i in arr) {
      Console.WriteLine(i);
    }
  }
}

Unlike the for loop, the iterating primitive or object of the foreach statement is read-only. That is, for (int i =0; i <10; ++i) lets you change the value of i inside the loop, but foreach (int i in arr) doesn't let you change the value of i inside its loop. Performance-conscious Java programmers will be quick to ask whether the foreach construct is faster than the for construct for the situation shown in Listing 9.8 (iterating over an array of integers). As always, the proof is in the pudding, so Listing 9.9 shows the pudding.

Listing 9.9 Which Is Faster: foreach or for?
using System;

public class Test {

  public static void Main(string[] args) {
    int NUM = 10000000;
    int[] arr = new int[NUM];
    for (int i =0; i < NUM; ++i) {
      arr[i] = i+1;
    }
    long t1 = System.Environment.TickCount;
    for (int i =0; i < NUM; ++i);
    long t2 = System.Environment.TickCount;
    foreach (int i in arr);
    long t3 = System.Environment.TickCount;
    Console.WriteLine("for loop "+(t2-t1)+" ms");
    Console.WriteLine("foreach loop "+(t3-t2)+" ms");
  }
}

Here is a sample output of Listing 9.9:

for loop 80 ms
foreach loop 391 ms

Note that the actual output of this listing may vary from one run to another. A single test should not dictate the choice of the appropriate construct for solving your problem. Choosing between for and foreach is also a matter of design, code consistency, and readability. For iterating over collections, using the foreach construct is more intuitive than using the for loop.


9.7 Jump Statements

Jump statements break the conventional flow of the control statements we have discussed. The C# jump statements are as follows:

  • break. The break statement terminates the closest enclosing loop or conditional statement in which it appears. Control is passed to the statement that follows the terminated statement, if any. The break statement takes this form:

    break-statement:break;

  • continue. The continue statement passes control to the next iteration of the enclosing iteration statement in which it appears.

  • goto. The goto statement transfers program control directly to a labeled statement.

The goto statement takes one of the following forms:

goto identifier;
goto case constant-expression;
goto default;

where

  • identifier is a label

  • constant-expression is a switch-case label

In the first form, identifier indicates a label located in the current body, the same lexical scope, or an enclosing scope of the goto statement. A common use of goto is to transfer control to a specific switch-case label or to the default label in a switch statement. The goto statement is also useful for getting out of deeply nested loops.


9.8 Summary

C# supports the following control flow constructs.

  • The if statement. The if, if-elseif, if-elseif-else, and if-else constructs are similar to those of Java. Note that unlike C++, C# and Java do not allow the expression to be evaluated to 1 or 0; the integer 1 does not stand for true, and 0 does not stand for false.

  • The while statement. Like the if statement, while loops can be nested. The while loop expression is an expression that can be implicitly converted to bool or to a type that contains overloading of the true and false operators.

  • The do while statement. The body loop of the do statement is executed at least once. The expression in the while part of the do while statement is an expression that can be implicitly converted to bool or to a type that contains overloading of the true and false operators.

  • The switch statement. The switch statement supports strings, something that is a big plus for programmers tired of not being able to compare strings. C# does not support an implicit fall through from one case label (switch statement) to another.

  • The foreach/for statement. C# supports both the for and the foreach construct. The foreach construct provides a generic mechanism for iterating over any collection. The foreach construct is absent in Java.

  • Jump statements. Jump statements are used for breaking the flow of control, typically inside a loop. These include the break, goto, continue, and return statements.


Chapter 10. Programming with Exceptions

In procedural programming, the status of a function call is often returned as an integer result code. The client code (which makes the function call) then uses this code to determine the branching of the program. Upon return of an error integer code in GUI applications, a dialog box indicating an error status is often displayed to the end user. COM programmers are familiar with the HRESULT return value, which is returned by COM components.

But using result codes has several deficiencies:

  • The client code (which makes the function call) must understand the various result codes returned by the function.

  • Depending on the specificity of the result code, the client code gets some idea of what went wrong inside the method at runtime. However, no snapshot of execution is stored for the client code to examine after the method fails.

  • There is no mechanism for tracking and logging runtime errors. Not all applications are GUI-based, and hence a runtime error cannot always be packaged as a pop-up dialog box that the user can see. Logging result codes is not very helpful. Error messages need to be logged in a manner that details the path of execution that led to the error. (Java programmers are probably thinking about the term "stack trace" here.)

Clearly, the makers of C# realized the limitations of using error codes for error management, and they devised a more rigorous mechanism of dealing with runtime execution errors through exceptions. As stated in Chapter 1, exceptions represent a breach of an implicit assumption made within code. For example, if your code tries to parse an integer from a string that contains nonnumeric characters, an exception will be thrown. However, if your code checks whether the string has only numeric digits before parsing it, then an exception will not be thrown. As one of the .NET languages, C# has built-in support for exception handling. Fortunately for Java programmers, many similarities exist between the Java try-catch-finally construct and C#'s try-catch-finally construct.


10.1 Fundamentals of the try-catch-finally Construct

Instead of listing all the permutations of the try-catch-finally construct, we will work through the combinations by starting with a routine programming problem: efficiently determining whether a string can be parsed to a double value. If you're a procedural programmer, you probably tackle this by first iterating through the characters of the string and checking whether each character is a valid numeric or decimal point or has specific symbols such as E, +, /, or -. You then check whether the decimal point and the symbols are positioned correctly inside the string and ensure that they do not occur more than once. Then you must find whether the value is within the min-max range for a double. All this sounds like coding a lot of if-else statements just to find out whether something is a valid double!

A cleaner and perhaps more object-oriented way of handling the problem is to exploit C#'s exception handling mechanism. Remember that exceptions are a breach of an assumption. So we will assume here that the string can be parsed to a double value, and then we will try to parse it. If there is a breach of our assumption, then an exception will be thrown by the .NET runtime. We will catch that exception and examine its nature. Finally, we will print some output.

Let's get our hands dirty by starting a new project and adding a code file to the project. Listing 10.1 shows what you would type in this code file.

Listing 10.1 A Simple try-catch-finally Construct (C#)
using System;

class ExceptionTest {
  static void Main(string[] args) {
    string stacktrace = null;
    double i = Double.NegativeInfinity;
    try {
      i = Double.Parse(args[0]);
    } catch (FormatException exception) {
      stacktrace = exception.StackTrace;
    } finally {
      if (stacktrace ! = null) {
        Console.WriteLine("Stacktrace is {0}", stacktrace);
      } else {
        Console.WriteLine("Double parsed "+i);
      }
    }
  }
}

The program takes the first command line argument and tries to parse it as a double (the try block). If a FormatException is thrown, then the stacktrace of the FormatException is recorded in a string (the catch block). In the finally block, the program prints the parsed integer if no FormatException was thrown; otherwise, it prints the stacktrace.

For now, it will suffice to know that C# has a System.Exception object that is the root object for all other Exception objects. This is similar to Java's java.lang.Exception class. Unlike Java, C# does not differentiate between errors and exceptions. In Java, errors are usually reserved for circumstances in which a programmer has no direct control or when the errors are something that the runtime environment should deal with. Errors are never used for control flow. Exceptions, on the other hand, can be used to alter the flow of execution.

An example of an error condition in Java is the java.lang.Virtual MachineError. Almost never thrown by the JVM, this is an error condition that typical Java programmers do not account for. Making the distinction between errors and exceptions allows Java programmers to take appropriate action in their program. Whereas an exception condition might warrant notifying the end user of a failure and leaving the end user to decide the course of action, an error condition might warrant shutting down the application gracefully.

Because C# does not make the distinction between error and exception conditions, it is in the best interest of the programmer to treat all exception and error conditions with the same rigor. C# does not have the concept of checked and unchecked exceptions.

Let's try some hands-on exception handling. Run Listing 10.1 with different command line parameters. The program prints the double value if a legal numeric value is passed. The stack trace is printed when a value containing nonnumeric characters is passed. However, if you pass in no arguments, then you will notice that the runtime pops up a dialog box (Figure 10.1).

Figure 10.1. The .NET Runtime's Way of Dealing with Unhandled Exceptions

The pop-up box is C#'s way of dealing with unhandled exceptions. It allows you to pick a debugger to further explore the kind of exception thrown. Click No for now. (We discuss debugging in later chapters.) The Visual Studio .NET 2003 IDE also allows you to set different debugging levels for the type of exception thrown. You reach this setting by clicking on the Debug menu item and then the Exceptions submenu item.

The Java equivalent of Listing 10.1 is shown in Listing 10.2.

Listing 10.2 Java Equivalent of Listing 10.1 (Java)
public class ExceptionTest {

  public static void main(String[] args) {
    double i = Double.MIN_VALUE;
    ExceptionWriter writer = new ExceptionWriter();
    String stacktrace = null;
    try {
      i = Double.parseDouble(args[0]);
    } catch (NumberFormatException e) {
      e.printStackTrace(writer);
      stacktrace = writer.getStackTrace();
    } finally {
      if (stacktrace != null) {
        System.out.println("Stacktrace is "+stacktrace);
      } else {
        System.out.println("Double parsed is "+i);
      }
    }
  }
}

class ExceptionWriter extends java.io.PrintWriter {

  public ExceptionWriter() {
    super(new java.io.StringWriter());
  }
  public String getStackTrace() {
    return ((java.io.StringWriter)out).getBuffer().toString();
  }
}

Java's exceptions don't have a convenient stacktrace property, so to obtain the stacktrace as a string we had to write a special class. The Java program behaves similarly to the C# program in terms of the command lines, but as you know, the JVM does not pop up a dialog box when the exception goes unhandled by the JVM thread running the main method.

Listings 10.1 and 10.2 contain only one method call in the try block, so it's easy to predict the number of exceptions that can be thrown. If you browse the help file in Visual Studio IDE .NET 2003, you will find out that the Double.parse (string) method throws three exceptions: Overflow Exception, FormatException, and ArgumentNullException. Thus, we can catch all three exceptions and have the stack trace printed for them.

Listing 10.3. shows a C# program that tries to catch all the possible exceptions that can be thrown by the try block. The last catch block catches the general System.Exception. Note that the most general exception must be caught in the last catch block. In this case, this exception catches the System.IndexOutOfRangeException, which gets thrown when the program is called without any command line arguments.

Listing 10.3 Catching All Possible Exceptions Thrown by the try Block (C#)
using System;

class ExceptionTest {
  static void Main(string[] args) {
    string stacktrace = null;
    double i = Double.NegativeInfinity;
    try {
      i = Double.Parse(args[0]);
    }catch (FormatException exception) {
      stacktrace = exception.StackTrace;
    }catch (OverflowException exception) {
      stacktrace = exception.StackTrace;
    }catch (ArgumentNullException exception) {
      stacktrace = exception.StackTrace;
    }catch (Exception exception) {
      stacktrace = exception.StackTrace;
    } finally {
      if (stacktrace != null) {
        Console.WriteLine("Stacktrace is {0}", stacktrace);
      } else {
        Console.WriteLine("Double parsed "+i);
      }
    }

  }
}

You can also catch specific exceptions in Java. However, because Java differentiates between exceptions and errors and furthermore between checked and unchecked exceptions, it is customary not to catch every exception thrown. Programming exceptions is more of a design issue than it seems; you have alternatives for dealing with them:

  • You can catch and log exceptions.

  • You can create custom exception objects and throw exceptions back to the called method.

  • You can let the runtime take care of exceptions.

We discuss these design issues in Section 10.5.

C# exceptions are implicitly thrown to the called method's parent method if they are not caught in the called method. Listing 10.4 shows a C# program in which the parse method is wrapped in a method that is itself wrapped in another method.

Listing 10.4 A Simple try-catch-finally Construct (C#)
using System;

class ExceptionTest {
  static void Main(string[] args) {
    string stacktrace = null;
    double i = Double.NegativeInfinity;
    try {
      i = execute(args);
    }
    catch (FormatException exception) {
      stacktrace = exception.StackTrace;
    } finally {
      if (stacktrace != null) {
        Console.WriteLine("Stacktrace is {0}", stacktrace);
      } else {
        Console.WriteLine("Double parsed "+i);
      }
    }

  }

  public static double execute(string[] args) {
    return =_execute(args);
  }
  private static double =_execute(string[] args) {
    return Double.Parse(args[0]);
  }

}

In Listing 10.4 we have merely moved the Double.Parse(string) call two functions deep. Notice that this program behaves similarly to Listing 10.1.

In C#, uncaught exceptions thrown by a method are passed to the parent method (the method that called the current method). In this case, the exception was thrown by the _execute() method, which was then passed to the execute() method, which passed the exception to the Main() method. The passing of exceptions bubbles up the method stack until it reaches the root method of execution, at which point the runtime takes over and pops up the dialog box. This bubbling up of the exception through the method stack is also present in Java.

You can nest try-catch-finally blocks as long as you account for every try block with a catch block or a finally block. If an exception is not caught by the inner try-catch block, then it is tried by the outer try-catch block. Listing 10.5 illustrates this point.

Listing 10.5 Nesting try-catch Blocks (C#)
using System;

class ExceptionTest {
  static void Main(string[] args) {
    string stacktrace = null;
    double i = Double.NegativeInfinity;
    try {
      try {
        i = Double.Parse(args[0]);
      } catch (ArgumentNullException e) {
        Console.WriteLine("Exception is {0}", e);
      }
    }catch (FormatException exception) {
      stacktrace = exception.StackTrace;
    } finally {
      if (stacktrace != null) {
        Console.WriteLine("Stacktrace is {0}", stacktrace);
      } else {
        Console.WriteLine("Double parsed "+i);
      }
    }

  }
}

When Listing 10.5 is run with a command line parameter that is nonnumeric, the inner try-catch block cannot catch the exception, and hence the outer try-catch block catches the exception. Now test this program by changing the inner try block to the following:

i = Double.Parse(null);

Now the inner try block catches the ArgumentNullException.

In nested try-catch blocks, the inner blocks can catch the more general exception. Although this is a bad coding practice, it can be done. Java supports nested exceptions in the same way as C#, and the inner exceptions can be general exceptions.


10.2 Custom Exceptions

As mentioned earlier, one of the key advantages of exceptions is the ability they give you to track the execution path in the event of a failure. In the previous listings we examined the stack trace and pinpointed what went wrong with the code, but often you need a more descriptive way of logging what went wrong. One quick fix is to print a friendly string to the file, console, or database and later search for that string during troubleshooting. However, searching for a specific string is error-prone because string messages change.

A more elegant way of handling exceptions is to create your own custom exception objects. These objects are like any other C# objects. Listing 10.6 shows a custom exception class.

Listing 10.6 A Custom Exception Class (C#)
using System;
public class ParseException : ApplicationException {
  public ParseException() {}
  public ParseException(string message) :base(message) {
  }
  public ParseException(string message, Exception inner)
:base(message, inner) {
  }
  public bool isWrapper() { return base.InnerException != null; }
  public String getStackTrace() {
    if (base.InnerException != null) {
      return base.InnerException.StackTrace;
    } else {
      return this.StackTrace;
    }
  }

}

Note that the custom exception class must have three constructors corresponding to the ApplicationException class. You use ApplicationException as the superclass of all your custom exception classes in C#. The second constructor simply calls the base constructor, which in turn sets the inner Message property of the exception to the string passed. The third constructor takes a user-friendly string and an exception for which ParseException is a wrapper. This constructor also calls the corresponding base constructor and sets the Message and InnerException properties of the base Exception class. The ParseException class has an additional method (isWrapper) that can be used to check whether this exception indeed wraps a more specific inner exception. The getStackTrace method returns the stack trace of the ParseException if there is no inner exception, and returns the stack trace of the inner exception if it is not null.

Let's modify our exception test class to use this new wrapper exception class. Listing 10.7 shows the use of the new exception class.

Listing 10.7 The Exception Class Using the New ParseException Custom Exception Class (C#)
using System;

class ExceptionTest {
  static void Main(string[] args) {
    string stacktrace = null;
    double i = Double.NegativeInfinity;
    try {
      parse(args[0]);
    }catch (ParseException exception) {
      //stacktrace = exception.InnerException.StackTrace;
      stacktrace = exception.StackTrace;
    } finally {
      if (stacktrace != null) {
        Console.WriteLine("Stacktrace is {0}", stacktrace);
      } else {
        Console.WriteLine("Double parsed "+i);
      }
    }

  }
  static double parse(string target) {
      try {
        return Double.Parse(target);
      } catch(Exception e) {
        throw new ParseException("Unable to parse the string
                                 ",e);
        }
  }
}

Running this program with a command line parameter test1234 produces the following output:

Stacktrace is at ExceptionTest.parse(String target) in
c:\book\code\chapter 10\exceptions\listing 10.7.cs:line 24
  at ExceptionTest.Main(String[] args) in c:\book\code\chapter
10\exceptions\listing 10.7.cs:line 7

The stack trace printed does not give any details about what happened. To get to the exception, you must deal with the inner exception thrown.

Replace the following catch block line:

exception.StackTrace;

with

stacktrace = exception.InnerException.StackTrace;

The output of this change is as follows:

Stacktrace is at System.Number.ParseDouble(String s,
NumberStyles
style, NumberFormatInfo info)
  at System.Double.Parse(String s, NumberStyles style,
IFormatProvider provider)
  at System.Double.Parse(String s)
  at ExceptionTest.parse(String target) in c:\book\code\chapter
10\exceptions\listing 10.7.cs:line 22

You can see that ParseException acts as a wrapper and wraps the actual exception thrown, which is FormatException.

Using custom exception classes gives you more control of how exceptions are handled. Using custom exception classes is superior to using custom strings for debugging. Custom exceptions are also common in Java. We talk more about custom exceptions later in this chapter.


10.3 Inheritance and Exceptions

You probably have noticed by now that there is no throws keyword in C#. Neither interface nor class methods explicitly declare thrown exceptions. In Java, specifying the throws clause in the method declaration is called exception specification. C# does not allow exception specification. The makers of C# realized that exception specification leads to unnecessary proliferation of exception handling code in normal classes.

For example, suppose A calls B, B calls C, C calls D, and D raises an exception that is eventually handled by A. If C# required exception specification, then A, B, C, and D each would have to contain exception handling code, even though the actual work of raising the exception is done only in D and the actual handling of the exception is done in A.

C#'s lack of exception specification may confuse Java programmers at first, but in fact it is a convenience that comes in handy in large code bases. Java enforces exception specification through the compiler. Therefore, a method that throws an exception puts the onus on the caller of the method to deal with that exception. If it's a checked exception that is thrown, then the caller must deal with it in a try-catch block or declare it in the throws clause. The caller of the method must catch as many such checked exceptions as are thrown by the called method. In Java an interface method that throws a checked exception must be implemented by a class that throws the same exception.

In C#, on the other hand, you can have a subclass method throw an exception that the superclass method does not throw. Listings 10.8 and 10.9 illustrate the point that overridden subclass methods can throw exceptions that are not thrown by the corresponding superclass method.

Listing 10.8 Parser, a Simple Superclass (C#)
using System;
using System.Collections;

public class Parser {
  Hashtable properties;
  public Parser() {
      properties = new Hashtable();
  }
  virtual public void Parse() {
      Console.WriteLine("Resource");
  }

}
Listing 10.9 A Simple Subclass of the Parser Class
using System;

public class XMLParser : Parser {
  public XMLParser():base(){
  }
  override public void Parse() {
    base.Parse();
    Console.WriteLine("XMLParser");
    throw new ParseException("Unable to parse ");
  }
  public static void Main(string[] args) {
    XMLParser conn =null;
    try {
      conn = new XMLParser();
      conn.Parse();
    } catch(ParseException e) {
      Console.WriteLine(e.getStackTrace());
    }

  }
}

In Listing 10.9, the XMLParser parse method throws a ParseException that is not thrown by the superclass parse method. A Java subclass that overrides a method of the superclass cannot throw any new checked exceptions (those that extend java.lang.Exception). It can, however, throw unchecked exceptions (those that extend java.lang.RuntimeException).

One disadvantage of Java's exception specification is that if you subclass a third-party read-only class (that is, the code cannot be modified or there is no access to the code, only the class file), then your subclass cannot throw custom checked exceptions. One disadvantage of C#'s relaxed view of exceptions is that it is totally up to the authors of methods to declare which exceptions their methods throw.


10.4 CLR Exceptions

The System.Exception class is the root of all exception classes in C#. To get a glimpse of the exceptions supported by the .NET runtime, with the Visual Studio .NET IDE running click on the Debug option and then on Exceptions. Next, click on Common Language Runtime Exceptions. You should see a dialog box as shown in Figure 10.2. The tree shows the exception classes in various namespaces.

Figure 10.2. List of CLR Exceptions

    [ Team LiB ]


10.5 Design Considerations

Programming with exceptions in C# introduces several design considerations because of performance trade-offs.

C# exceptions are first-class objects. Throwing exceptions involves creating new objects, and if these objects are not released, you can get memory leaks. Although C# and Java both alleviate this problem through automatic garbage collection, excessive creation of short-lived objects forces the garbage collector to run often and hampers runtime performance.

Another design consideration is that catching exceptions involves diverting the flow of execution, and that may slow your program. Therefore, you should use exception handling to deal with exceptional cases and not to control program flow (that is, in the manner of a control flow structure such as a while loop or if-else condition).

There are several alternatives for handling exceptions, each with distinct advantages and disadvantages.

10.5.1 Let It Throw

This is a laissez-faire approach. If method A calls B and B throws an exception, then A does not do anything special but simply throws it back to the caller of method A. In the case of C#, which does not require exception specification, this approach means declaring nothing. This is often the recommended approach, especially when you are dealing with several layers of method calls and want to expose the exception thrown at the lowermost layer to be handled at the uppermost layer. Java servlet programmers are familiar with this approach in the context of exceptions thrown in a doGet() or a doPost() method that are allowed to pass through and be thrown as ServletExceptions.

10.5.2 Catch, Clean Up, and Rethrow

In this approach a method catches the exception, does some cleanup (such as setting some references to null or resetting some Boolean values), and then rethrows the exception. This approach is appropriate if you want the object to keep clean state when the exception is thrown. In Listing 10.10, we have added an init() method to Listing 10.9 and an initialize Boolean member variable that indicates whether the instance was initialized properly. Here, the exception thrown is a ParseInitialize Exception. However, we want to maintain the fact that the parser was not initialized after an exception was thrown, and we still throw the same exception to let the user know that the parser was not initialized.

Listing 10.10 Maintaining the State of the Object after an Exception Is Thrown (C#)
using System;

public class XMLParser : Parser {
  bool initialize = true;

  public XMLParser():base(){
  }

  public void init() {
    try {
      LoadParsers();
    } catch (ParseInitializeException e) {
      initialize = false;
      throw;
    }
  }
  private void LoadParsers() {
    throw new ParseInitializeException("Parser File
                        location is null");
  }
  override public void Parse() {
    base.Parse();
    Console.WriteLine("XMLParser");
    throw new ParseException("Unable to parse ");
  }
  public static void Main(string[] args) {
    XMLParser conn =null;
    try {
      conn = new XMLParser();
      conn.Parse();
    } catch(ParseException e) {
      Console.WriteLine(e.getStackTrace());
    }

  }
}

10.5.3 Catch, Clean Up, and Rethrow as a Different Exception

Throwing the same exception out of the method that is thrown inside a method can create ambiguity. To avoid confusion, it is customary to put the inner specification in a wrapper exception that is then thrown from the method. You can sometimes throw custom exceptions to aggregate several exceptions that could be thrown inside a method.


10.6 Summary

The .NET CLR supports exception handling in the form of a Java-style try-catch-finally construct. Here are the key points to keep in mind about C# exception handling:

  • A try block must be followed by one or more catch blocks or by a finally block, or both.

  • The most general exception needs to be caught in the last catch block.

  • A try-catch block can be nested. Exceptions bubble up the execution stack to outer try-catch blocks. Any uncaught exceptions bubble up the method call stack until they are handled by the CLR.

  • Like Java, C# has one System.Exception class from which all other exception classes inherit.

  • C# does not support checked exceptions, unchecked exceptions, or errors.

  • C# allows implementing classes to throw exceptions that are not explicitly declared in their corresponding interface methods. Also, subclasses can override superclass methods and throw new exceptions in the overridden methods.

  • In C# you can create custom exceptions, which allow more control over how expressive your exceptions can be.

  • There are four ways to deal with exceptions: Do nothing about a thrown exception (often the best approach); clean up and reset the object's state and throw the same exception; clean up and reset the state and rethrow the exception as a custom exception; or handle the exception.


Chapter 11. Working with Arrays

Arrays are one of the most fundamental and commonplace data structures of any programming language. Both C# and Java treat arrays as first-class objects, meaning that arrays are allocated on the heap. Although arrays are treated as objects by the runtime, for the most part you use arrays primarily as containers for holding elements, as in this example:

int[] arr = new int[5];

Although arr is an object, you would not inherit from it; instead, you would use arr primarily for storing five integers.


11.1 Java versus C# Arrays

Although arrays are syntactically declared in the same way in Java and C#, there some differences in the way arrays are treated in the two languages.

11.1.1 Java Arrays

In Java, arrays are first-class objects, but there is no single class that defines an array. Furthermore, Java arrays are not collections; although the java.util.Arrays class exists alongside other collection classes, it is primarily a utility class with all static methods to manipulate arrays. Declaring an array does not create an array object nor allocate space in memory; instead, it creates a variable with a reference to an array. Array variable declarations must indicate a dimension by using []. Java array indices start from 0. All array elements must be of the same type. Although an Object[] array can contain objects of different classes, doing so is not intuitive and is not advisable. An element of an array can itself be an array.

Following are some valid Java array declarations:

String[]s, String s[], String[][] s, String s[][]

Declaring the size of the array with the following notation is illegal:

String[5] s;

Following are some of the rules that apply to initializing arrays in Java:

  • All arrays are zero-based.

  • Arrays must be indexed by int values or by byte, short, or char values (because these can be promoted to int).

  • Using a long index value to access an array causes a compilation error.

  • Attempting to access an array with an index less than 0 or greater than the length of the array causes an ArrayIndexOutOfBounds Exception to be thrown at runtime.

  • Because arrays are objects, they can be initialized using the new operator.

  • When created, arrays are automatically initialized with the default value of their type.

  • Instance array variables are initialized to null, but method local variables are not initialized.

  • You can initialize an array by using {} as part of the array declaration:

    String[] oneDimArray = { "abc","def","xyz" };

11.1.2 C Arrays

In C#, arrays are also objects, and they all derive from the System.Array class. The Array class is the base class for language implementations that support arrays. However, only the system and compilers can derive explicitly from the Array class. Users should use the array constructs provided by the language.

A C# abstract class is as follows:

[Serializable]
public abstract class Array : ICloneable, IList, ICollection,
IEnumerable

This is slightly different from Java, which does not have any single class to represent array objects. The System.Array class consists of several properties and static methods for creating and manipulating arrays (Tables 11.1 and 11.2).

Table 11.1. Properties of the System.Array Class

Property

Description

IsFixedSize

Gets a value indicating whether the Array has a fixed size

IsReadOnly

Gets a value indicating whether the Array is read-only

IsSynchronized

Gets a value indicating whether access to the Array is synchronized (thread-safe)

Length

Gets the total number of elements in all the dimensions of the Array

Rank

Gets the rank (number of dimensions) of the Array

SyncRoot

Gets an object that can be used to synchronize access to the Array

The System.Array class is a collection inasmuch as it implements the IList and the ICollection interfaces.

The rest of the chapter looks at one-dimensional, multidimensional, and jagged arrays.


11.2 One-Dimensional Arrays

A one-dimensional array is basically a single-column table; elements in the table can be accessed with an index, and all the elements belong to the same data type. Elements in an array always begin with an index of 0. Arrays are declared using [] brackets and are initialized using the new operator in both C# and Java. Let's look at array declaration and initialization in both languages.

Although both languages share similarities, there is a slight difference in initializing arrays. Java has two ways to declare an array; the [] brackets can follow either the data type or the variable name. In C#, the brackets cannot follow the variable name.

A one-dimensional array is initialized in C# this way:

Int[] arr
Int arr[] // ERROR, won't compile

Table 11.2. Methods of the System.Array Class

Method

Description

BinarySearch

Searches a one-dimensional sorted Array for a value, using a binary search algorithm.

Clear

Sets a range of elements in the Array to zero, to false, or to a null reference (Nothing in Visual Basic), depending on the element type.

Clone

Creates a shallow copy of the Array.

Copy

Copies a section of one Array to another Array and performs type casting and boxing as required.

CopyTo

Copies all the elements of the current one-dimensional Array to the specified one-dimensional Array starting at the specified destination Array index.

CreateInstance

Initializes a new instance of the Array class.

Equals (inherited from Object)

Determines whether two Object instances are equal.

GetEnumerator

Returns an IEnumerator for the Array.

GetHashCode (inherited from Object)

Serves as a hash function for a particular type, suitable for use in hashing algorithms and data structures such as a hash table.

GetLength

Gets the number of elements in the specified dimension of the Array.

GetLowerBound

Gets the lower bound of the specified dimension in the Array.

GetType (inherited from Object)

Gets the type of the current instance.

GetUpperBound

Gets the upper bound of the specified dimension in the Array.

GetValue

Overloaded. Gets the value of the specified element in the current Array.

IndexOf

Returns the index of the first occurrence of a value in a one-dimensional Array or in a portion of the Array.

Initialize

Initializes every element of the value-type Array by calling the default constructor of the value type.

LastIndexOf

Returns the index of the last occurrence of a value in a one-dimensional Array or in a portion of the Array.

Reverse

Reverses the order of the elements in a one-dimensional Array or in a portion of the Array.

SetValue

Sets the specified element in the current Array to the specified value.

Sort

Sorts the elements in one-dimensional Array objects.

ToString (inherited from Object)

Returns a string that represents the current Object.

In Java, you initialize a one-dimensional array this way:

Int[] arr
Int arr[]

Both languages accept the notation of the new operator to allocate arrays:

Int[] arr = new arr[5]

Arrays can be initialized and declared at the same time. Let's look at several ways of initialization and declaration.

Here is initialization and declaration of a one-dimensional array in C#:

int[] arr = {1,2,3,4,5}
int[] arr = new int[5] {1,2,3,4,5}

Here is initialization and declaration of a one-dimensional array in Java:

int[] arr = {1,2,3,4,5};
int[] arr = new int[5] {1,2,3,4,5}; //Error, won't work

Listings 11.1 and 11.2 show initialization and declaration of a simple one-dimensional array in C# and in Java, respectively. The code syntax in the C# and Java code samples is similar except for the length property of an array. In C# the length property is Length, and in Java the length property is length. Both C# and Java array indices are zero-based.

Listing 11.1 BasicArrayClass.cs: A Basic One-Dimensional Array in C#
class BasicArrayClass
{
  public static void Main()
  {
    int[] arr = new int[5];
    //int arr[] = new int[5]; ERROR: WON'T Compile
    for (int i = 0; i < arr.Length; i++)
      arr[i] = i * i;
      for (int i = 0; i < arr.Length; i++)
        Console.WriteLine("arr[{0}] = {1}", i, arr[i]);
  }
}

The output of Listing 11.1 is as follows:

arr[0] = 0
arr[1] = 1
arr[2] = 4
arr[3] = 9
arr[4] = 16
Listing 11.2 BasicArrayClass.java: A Basic One-Dimensional Array in Java
public class BasicArrayClass
{
  public static void main(String[] args)
  {
    //int[] arr = new int[5];
      int arr[] = new int[5];
      for (int i = 0; i < arr.length; i++)
          arr[i] = i * i;
      for (int i = 0; i < arr.length; i++)
        System.out.print(arr[i]);
  }
}

Single-dimension arrays are the most common kind of arrays you will see. Multidimensional arrays are discussed in the following sections.


11.3 Multidimensional Arrays

Arrays can have more than one dimension. The rank of an array denotes the array's dimension. Two-dimensional arrays are indexed with two parameters and can be visualized as elements stored in a table with rows and columns. Elements can be accessed by specifying row and column numbers. Three-dimensional arrays can be thought of as layers of two-dimensional arrays. Basically, three-dimensional arrays require three parameters to index an element.

Let's look at some of the similarities and differences in initializing and declaring multidimensional arrays in C# and Java.

Surprisingly, C# has its own way of declaring multidimensional arrays. In C#, individual dimensions are separated by commas within [] brackets; in contrast, in Java individual dimensions are based on the [] brackets: You use [][] for two-dimensional arrays, and [][][] for three-dimensional arrays.

Here is a two-dimensional array declaration in C#:

int[,] arr = new int[5,2]
int arr[,] = new [5,2] // ERROR, won't work

Here are two-dimensional array declarations in Java:

int[][] arr = new int[5][2]
int arr[][] = new int[5][2]

Arrays can be initialized and declared at the same time. The following examples show initialization and declaration of two-dimensional arrays in C# and Java.

Two-dimensional array declaration and initialization in C#:

int[,] b = {{0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}};

Two-dimensional array declaration and initialization in Java:

int[][] b = new int[][] {{10, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}};
int[][] b = {{10, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}};

Now let's dissect a simple two-dimensional array as shown in Listing 11.3 and then compare it to the Java version shown in Listing 11.4. First, we declare and initialize the two-dimensional array.

int[,] b = {{1, 6}, {2, 7}, {3, 8}, {4, 9}, {5, 10}};

We have two for loops: one for rows, and a second one for columns. Dimension 0 indicates rows, and dimension 1 indicates columns. The length of an array of any dimension in C# is obtained by the GetLength(x) method, where x indicates the dimension. Java and C# store the elements in a similar manner except for a few syntactical differences. In C#, b.Length gives the total number of elements in an array, whereas in Java b.length gives the number of rows in the array.

Listing 11.3 BasicTwoDimensionalArrayClass.CS: A Two-Dimensional Array Sample in C#
using System;
class BasicTwoDimensionalArrayClass
{
  public static void Main()
  {
    int[,] b = {{1, 6}, {2, 7}, {3, 8}, {4, 9}, {5, 10}};

    for (int i=0; i < b.GetLength(0); i++ )
    {
      Console.Write(";bsn");
      for (int j = 0; j < b.GetLength(1); j++)
        Console.Write("b[" + i + "," + j + "]=" + b[i,j] + " ");
    }
  }
}

The output of Listing 11.3 is as follows:

b[0,0]=1 b[0,1]=6
b[1,0]=2 b[1,1]=7
b[2,0]=3 b[2,1]=8
b[3,0]=4 b[3,1]=9
b[4,0]=5 b[4,1]=10
Listing 11.4 BasicTwoDimensionalArray.Java: A Two-Dimensional Array Sample in Java
class BasicTwoDimensionalArray
{
  public static void main(String[] args)
  {
    int[][] b = {{1, 6}, {2, 7}, {3, 8}, {4, 9}, {5, 10}};

    for (int i=0; i < b.length; i++ )
    {
      System.out.print(";bsn");
        for (int j = 0; j < b[i].length; j++)
        System.out.print("b[" + i + "," + j + "]=" + b[i][j] + " ");
    }
  }
}


11.4 Jagged Arrays

The multidimensional arrays that we have discussed are purely rectangular, meaning that all rows and columns have a uniform length. In jagged arrays, the array elements are themselves arrays of various dimensions and sizes (see Figure 11.1). A jagged array is also defined as an "array of arrays."

Figure 11.1. A Jagged Array

Let's look at initialization of jagged arrays in C# and Java. There is no difference in the syntax of initializing jagged arrays in the two languages. A jagged array can be declared as an array of arrays, with each row having a different number of elements.

The following code snippet shows array declaration and initialization in C# and Java:

int [][] arr = new int[3][];

With the array declared, let's initialize each row in the array:

arr[0] = new int[] {1,2,3};
arr[1] = new int[] {1,2,3,4};
arr[2] = new int[] {1,2,3,4,5};

The size of each row is determined at compile time based on the number of elements in the {} brackets.

To create a jagged array, fire up the .NET IDE and copy into it the C# version of the jagged array code. The outer loop in Listing 11.5 loops through all the rows in the array. The number of rows is determined by the arr.length property (in C#) or the arr.Length property (in Java). This might sound strange for Java programmers because, for rectangular arrays, arr.length determines the total number of elements in the array. The inner loop loops through all the columns. The column length in each row is determined by arr[i].Length (in C#) or the arr[i].length (in Java).

Listing 11.5 JaggedArray.: Sample C# Jagged Array
using System;
class JaggedArray
{
  public static void Main()
  {
    int [][] arr = new int[3][];

    arr[0] = new int[] {1,2,3};
    arr[1] = new int[] {1,2,3,4};
    arr[2] = new int[] {1,2,3,4,5};

    for (int i = 0; i < arr.Length; i++)
    {
      System.Console.Write(";bsn");
      for (int j = 0; j < arr[i].Length; j++)
        System.Console.Write("arr[" + i + "," +
                       j + "]=" + arr[i][j] + " ");
    }
  }
}

The output of Listing 11.5 is as follows:

arr[0,0]=1 arr[0,1]=2 arr[0,2]=3
arr[1,0]=1 arr[1,1]=2 arr[1,2]=3 arr[1,3]=4
arr[2,0]=1 arr[2,1]=2 arr[2,2]=3 arr[2,3]=4 arr[2,4]=5

Listing 11.6 shows a sample jagged array in Java.

Listing 11.6 JaggedArray.Java: Sample Java Jagged Array
class JaggedArrays
{
  public static void main(String[] args)
  {
    int [][] arr = new int[3][];

    arr[0] = new int[] {1,2,3};
    arr[1] = new int[] {1,2,3,4};
    arr[2] = new int[] {1,2,3,4,5};

      for (int i = 0; i < arr.length; i++)
      {
        System.out.print(";bsn");
        for (int j = 0; j < arr[i].length; j++)
          System.out.print("arr[" + i + "," + j + "]=" +
                                             arr[i][j] + " ");
      }
  }
}

Next, we look at some of the utility methods of the System.Array class.


11.5 Copying Arrays

Copying one array to another comes in handy for programmers, and all it takes is a simple method call in both C# and Java. The Copy method is built into the System.Array object for C#, and the equivalent Java version, arrayCopy, is built into the Java System object.

Basically, C# has two versions of Array.Copy. These two functions can override each other based on the input parameters. Let's look at the two functions in detail.

The following function copies the specified number of elements from the specified source array to the specified destination array:

Array.Copy(sourceArray, destinationArray, length)

The Array.Copy function accepts three parameters: sourceArray of type System.Array, destinationArray of type System.Array, and finally length of type integer.

The following function copies the specified number of elements from a source array starting at the specified source index to a destination array starting at the specified destination index:

Array.Copy(sourceArray, sourceIndex, destinationArray, destinationIndex, length)

The Array.Copy function accepts five parameters: sourceArray of type System.Array, sourceIndex of type integer, destinationArray of type System.Array, destinationIndex of type integer, and length of type integer.

The next two listings demonstrate how to use the Array.Copy function. In Listing 11.7, the objective is to copy all the elements of array A to array B. For simplicity, we declare and initialize source array A as follows:

int[] A = new int[] {0, 1, 2, 4, 6};

We declare and initialize destination array B like this:

double[] B = new double[5];

Let's copy four elements of source array A to destination array B by calling the built-in copy method of System.Array as follows:

Array.Copy(A, B, 4);

Note that the last element of destination array A is 0, because we copied only the first four elements from array A.

Listing 11.7 CopyArray1.cs: Copying Arrays in C#
class CopyArray1{
  public static void Main(){
    int[] A = new int[] {0, 1, 2, 4, 6};
    double[] B = new double[5];

    Array.Copy(A, B, 4);
    Console.Write("The elements of B are:");
    Console.Write(";bsn");
    for (int i = 0; i < B.Length; i ++)
      Console.Write("{0, 3}", B[i] );
  }
}

The output of Listing 11.7 is as follows:

The elements of B are:
0 1 2 4 0

Now let's look at the second version of the Array.Copy method in C# (Listing 11.8). This is similar to the System.arrayCopy function of Java. We initialize and declare the source array as follows:

int[] A = new int[] {0, 1, 2, 4, 6, 8, 10};

The objective of the listing is to replace the first three elements starting at index 1 (1,2,4) with the first three elements starting at index 0 (0,1,2). We use the following method call:

Array.Copy(A, 1, A, 0, 3);
Listing 11.8 CopyArray2.cs: Copying Arrays in C#, Version 2
class CopyArray2{
  public static void Main(){
  int[] A = new int[] {0, 1, 2, 4, 6, 8, 10};

  Array.Copy(A, 1, A, 0, 3);
  Console.Write("The elements of A are:");
  Console.Write("\n");
  for (int i = 0; i < A.Length; i ++)
    Console.Write("{0, 3}", A[i] );
    }
}

The output of Listing 11.8 is as follows:

The elements of A are:
1 2 4 4 6 8 10

The Java version of Array.Copy is almost the same, except for the method name. Listing 11.9 shows the Java version.

Listing 11.9 CopyArray2.Java: Copying Arrays in Java
class CopyArrays2
{
  public static void main(String[] args)
  {
    int[] A = new int[] {0, 1, 2, 4, 6, 8, 10};
    System.arraycopy(A, 1, A, 0, 3);
    System.out.print("The elements of A are:");
    System.out.print(";bsn");
      for (int i = 0; i < A.length; i ++)
        System.out.print(A[i] + " ");
  }
}


11.6 Arrays as Collections

C# arrays are first-class objects, and therefore all methods that apply to the canonical System.Object class can also be applied to a regular array. C# arrays are of type ICollection (in the System.Collections namespace); in contrast, Java arrays are not formally tied to the java.util.Collection package. Arrays are not thread-safe, and therefore operations that modify arrays should use the SyncRoot property of the array to lock the array object, as shown in the following code snippet. (Note the use of the foreach construct.) Because the class Array implements the ICollection and IList interfaces, many of the properties and methods of the class come from these interfaces.

int[] arr = new int[] {5,5,5,5,5,5};
lock( arr.SyncRoot ) {
  foreach ( Object item in arr ) {
  }
}


11.7 Summary

Both C# and Java treat arrays as objects; arrays are allocated on the heap. In C# all arrays are of the type System.Array class. Like the java.util.Arrays class, the System.Array class has several static utility methods for manipulating arrays. C# arrays can be jagged, rectangular, one-dimensional, or multidimensional. In C#, arrays are also collections and implement the IList and ICollection interfaces.


Chapter 12. Processing Strings

Strings are as ubiquitous in C# as they are in Java, so it's important for Java programmers to understand the concept of string processing in C#. The System.String class of C# models String objects just as the java.lang.String class of Java does, but this doesn't mean that C# and Java have a similar interface. Let's explore the string operations in C# to see how they differ from those in Java.


12.1 The System.String Class

Java and C# strings are immutable, and therefore the string instance cannot be modified. Instead, a new instance containing the modification is created whenever a method is called on a string instance that tries to modify it. The .NET Framework class library gives the following class declaration for the System.String class. (If you prefer, you can skip this section and use it as a reference when you start using some of the methods in this class.)

[Serializable]
public sealed class String : IComparable, ICloneable,
IConvertible, IEnumerable

The IComparable interface is similar to the java.lang.Comparable interface. An object implements this interface in order to compare itself to another object of the same type. The interface contains a CompareTo method, which returns a value of –1, 0, or 1 depending on whether the instance is less than, equal to, or greater than the object passed to the argument. Because a string is a sequence of characters, this class implements the IEnumerable interface, which returns an IEnumerator. This interface lets you iterate over the sequence of characters. The IConvertible interface defines methods that convert the value of the implementing reference or value type to a Common Language Runtime type that has an equivalent value. The CLR typically exposes the IConvertible interface through the Convert class.

Listing 12.1 shows how the String interfaces can be useful.

Listing 12.1 Using the System.String Interfaces (C#)
using System;
using System.Collections;

public class Test {

  public static void Main(string[] args) {

    TestIComparable();
    TestIEnumerable();
    TestICloneable();
    TestIConvertible();
  }
  private static void TestIComparable() {
    String s1 = "C#";
    String s2 = "Java";
    Console.WriteLine("Comparing C# and Java
                       "+s1.CompareTo(s2));
  }
  private static void TestICloneable() {
    String s1 = "C#";
    String s2 = (String) s1.Clone();
    Console.WriteLine("(s1 Equals s2)? "+s1.Equals(s2));
    Console.WriteLine("(s1 == s2)? " + (s1 == s2));
  }
  private static void TestIEnumerable() {
    String s1 = "abcdefghijklmnopqrstuvwxyz";
    IEnumerator enums = s1.GetEnumerator();
    while (enums.MoveNext()) {
      Console.Write(enums.Current);
    }
  }
  private static void TestIConvertible() {
    String s = "12345";
    try {
      Console.WriteLine(Convert.ToInt16(s));
      Console.WriteLine(Convert.ToInt32(s));
      Console.WriteLine(Convert.ToInt64(s));
      Console.WriteLine(Convert.ToDecimal(s));
      Console.WriteLine(Convert.ToDouble(s));
      Console.WriteLine(Convert.ToBoolean(s));
    } catch (Exception e) {
      Console.WriteLine(e.Message);
    }
  }
}

The output of Listing 12.1 is as follows:

Comparing C# and Java -1
abcdefghijklmnopqrstuvwxyz(s1 Equals s2)? True
(s1 == s2)? True
12345
12345
12345
12345
12345
String was not recognized as a valid Boolean.

Note that the Convert class is a utility class found in the System namespace that can be used to convert from one data type to another. Converting the string 12345 to a bool throws an exception, and this is indicated by the last line of the output.

The String class is declared as sealed, which is similar to the final declaration of Java strings. The System.String class cannot be subclassed. The System.String class is thread-safe. Thread safety may be inconsequential for this class, considering that objects of this class cannot be changed by any thread. Tables 12.1 and 12.2 list the constructors and methods, respectively, of the System.String class.

String constructors are seldom used to initialize strings. A traditional approach is to assign the string reference to a string literal; the runtime automatically creates a string object and makes the reference point to this string object. The C# runtime optimizes creation of Strings (just as the JVM does) through the process of interning; essentially, this means that strings with the same value are represented by one object in the runtime, with multiple references pointing to the same object. This technique minimizes object creation, given that Strings are ubiquitous. Both the CLR and the JVM perform this optimization internally. One constructor that Java programmers may miss in C# is the one that takes a string literal.

Table 12.1. CLS-Compliant Public Constructors of the String Class

Constructor

Description

public String(char[])

Initializes a new instance of the String class to the value indicated by an array of Unicode characters

public String(char, int)

Initializes a new instance of the String class to the value indicated by a specified Unicode character repeated a specified number of times

public String(char[], int, int)

Initializes a new instance of the String class to the value indicated by an array of Unicode characters, a starting character position within that array, and a length

Table 12.2 shows the CLS-compliant public methods of the String class. The compare methods make a lexical comparison of the compared string instances. The int returned in all the compare methods is 0 if the strings are equal, –1 if the first string is less than the second one, and 1 if the first string is greater than the second string. Note that all the compare methods base their comparison on comparing the values of the strings.

Table 12.3 shows the compare methods of the String class. The CompareTo method compares one String instance to another. Note that this method is not static and is defined on the string object itself. Again, the method makes a lexical comparison, resulting in 0 if the two objects are equal, –1 if the instance is less than the value, and 1 if the instance is greater than the value.

Table 12.4 shows the concatenation methods of the String class. Notice that the Concat methods are all static. Two strings concatenated twice by the same method will yield two different string instances. You can test this by using the = = operator to compare the object instances, as shown in Listing 12.2.

Table 12.2. CLS-Compliant Methods of the String Class

Constructor/Public Method

Description

public object Clone()

Returns a reference to a copy of String.

public static int Compare(string, string)

Compares two String objects.

public static int Compare(string, string, bool)

Compares two strings, ignoring or honoring their case.

public static int Compare(string, string, bool, CultureInfo)

Compares two specified String objects, ignoring or honoring their case, and honoring culture-specific information about their formatting.

public static int Compare(string, int, string, int, int)

Compares substrings of two specified String objects.

public static int Compare(string, int, string, int, int, bool)

Compares substrings of two specified String objects, ignoring (bool = true) or honoring (bool = false) their case.

public static int Compare(string, int, string, int, int, bool, CultureInfo);

Compares substrings of two specified String objects, ignoring or honoring their case, and honoring culture-specific information about their formatting. CultureInfo is similar to the Locale information in Java.

public static int CompareOrdinal (string, string)

Compares two specified String objects without considering the local language or culture.

public static int CompareOrdinal (string, int, string, int, int)

Compares substrings of two specified String objects without considering the local language or culture. Parameters specify the length and starting positions of the substrings.

Listing 12.2 Comparing Concatenated Instances of a String (C#)
using System;
public class Test {
  public static void Main(string[] args) {
    String s = String.Concat("a", "b");
    String t = String.Concat("a", "b");
    Console.WriteLine(s == t);
  }
}

Table 12.3. Compare Methods of the String Class

Method

Description

public int CompareTo(object)

Compares this instance with a specified Object

public int CompareTo(string)

Compares this instance with a specified String object

Table 12.4. Concatenation Methods of the String Class

Method

Description

public static string Concat(object)

Creates the String representation of a specified object

public static string
  Concat(params object[])

Concatenates the String representations of the elements in a specified Object array

public static string
  Concat(params string[]);

Concatenates the elements of a specified String array

public static string
  Concat(object, object)

Concatenates the String representations of two specified objects

public static string Concat(string,
  string)

Concatenates two strings

public static string
  Concat(object, object, object)

Concatenates the String representations of three specified objects

public static string Concat(string,
  string, string);

Concatenates three strings

public static string Concat(string,
  string, string, string)

Concatenates four strings

The output of Listing 12.2 is

True

The CLR does string interning in which String instances are reused. In Listing 12.2 we created two different strings (s and t) by concatenating the string literals a and b. The two string instances s and t point to the same string object.

Table 12.5 shows an assortment of methods in the String class. Most of the methods in Table 12.5 are self-explanatory, and therefore we will not give examples for each of the member methods. Remember that methods that seem to modify the string return a new string that is different from the original string. Listing 12.3 demonstrates this point.

Listing 12.3 Testing String Equality (C#)
using System;

public class StringTest {

  public static void Main(string[] args) {
    string s = "hello ";
    String t = s.Insert(s.Length-1, "World").Remove(s.Length-
    1,5);
    Console.WriteLine(s == t);
    Console.WriteLine(s.Equals(t));
    Console.WriteLine((object)s == (object)t);
  }
}

The output of Listing 12.3 is as follows:

True
True
False

Although s and t have the same value, they are two different string object instances (not interned) because we made some modifications on the string instance s.

Table 12.5. An Assortment of Methods in the String class

Method

Description

public static string
  Copy(string str)

Creates a new instance of String with the same value as a specified String.

public void CopyTo (
  int sourceIndex,
  char[] destination,
  int destinationIndex,
  int count
  )

Copies a specified number of characters from a specified position in this instance to a specified position in an array of Unicode characters.

public bool EndsWith(
  string value
  )

Determines whether the end of this instance matches the specified String.

public override bool
  Equals(object)

Determines whether this instance of String and a specified object, which must be a String, have the same value.

public override bool
  Equals(string)

Determines whether this instance and a specified String have the same value.

public static bool
  Equals(string, string)

Determines whether two specified String objects have the same value.

public static string
  Format(string, object)

Replaces the format specification in a specified String with the textual equivalent of the value of a specified Object instance.

public static string
  Format(string, params
  object[])

Replaces the format specification in a specified String with the textual equivalent of the value of a corresponding Object instance in a specified array.

public static string
  Format(IFormatProvider,
  string, params object[])

Replaces the format specification in a specified String with the textual equivalent of the value of a corresponding Object instance in a specified array. A specified parameter supplies culture-specific formatting information.

public static string
  Format(string, object,
  object)

Replaces the format specification in a specified String with the textual equivalent of the value of two specified Object instances.

public static string
  Format(string, object,
  object, object)

Replaces the format specification in a specified String with the textual equivalent of the value of three specified Object instances.

public CharEnumerator
  GetEnumerator()

Retrieves an object that can iterate through the individual characters in this instance.

public override int
  GetHashCode()

Returns the hash code for this instance.

public Type GetType()

Gets the type of the current instance.

public TypeCode
  GetTypeCode()

Returns the TypeCode for class String.

public int
  IndexOf(char)

Returns the index of the first occurrence of the specified Unicode character in this instance.

public int
  IndexOf(string)

Returns the index of the first occurrence of the specified String in this instance.

public int IndexOf(char,
  int)

Returns the index of the first occurrence of the specified Unicode character in this instance. The search starts at a specified character position.

public int
  IndexOf(string, int)

Returns the index of the first occurrence of the specified String in this instance. The search starts at a specified character position.

public int IndexOf(char,
  int, int)

Returns the index of the first occurrence of the specified character in this instance. The search starts at a specified character position and examines a specified number of character positions.

public int
  IndexOf(string,
  int, int)

Returns the index of the first occurrence of the specified String in this instance. The search starts at a specified character position and examines a specified number of character positions.

public int
  IndexOfAny(char[])

Returns the index of the first occurrence in this instance of any character in a specified array of Unicode characters.

public int
  IndexOfAny(char[], int)

Returns the index of the first occurrence in this instance of any character in a specified array of Unicode characters. The search starts at a specified character position.

public int
  IndexOfAny(char[],
  int, int)

Returns the index of the first occurrence in this instance of any character in a specified array of Unicode characters. The search starts at a specified character position and examines a specified number of character positions.

public string Insert(
  int startIndex,
  string value)

Inserts a specified instance of String at a specified index position in this instance.

public static string
  IsInterned(string str)

Retrieves a reference to a specified String.

public static string
  Join (string
  separator, string[]
  value, int startIndex,
  int count
  )

Concatenates a specified separator String between each element of a specified String array, yielding a single concatenated string.

public static string
  Join(string, string[],
  int, int)

Concatenates a specified separator String between each element of a specified String array, yielding a single concatenated string. Parameters specify the first array element and the number of elements to use.

public int
  LastIndexOf(char)

Returns the index position of the last occurrence of a specified Unicode character within this instance.

public int
  LastIndexOf(string)

Returns the index position of the last occurrence of a specified String within this instance.

public int
  LastIndexOf(char,
  int)

Returns the index position of the last occurrence of a specified Unicode character within this instance. The search starts at a specified character position.

public int
  LastIndexOf(string,
  int)

Returns the index position of the last occurrence of a specified String within this instance. The search starts at a specified character position.

public int
  LastIndexOf(char,
  int, int)

Returns the index position of the last occurrence of the specified Unicode character in a substring within this instance. The search starts at a specified character position and examines a specified number of character positions.

public int
  LastIndexOf(string,
  int, int)

Returns the index position of the last occurrence of a specified String within this instance. The search starts at a specified character position and examines a specified number of character positions.

public int
  LastIndexOfAny(char[])

Returns the index position of the last occurrence in this instance of one or more characters specified in a Unicode array.

public int
  LastIndexOfAny
  (char[], int)

Returns the index position of the last occurrence in this instance of one or more characters specified in a Unicode array. The search starts at a specified character position.

public int
  LastIndexOfAny(char[],
  int, int)

Returns the index position of the last occurrence in this instance of one or more characters specified in a Unicode array. The search starts at a specified character position and examines a specified number of character positions.

public string
  PadLeft(int)

Right-aligns the characters in this instance, padding with spaces on the left for a specified total length.

public string
  PadLeft(int, char)

Right-aligns the characters in this instance, padding on the left with a specified Unicode character for a specified total length.

public string
  PadRight(int)

Left-aligns the characters in this string, padding with spaces on the right for a specified total length.

public string
  PadRight(int, char)

Left-aligns the characters in this string, padding on the right with a specified Unicode character for a specified total length.

public string Remove(
  int startIndex,
  int count
  )

Deletes a specified number of characters from this instance beginning at a specified position.

public string
  Replace(char, char)

Replaces all occurrences of a specified Unicode character in this instance with another specified Unicode character.

public string
  Replace(string, string)

Replaces all occurrences of a specified String in this instance with another specified String.

public string[]
  Split(char[])

Identifies the substrings in this instance that are delimited by one or more characters specified in an array, and then places the substrings into a String array.

public string[]
  Split(char[], int)

Identifies the substrings in this instance that are delimited by one or more characters specified in an array, and then places the substrings into a String array. A parameter specifies the maximum number of array elements to return.

public bool
  StartsWith(string
  value)

Determines whether the beginning of this instance matches the specified String.

public string
  Substring(int)

Retrieves a substring from this instance. The substring starts at a specified character position.

public string
  Substring(int, int)

Retrieves a substring from this instance. The substring starts at a specified character position and has a specified length.

public char[]
  ToCharArray()

Copies the characters in this instance to a Unicode character array.

public char[]
  ToCharArray(int, int)

Copies the characters in a specified substring in this instance to a Unicode character array.

public string ToLower()

Returns a copy of this String in lowercase.

public string
  ToLower(CultureInfo)

Returns a copy of this String in lowercase, taking into account specified culture-specific information.

public override
  string ToString()

Returns this instance of String; no actual conversion is performed.

public string
  ToString(IFormat
  Provider)

Returns this instance of String; no actual conversion is performed.

public string ToUpper()

Returns a copy of this String in uppercase, using default properties.

public string
  ToUpper(CultureInfo)

Returns a copy of this String in uppercase, taking into account culture-specific information.

public string Trim()

Removes all occurrences of white space characters from the beginning and end of this instance.

public string
  Trim(params char[])

Removes all occurrences of a set of characters specified in a Unicode character array from the beginning and end of this instance.

public string TrimEnd(
  params char[]
  trimChars)

Removes all occurrences of a set of characters specified in a Unicode character array from the end of this instance.

public string
  TrimStart(params
  char[] trimChars)

Removes all occurrences of a set of characters specified in a Unicode character array from the beginning of this instance.


12.2 Initializing Strings

As stated earlier, the optimal way to initialize strings is by assigning a string reference to a string literal. Because of transparent optimizations performed by the runtime, it is imperative to know when two strings are equal. The semantics of equality for strings exist at different levels. Strings can be compared by comparing string instances, object instances, and their values. The default comparison operations in both C# and Java compare the strings' values instead of the object references. This is a reasonable feature considering that strings are commonly used, and because of interning, it makes little sense to compare strings as string instances or object instances. Listing 12.4 illustrates some of these semantic differences.

Listing 12.4 Initializing and Comparing C# Strings
using System;

public class StringTest {

  public static void Main(string[] args) {
    char[] c = new char[] {'H','e','l','l','o'};
    string[] strings = new string[4];
    strings[0] = "Hello";
    strings[1] = "World";
    strings[2] = "Hello";
    strings[3] = new string(c);

    Console.WriteLine("-----[ Comparing strings using ==
                     operator of System.String ]-----");
    for (int i =0; i < strings.Length; ++i) {
      for (int j = i+1; j < strings.Length; ++j) {
        Console.WriteLine("{0} = {1} {2}",i,j,strings[i] ==
                                                  strings[j]);
      }
    }
    Console.WriteLine("-----[ Comparing strings using ==
                     operator of System.Object ]-----");
    for (int i =0; i < strings.Length; ++i) {
      for (int j = i+1; j < strings.Length; ++j) {
          Console.WriteLine("{0} = {1}
             {2}",i,j,(object)strings[i] ==
               (object)strings[j]);
      }
    }
    Console.WriteLine("-----[ Comparing String using the Equals
                     method ]-----");
    for (int i =0; i < strings.Length; ++i) {
      for (int j = i+1; j < strings.Length; ++j) {
        Console.WriteLine("{0} = {1}
            {2}",i,j,strings[i].Equals(strings[j]));
      }
    }
  }
}

The output of Listing 12.4 is as follows:

Using == operator of System.String]
0=1 False
0=2 True
0=3 True
1=2 False
1=3 False
2=3 True
Using == operator of System.Object ]
0=1 False
0=2 True
0=3 False
1=2 False
1=3 False
2=3 False
Using the String.Equals method
0=1 False
0=2 True
0=3 True
1=2 False
1=3 False
2=3 True

In C#, the == operator of the System.Object method compares the references of the objects and returns true if the two references point to the same object, and false otherwise. The == operator is overloaded in the System.String class to compare the value of the strings instead of the actual objects they refer to. Therefore, in Listing 12.4, strings[3] == strings[0] returns true because they have the same string value. However, when both strings[0] and strings[3] are cast to the object and then compared using the == operator, they return false. This is because strings[3] points to a different string object.

C# also has the Equals method, which is similar in behavior to the == operator. Notice in the output of Listing 12.4 that comparing strings using the Equals method and the overloaded == operator gives the same results.

Java programmers must be particularly careful when dealing with the == operator and C# strings.

Note that in Java the equals method functions the same way as the == operator of C#. The equals method is defined on java.lang.Object, and by default it compares the object references. However, the java.lang.String class overrides the equals method and compares the String values. Both Java and C# support String interning, so both the JVM and the CLR create only one object from a String that has the same value. This is an optimization feature so that different String references pointing to the same literal value are internally stored as pointing to the same String object. The process of interning is transparent in both C# and Java.


12.3 The + Operator and Strings

You can manipulate strings in C# using the StringBuilder class, which is equivalent to the java.lang.StringBuffer class of Java. StringBuilder is a sealed class and therefore cannot be subclassed. Similarly, the StringBuffer class is final and therefore cannot be subclassed. However, instances of the StringBuilder class are not guaranteed to be thread-safe. In Java, StringBuffer methods are synchronized wherever necessary. Listing 12.5 shows StringBuilder in action.

Listing 12.5 A Simple StringBuilder Usage (C#)
using System;
using System.Text;
public class StringTest {
  public static void Main(string[] args) {
    StringBuilder t = new
StringBuilder().Append("It's").Append("My").Append("Life");
    Console.WriteLine(t);
  }
}

The output of Listing 12.5 is

It'sMyLife

The Console.WriteLine() method takes the StringBuilder object as a parameter, and the runtime calls the ToString() method on the StringBuilder instance. The ToString() method of the StringBuilder returns the actual string stored by the builder.

Java developers often assert that Java optimizes string concatenation. In Java, using the + operator on strings is wasteful, especially if some strings that are to be added are dynamic in nature. Unless all the strings that are added are static, the + operator in Java creates a new string every time it is to be added to the old string, and therefore a long string made up by adding several dynamic strings ends up creating many temporary strings (which are eventually garbage-collected). The Java compiler, though, is smart enough to optimize the addition operator when all the strings that are being added are literals.

Listing 12.6 is a Java program that shows three ways to add strings. In the first approach, all the strings that are added are literals. Behind the scenes, the compiler translates approach 1 to approach 2, and hence the two approaches are the same. Approach 1 is more readable and hence is preferred if your string is made up of only literals. Approach 3 is where a StringBuffer would be useful, because in this case, for every + operator the runtime creates a new String instance by concatenating to the preceding string.

Listing 12.6 Adding Strings (Java)
public class StringTesting {
  public static void main(String[] args) {
    //Approach 1
    String s1= "It's "+"my "+"life";
    //Approach 2
    String s2 = new StringBuffer() .append("It's ") .append("my
               ") .append("life");
    //Approach 3
    Strings s3= "It's ";
    String s4 = s3+"my "+" life";
  }
}

Listing 12.7 illustrates the same thing in C#.

Listing 12.7 C# Version of Listing 12.6
using System;
using System.Text;
public class StringTest {
  public static void Main(string[] args) {
    //Approach 1
    String s1 = "It's "+"My "+"Life";
    //Approach 2
    String s2 = new StringBuilder() .Append("It's")
                .Append("My") .Append("Life") .ToString();
    //Approach 3
    String s3 = "It's";
    String s4 = s3 + "My "+"Life";
  }
}

In general, method calls are considered expensive; hence, if something can be achieved with fewer method calls, that approach is faster. We will compare the three approaches (Listing 12.7) of concatenating strings by comparing how many method calls each approach translates into. To do so we need to look at the Intermediate Language (IL) generated by Listing 12.7, as shown in Listing 12.8.

Listing 12.8 IL of Listing 12.7
1 // Code size 66 (0x42)
2 .maxstack 2
3 .locals init ([0] string s1,
          [1] string s2,
          [2] string s3,
          [3] string s4)
4 IL_0000: ldstr "It's My Life "
5 IL_0005: stloc.0
6 IL_0006: newobj instance void
[mscorlib]System.Text.StringBuilder::.ctor()
7 IL_000b: ldstr "It's "
8 IL_0010: call instance class
[mscorlib]System.Text.StringBuilder
[mscorlib]System.Text.StringBuilder::Append(string)
9 IL_0015: ldstr "My "
10 IL_001a: callvirt instance class
[mscorlib]System.Text.StringBuilder
[mscorlib]System.Text.StringBuilder::Append(string)
11 IL_001f: ldstr "Life"
12 IL_0024: callvirt instance class
[mscorlib]System.Text.StringBuilder
[mscorlib]System.Text.StringBuilder::Append(string)
13 IL_0029: callvirt instance string
[mscorlib]System.Text.StringBuilder::ToString()
14 IL_002e: stloc.1
15 IL_002f: ldstr "It's "
16 IL_0034: stloc.2
17 IL_0035: ldloc.2
18 IL_0036: ldstr "My Life"
19 IL_003b: call string mscorlib]System.String::Concat(string,
                                                    string)
20 IL_0040: stloc.3
21 IL_0041: ret
22} // end of method StringTest::Main

Line 4 of Listing 12.8 shows the IL of approach 1 of Listing 12.7. Instruction IL_0000 of Listing 12.8 shows that the C# compiler automatically concatenates the literals for approach 1. There are no method calls involved, and the C# compiler merely pushes the concatenated string on to the Main method stack.

Approach 2 is shown from IL_0006 to IL_002e (line 6 to line 14). Notice that the StringBuilder approach involves five method calls (one constructor, three append methods, and one ToString() method). Compared with approach 1, approach 2 is inefficient for adding literals. Earlier we said that the Java compiler treats approach 1 and approach 2 as similar. The CLR, however, does not treat the two approaches as similar. In fact, the + operator of the String has no relation to the StringBuilder class.

Approach 3 is shown between IL_002f and IL_0040 (line 15 to line 20). Notice how the compiler concatenates the My and Life literals and uses the static function String.concat() to concatenate the It's literal to the new My Life literal. If the compiler had instead used a StringBuilder, it would have had to create a StringBuilder (constructor called) and then call the Append() method, making two method calls.

Clearly, you should use the + operator in C# whenever possible in concatenating literals, and you can also safely use it when adding dynamic strings to literals. In Java, however, the use of StringBuffer is the only way to optimize the addition of dynamic strings to literals. It uses extra method calls, but that is less expensive than creating new String instances, something that would occur if we used the + operator.

Now let's look at splitting (also called tokenizing) strings instead of concatenating them. Listing 12.9 gives a C# example of splitting strings. Listing 12.10 is the Java equivalent. Note that in Java, the access to the individual tokens is sequential. The C# API is flexible because it allows splitting the string using multiple delimiters.

Listing 12.9 Tokenizing Strings in C#
using System;
using System.Text;

public class StringTesting {
  public static void Main(string[] args) {
    string s = "I equivocally deny the claim, but I will agree
                to it under other circumstances";
    string[] tokens = s.Split(new char[] {' ',','} );
    foreach (string t in tokens) {
      Console.WriteLine(t);
    }
  }
}

The output of Listing 12.9 is as follows:

I
equivocally
deny
the
claim

but
I
will
agree
to
it
under
other
circumstances

Notice that the string is split into words, and where there is a "," in the string, it is replaced by a blank character. Java's java.util.StringTokenizer tokenizes a string based on a single delimiter, as shown in Listing 12.10.

Listing 12.10 Java Version of Tokenizing Strings
import java.util.StringTokenizer;

public class StringTest {
  public static void main(String[] args) {
  String s = "I equivocally deny the claim, but I will agree to
              it under other circumstances";
  StringTokenizer st1 = new StringTokenizer(s, ",");
  while (st1.hasMoreTokens()) {
    StringTokenizer st2 = new StringTokenizer(st1.nextToken());
    while (st2.hasMoreTokens()) {
      System.out.println(st2.nextToken());
    }
  }
 }
}


12.4 Regular Expressions

Pattern matching using regular expressions is a powerful technique for advanced splitting and fetching of content from strings. C# provides classes in the System.Text namespace for doing pattern matching.

Note that this section assumes that you know the basics of pattern matching. If you are new to pattern matching, you should take a look at perl.com for introductory material. A detailed discussion of regular expression techniques is beyond the scope of this book. If you would like to explore regular expressions, we recommend the book Mastering Regular Expressions, by Jeffrey E.F. Friedl (O'Reilly, 2002).

As of JDK 1.3, Java did not have a built-in pattern matching API. However, JDK 1.4 has support for pattern matching. The C# pattern matching class does not interpret the pattern for every match; instead, it compiles a small program, which is then run to do the pattern match. This magic of compiling a small program in memory is achieved using the classes of the System.Reflection.Emit namespace. (Reflection is discussed in detail in Chapter 22.) Using the classes of the Reflection.Emit namespace, you can directly write IL for the functions of that class. It's a powerful technique used by the System.Text.RegularExpressions class to create a custom-matching engine.

The regular expression (System.Text.RegularExpressions.Regex) is compiled by the compiler to a stand-alone assembly using the information from System.Text.RegularExpressions.RegexCompilationInfo. Table 12.6 lists some of the key classes of the namespace.

Table 12.7 lists the static methods found in the Regex class. These methods are the most frequently used methods when you deal with pattern matching in C#.

Listing 12.11 shows the use of some of the Regex class's static methods.

Listing 12.11 Regex Methods in Action (C#)
using System;

using System.Text.RegularExpressions;

public class StringTesting {

  public static void Main(string[] args) {
    string s = "mammagammarammadramma";
    string pattern = "mm";

    //IsMatch
    Console.WriteLine(Regex.IsMatch(s, "kk"));

    //Get matches
    MatchCollection matches = Regex.Matches(s, pattern);
    foreach (Match m in matches) {
      Console.WriteLine(m);
    }
    //Replace
    Console.WriteLine(Regex.Replace(s, pattern, "kk"));
    //Split
    foreach (string part in Regex.Split(s,pattern)) {
      Console.Write(part+" ");
    }
  }
}

Table 12.6. Classes Related to Pattern Matching

Class

Description

Regex

Represents an immutable regular expression.

Capture

Represents the results from a single subexpression capture. Capture represents one substring for a single successful capture.

Group

Represents the results from a single capturing group. A capturing group can capture zero, one, or more strings in a single match because of quantifiers, so Group supplies a collection of Capture objects.

Match

Represents the results from a single regular expression match.

Table 12.7. Static Methods in the Regex Class

Method

Description

Escape

Escapes a minimal set of metacharacters (\, *, +, ?, |, {, [, ( , ), ^, $, ., #, and white space) by replacing them with their escape codes.

IsMatch

Indicates whether the regular expression finds a match in the input string.

Match

Searches an input string for an occurrence of a regular expression and returns the precise result as a single Match object.

Matches

Searches an input string for all occurrences of a regular expression and returns all the successful matches as if Match were called numerous times.

Replace

Replaces all occurrences of a character pattern defined by a regular expression with a specified replacement character string.

Split

Splits an input string into an array of substrings at the positions defined by a regular expression match.

Unescape

Unescapes any escaped characters in the input string.

Here is the output of Listing 12.11 (numbered for convenience):

1. False
2. mm
3. mm
4. mm
5. mm
6. makkagakkarakkadrakka
7. ma aga ara adra a

Line 1 is obvious because the term kk does not exist in the target string s, so a False value is returned. Lines 2–5 are the ToString() representation of the Match object. There are four instances of the mm string in the string s, and hence four matches are printed. Line 6 shows that the pattern mm is replaced by the string kk in the string s. In line 7, the string s is split using the mm pattern.

Consider the string mnmkkmnmkkmnmkkmnmkk and the pattern @(mnm (kk)?)+. Listing 12.12 shows the use of the Groups and Capture classes.

Listing 12.12 Using Groups in Regex (C#)
using System;
using System.Text.RegularExpressions;

public class StringTesting {

  public static void Main(string[]args) {

    string text = " mnmkkmnm1mnmkkmnm2mnmkkmnm3";
    string pat = @"(mnm(kk)?)+";
    // use 'x' modifier to ignore comments
    Regex r = new Regex(pat);
    int[] gnums = r.GetGroupNumbers();
    Match m = r.Match(text);
    while (m.Success) {
      for (int i = 1; i < gnums.Length; i++) {
        Group g = m.Groups[gnums[i]];
        Console.WriteLine("Group"+gnums[i]+"=
                  ["+g.ToString()+"]");
        CaptureCollection cc = g.Captures;
        for (int j = 0; j < cc.Count; j++) {
          Capture c = cc[j];
          Console.WriteLine("Capture" + j +
                    "=["+c.ToString() + "] Index=" + c.Index
                        + " Length=" + c.Length);
        }
      }
      m = m.NextMatch();
    }
  }
}

The output of Listing 12.12 is as follows:

Group1=[mnm]
  Capture0=[mnmkk] Index=0 Length=5
  Capture1=[mnm] Index=5 Length=3
Group2=[kk]
  Capture0=[kk] Index=3 Length=2
Group1=[mnm]
  Capture0=[mnmkk] Index=9 Length=5
  Capture1=[mnm] Index=14 Length=3
Group2=[kk]
  Capture0=[kk] Index=12 Length=2
Group1=[mnm]
  Capture0=[mnmkk] Index=18 Length=5
  Capture1=[mnm] Index=23 Length=3
Group2=[kk]
  Capture0=[kk] Index=21 Length=2

The first capture group is marked by the first bracket, and then the expression will match an mnm if the Regex engine matches the expression to the one found in the text. Then the second capture group, marked by the second bracket, begins, but the definition of the first capture group is still ongoing. This tells us that the first group must match mnmkk and the second group would match only the kk. So if you decide to make the kk match an optional occurrence with the ? metacharacter, then mnm or mnmkk will be matched.

Next, you end the first group and specify the + metacharacter to ask the expression to match one or more occurrences. Now let's examine what happens during the matching process. First, create an instance of the expression by calling the Regex constructor, which is also where you specify your options. By turning on the x option, you make the expression ignore the comments and all white space:

Regex r = new Regex(pat);

Next, get the list of group numbers (gnums) defined in this regular expression:

int[] gnums = r.GetGroupNumbers();

Next, perform the first match. Then enter a loop, testing the success of the current match.

Match m = r.Match(text);
while (m.Success) (
)

The next step is to iterate through the list of groups starting at group 1. The reason you do not use group 0 in this sample is that group 0 is the fully captured match string, and what you usually (but not always) want to pick from a string is a subgroup. You might use group 0 if you wanted to collect the fully matched string as a single string.

for (int i = 1; i < gnums.Length; i++) {
    Group g = m.Groups[gnums[i]]

    Console.WriteLine("Group"+gnums[i]+"=["+g.ToString
}

Within each group, iterate through the CaptureCollection.

CaptureCollection cc = g.Captures;
for (int j = 0; j < cc.Count; j++) {
  Capture c = cc[j];
  Console.WriteLine("Capture" + j + "=["+c.ToString()
      + "] Index=" + c.Index + " Length=" + c.Length);
}

There is usually only one capture per match, per group, but in this case, for Group1, two captures show: Capture0 and Capture1. And if you had asked for only the ToString of Group1, you would have received mnm, although it also matched mnmkk. The value of the group ToString will be the value of the last Capture in its CaptureCollection. This is the expected behavior, and if you want the match to stop after mnm, you remove the + from the expression, telling the Regex engine to match on only the expression.

Regular expressions provide a powerful technique to do any kind of string manipulation. The key to exploiting C#'s Regex class is to understand the syntax of regular expression patterns. Once you understand how patterns are interpreted, using the Regex classes is trivial. A good resource on routine regular expressions is the book Perl Cookbook, by Tom Christiansen and Nathan Torkington (O'Reilly, 1998).


12.5 Summary

  • Strings in C# are immutable. The String class cannot be subclassed, and, just as in Java, the CLR optimizes String objects through the process of interning. This means that strings having the same value are represented by the same String instance object.

  • C#'s String.Equals() method is similar to Java's String.equals() method; they both equate the actual value of the string. The == operator in C# is overloaded and compares the string values instead of comparing object references as in Java. To compare the object references of the strings in C#, you use the == operator on the object class.

  • Unlike Java, adding C# string literals using the + operator does not result in the use of the StringBuilder class. In fact, using the + operator for concatenating C# strings (literal or dynamic) does not have the same object-creation overhead that Java has.

  • C# has built-in support for regular expressions. The classes in the System.Text.RegularExpression namespace can be used for pattern matching.


Chapter 13. Formatting Numbers, Strings, and Dates

Although computers don't care about data types being represented as 0's and 1's, humans do, and so formatting of the data is essential for meaningful display. Formatting allows you to take routine data types such as numbers, dates, times, and strings and represent them in a format that is suitable for the end user.


13.1 Formatting

Java programmers are familiar with the java.text and java.util packages, which have a bunch of classes that deal with formatting objects to strings. In Java, you implement formatting by specifying pattern strings. Pattern strings are string literals that have special characters that are interpreted by the runtime to format the data type. The special characters usually indicate the degree of precision, the type of formatting to be applied (based on the data type), and the positioning of the data in the formatted string. Depending on the data type being formatted (numbers, dates, or strings), formatting pattern strings differ. Pattern strings usually work in combination with locale information (java.util.Locale) and specifications of the symbols to use in displaying the rendered pattern. Java allows you to override the pattern string and the locale information at the time of formatting. Formatting in Java therefore relies on three cornerstone concepts: pattern strings, symbols, and locale information. All these can be specified in the method calls to any one of the subclasses of the java.text.Format class.

The cornerstone concepts in C# formatting are format specifiers (Java pattern strings) and format providers. Format specifiers are string literals of characters indicating how the formatting is to be done. Like Java pattern strings, format specifiers consist of characters that determine how the formatting is to take place. Unlike Java, which implements the locale and the symbol information in separate classes, C# combines the two in one format provider interface called IFormatProvider. Classes that implement this interface provide the necessary information about the symbols and the culture ("locale" in Javaspeak) to be used in rendering the data type using the format specifier. Like the JVM, the .NET runtime uses the default provider settings applicable to your culture when formatting data types, and hence you need not always specify the providers. You do have to specify the format specifier (pattern) in order to do any kind of formatting.

There are three format provider classes: NumberFormatInfo, Date TimeFormatInfo, and CultureInfo. You can also specify a custom provider by implementing the IFormatProvider interface.

Unlike Java, which has the java.text.Format class and its subclasses to actually do the formatting, C# formatting is built in using the ToString method on the data type itself. The .NET Framework standard data types—such as Int32, Int64, Single, Double, Enumeration, DateTime, and so on—have their ToString method overloaded to accept a format specifier or format provider.


13.2 Number Formatting

Table 13.1 lists the format specifiers for number formatting. You can format numeric types using the ToString() method on the data types. The standard format specifier string consists of a special character followed by a sequence of digits specifying precision.

13.2.1 Currency Formatting

Listing 13.1 shows currency formatting. The format specifier to be used is C or c.

Listing 13.1 Currency Formatting (C#)
using System;
using System.Globalization;
using System.IO;

public class NumericFormatting {

  static float NUMBER = 334.456F;

  public static void Main(string[] args) {
    TextWriter tw = Console.Out;
    PlainCurrencyFormatting(tw);
    CurrencyFormattingWithCustomProvider(tw);
    GreatBritainCurrencyFormatting(tw);
  }

  private static void PlainCurrencyFormatting(TextWriter tw) {
    tw.WriteLine(NUMBER.ToString("C"));
    tw.WriteLine(NUMBER.ToString("C6"));
  }

  private static void CurrencyFormattingWithCustomProvider
                                         (TextWriter tw) {
    NumberFormatInfo provider = new NumberFormatInfo();
    provider.CurrencyDecimalDigits = 3;
    provider.CurrencySymbol= "$";
    provider.CurrencyDecimalSeparator= "*";
    tw.WriteLine(NUMBER.ToString("c", provider));
  }

  private static void GreatBritainCurrencyFormatting(TextWriter
    tw) {
    CultureInfo culture =
           CultureInfo.CreateSpecificCulture("en-GB");
    NumberFormatInfo provider = culture.NumberFormat;
    tw.WriteLine(NUMBER.ToString("c", provider));
  }
}

Table 13.1. Format Specifiers for Numerical Formatting in C#

Format Character

Description

C,c

Currency

E,e

Scientific

F,f

Fixed point

G,g

General

N,n

Number

R,r

Roundtrip

X,x

Hexadecimal

The output of the Listing 13.1 is as follows:

$334.46
$334.456000
$334*456
£334.46

When no provider is specified, the number is formatted as $334.46, which uses the CultureInfo en-US (that is the equivalent of Locale.US). The $ symbol is used for the currency, and by default there are two digits after the decimal point, so the last digit (6) was the result of rounding. The third number in the output is a currency from a custom NumberFormatInfo. Note that the currency symbol had to be specified as $; it is not assumed to be $ by default. Because the plain instantiation of NumberFormatInfo is culture invariant, it makes no assumption about the currency symbol.

The next example is that of printing the same currency using the British pound. Note how the appropriate culture is obtained and the default NumberFormatInfo for that culture is used.

13.2.2 Scientific Formatting

The E and e format specifiers can be used to format numbers in the scientific notation m.dddE+xxx. One digit always precedes the decimal point, and the number of decimal places is specified by the precision specifier, with six places used as the default. The format specifier controls whether E or e appears in the output. Listing 13.2 shows an example of scientific formatting.

Listing 13.2 Scientific Formatting (C#)
using System;
using System.Globalization;
using System.IO;
public class ScientificFormatting {

  static double NUMBER = 2345334.456547554D;

  public static void Main(string[] args) {
    TextWriter tw = Console.Out;
    PlainFormatting(tw);
    FormattingWithCustomProvider(tw);
    CultureSpecificFormatting(tw);
  }

  private static void PlainFormatting(TextWriter tw) {
    tw.WriteLine(NUMBER.ToString("E"));
    tw.WriteLine(NUMBER.ToString("E2"));
    tw.WriteLine(NUMBER.ToString("E4"));
  }

  private static void FormattingWithCustomProvider(TextWriter
      tw) {
    NumberFormatInfo provider = new NumberFormatInfo();
    provider.NumberDecimalDigits = 2;
    tw.WriteLine(NUMBER.ToString("E", provider));
  }

  private static void CultureSpecificFormatting(TextWriter tw) {
      CultureInfo culture =
             CultureInfo.CreateSpecificCulture("fr-FR");
    NumberFormatInfo provider = culture.NumberFormat;
    tw.WriteLine(NUMBER.ToString("e", provider));
  }
}

The output of Listing 13.2 is as follows:

2.345334E+006
2.35E+006
2.3453E+006
2.345334E+006
2,345334e+006

In Listing 13.2, the first method, PlainFormatting, merely modifies the number of digits that are put after the decimal point using the format specifier. The second method, FormattingWithCustomProvider, tries to limit the number of decimal digits to 2, but the runtime does not do so because of the E/e format specifier. The last method shows how French scientists would denote a number in exponential form.

13.2.3 Number, Fixed-Point, and General Formatting

The number format specified by the N or n specifier formats a number into a string that has commas. By default, the number is formatted with two digits to the right of the decimal point. You can control this by specifying the number of digits after the format specifier; for example, N4 or n4 would mean four digits after the decimal point.

The fixed-point format is similar to the number format except that it does not put commas into the formatted string. The general format string converts the value to either the fixed-point or the scientific format, whichever is more compact. Listing 13.3 gives examples of the three formats.

Listing 13.3 Number, General, and Fixed-Point Formats (C#)
using System;
using System.Globalization;
using System.IO;
public class ScientificFormatting {

  static float NUMBER = 85.56546F;

  public static void Main(string[] args) {
    TextWriter tw = Console.Out;

    //Using the {N,n} format specifiers
    tw.WriteLine("The number format");
    PlainFormatting (tw, "n2");
    FormattingWithCustomProvider(tw, "n");
    CultureSpecificFormatting(tw, "n");

    //Using the {G,g} format specifiers
    tw.WriteLine("The general format");
    PlainFormatting (tw, "g2");
    FormattingWithCustomProvider(tw, "g");
    CultureSpecificFormatting(tw, "g");

    //Using the fixed-point {F,f} format specifiers
    tw.WriteLine("The fixed-point format");
    PlainFormatting (tw, "f2");
    FormattingWithCustomProvider(tw, "f");
    CultureSpecificFormatting(tw, "f");

    //Using the % format specifiers
    tw.WriteLine("The % format");
    PlainFormatting (tw, "###.##%");
    FormattingWithCustomProvider(tw, "###.##%");
    CultureSpecificFormatting(tw, "###.##%");
  }

  private static void PlainFormatting(TextWriter tw, string
    pattern){
    tw.WriteLine(NUMBER.ToString(pattern));
  }

  private static void FormattingWithCustomProvider(TextWriter
    tw, string pattern) {
    NumberFormatInfo provider = new NumberFormatInfo();
    provider.PercentDecimalDigits = 2;
    provider.PercentSymbol = "&";
    tw.WriteLine(NUMBER.ToString(pattern, provider));
  }

  private static void CultureSpecificFormatting(TextWriter tw,
    string pattern) {
    CultureInfo culture =
           CultureInfo.CreateSpecificCulture("fr-FR");
    NumberFormatInfo provider = culture.NumberFormat;
    tw.WriteLine(NUMBER.ToString(pattern, provider));
  }
}

The output of Listing 13.3 is as follows:

The number format
85.57
85.57
85,57
The general format
86
85.56546
85,56546
The fixed-point format
85.57
85.57
85,57
The % format
8556.55%
8556.55&
8556,55%

Note that when you use the French culture, the symbol "." is replaced with a ",". Note also that using the % specifier, the % symbol is changed to & in the last example because of the following statement in the code:

provider.PercentSymbol = "&" ;

13.2.4 Percent Formatting

Unlike Java, C# has no special percent formatter. However, the % symbol is used to indicate the percentage. The number is multiplied by 100 before it is displayed as a percentage. In Listing 13.3 the last method call shows percent formatting. Note that we introduced a new character in the format specifier. The next section discusses these custom format specifiers.

13.2.5 Custom Specifiers

In addition to the standard format specifiers we have seen, you can specify custom format specifiers. Custom format specifiers are similar to the ones in Java. The special characters in Table 13.2 are used for specifying custom format strings and have similar meanings to the ones in Java.

In general, the zero character (0) is used as a digit or zero placeholder. If the numeric value has a digit in the position at which the 0 appears in the format string, the digit will appear in the result. If not, a zero appears in that position.

Throughout this chapter we have used the ToString() function to format numbers. However, format specifier strings can also be passed to the Console.WriteLine method to format the output at the time of printing.

Table 13.2. Format Specifiers for Custom Formatting in C#

Character

Result

0

Displays leading zeros if the number has fewer digits than there are zeros in the format.

#

Replaces # with digits only for significant digits.

,{comma)

Separates number groups such as 1,000. When used after a number, divides by 1,000.

%

Displays the % character.

.

Displays the decimal point.

;

Separates sections.

The format specifiers follow the same syntax as when you specify them in the ToString() method, with a slight modification. Listing 13.4 shows how to use the format specifiers in the Console.WriteLine method. The specifier string is {0:00} here; the 00 after the : is the actual specifier. The 0 before the : indicates that the specifier is to be applied to the first argument of the Console.WriteLine call.

Listing 13.4 Using the 0 Character Placeholder (C#)
using System;
public class ZeroFormatting {

  public static void Main(string[] args) {
    Console.WriteLine("{0:00}", 55);
    Console.WriteLine("{0:00000}", 556);
    Console.WriteLine("{0:00}", 66464);
  }
}

The output of Listing 13.4 is as follows:

55
00556
66464

The # character behaves similarly to the 0 character, except that the # character is omitted if there is no digit in that position. Listing 13.5 shows the use of the # character.

Listing 13.5 Using the # Character Placeholder
using System;

public class HashFormatting {

  public static void Main(string[] args) {
    Console.WriteLine("{0:####}", 55);
    Console.WriteLine("{0:#}", 556);
    Console.WriteLine("{0:##}", 66464);
  }
}

The output of Listing 13.5 is as follows:

55
556
66464

Note that 556 is now not preceded with leading zeros.

The "," character is used as a group separator. If a comma appears in the middle of a display digit placeholder (0 or #) and to the left of the decimal point, then a group separator will be inserted into the string. Listing 13.6 shows how this works.

Listing 13.6 Using the "," Character Placeholder (C#)
using System;
public class CommaFormatting {

  public static void Main(string[] args) {
    Console.WriteLine("{0:,###}", 55678);
    Console.WriteLine("{0:#,##}", 55678);
    Console.WriteLine("{0:#,#,#}", 55678);
    Console.WriteLine("{0:#,#,#}", 55678857);
    Console.WriteLine("{0:000#,.000}", 55678857);
  }
}

The output of Listing 13.6 is as follows:

55678
55,678
55,678
55,678,857
55678.857

Note that putting one comma first divides the number by 1,000 and places the comma at the appropriate location. Every subsequent comma added divides the number further by 1,000, and the commas are again added to appropriate places. You can scale a number by placing the comma character directly before the decimal point. For each comma present in this location, the number is divided by 1,000 before it is formatted.

13.2.6 Section, Hexadecimal, and Escaping Specifiers

Listing 13.7 shows the section, hexadecimal, and escaping specifiers. The section specifier is used to specify different formatting strings for a number depending on whether it is positive, negative, or zero. Sections are demarcated by a semicolon. If there are three sections (x;y;z), then format x would apply if the number were positive, format y would apply if the number were negative, and format z would apply if the number were zero.

Listing 13.7 Three Kinds of Specifiers (C#)
using System;
using System.Globalization;
using System.IO;

public class ScientificFormatting {

  static float NUMBER = 85.56546F;

  public static void Main(string[] args) {

    //Hexadecimal formatting
    Console.WriteLine("{0:X}", 58);

    //Section formatting
    Console.WriteLine("{0:##;#.000;(##.00)}", 57575.356);
    Console.WriteLine("{0:##,00;00.##;(##.00)}",-57575.356);
    Console.WriteLine("{0:##,00;00.##;(##.00)}", 0);

    //Escaping characters
    Console.WriteLine("{0:###;bs;bs#}", 345);
  }
}

The escaping specifier is in fact not a specifier but a way to escape characters so that they don't get interpreted at the time of formatting.

The output of Listing 13.7 is as follows:

3A
57575
57575.36
(.00)
345#

Notice that when the number is 0, the section specifier chooses the rightmost format for formatting the number.


13.3 Date and Time Formatting

This section explores the date and time formatting capabilities of C#. In Java, the java.text.DateFormat class provides methods such as getTimeInstance(), getDateTimeInstance(), and getDateInstance(). These methods take optional locale information to create a DateFormat instance. You can gain some level of control over the formatting by specifying whether the format to be used is LONG, SHORT, or MEDIUM. However, the formatted string always follows a standard pattern. To change that pattern and to gain finer control of the formatted date, you use java.text.SimpleDateFormat, whose constructor takes a pattern string. The pattern string consists of date- and time-related symbols. For example, consider the following Java code:

SimpleDateFormat formatter = new SimpleDateFormat ("yyyy.MM.dd G 'at' hh:mm:ss a z");

This code would print a sample date and time as follows:

1996.07.10 AD at 15:08:56 PDT

Here, the letters y, M, d, G, h, m, s, and z have a special meaning. Repeating y four times would indicate printing the four-digit year. M denotes a month in the year, and m denotes a minute in an hour. So the character case is important.

To avoid confusion between the Java characters and their equivalent C# characters, we will leave the introduction to Java date and time formatting right here. Suffice it to note that in C# you have similar pattern characters. As a Java programmer you would be wise not to assume that the characters mean the same thing in C#.

Table 13.3 was taken from the .NET environment help file for the DateTimeFormatInfo class. It lists the patterns available for formatting dates and times and their associated DateTimeFormatInfo. Earlier in the chapter we mentioned the IFormatProvider interface. DateTimeFormatInfo implements this interface and has the necessary constants for getting standard patterns and symbols for formatting. The format characters are case-sensitive; for example, g and G represent slightly different patterns.

Listing 13.8 shows a formatting example for dates and times.

Listing 13.8 Using the DateTime Formats (C#)
using System;
using System.Globalization;
using System.IO;

public class DateFormatting {

  public static void Main(string[] args) {
    TextWriter tw = Console.Out;
    //Hardcoded patterns
    tw.WriteLine("Format d:" + DateTime.Now.ToString("d"));
    tw.WriteLine("Format D:" + DateTime.Now.ToString("D"));
    tw.WriteLine("Format f:" + DateTime.Now.ToString("f"));
    tw.WriteLine("Format F:" + DateTime.Now.ToString("F"));
    tw.WriteLine("Format g:" + DateTime.Now.ToString("g"));
    tw.WriteLine("Format G:" + DateTime.Now.ToString("G"));
    tw.WriteLine("Format m:" + DateTime.Now.ToString("m"));
    tw.WriteLine("Format s:" + DateTime.Now.ToString("s"));
    tw.WriteLine("Format t:" + DateTime.Now.ToString("t"));
    tw.WriteLine("Format T:" + DateTime.Now.ToString("T"));
    tw.WriteLine("Format u:" + DateTime.Now.ToString("u"));
    tw.WriteLine("Format U:" + DateTime.Now.ToString("U"));
    tw.WriteLine("Format y:" + DateTime.Now.ToString("y"));

    //Using DateTimeFormatInfo
    Console.WriteLine(";bsn;bsnUsing the info class:;bsn;bsn");
    DateTimeFormatInfo info = new DateTimeFormatInfo();
    tw.WriteLine("Month Day Pattern:" +
        DateTime.Now.ToString(info.MonthDayPattern));
    tw.WriteLine("Year Month Pattern:" +
        DateTime.Now.ToString(info.YearMonthPattern));
    tw.WriteLine("Universal Sortable Date Time Pattern:" +
        DateTime.Now.ToString(
        info.UniversalSortableDateTimePattern));
    tw.WriteLine("Long Date Pattern:" +
        DateTime.Now.ToString(info.LongDatePattern));
    tw.WriteLine("Short Date Pattern:" +
        DateTime.Now.ToString(info.ShortDatePattern));
  }
}

Table 13.3. Date Formatting Specifiers in C#

Format Character

Associated Property/Description

Sample Format Pattern (en-US)

D

ShortDatePattern

MM/dd/yyyy

D

LongDatePattern

dddd, dd MMMM yyyy

F

Full date and time (long date and short time)

dddd, dd MMMM yyyy HH:mm

F

FullDatePattern (long date and long time)

dddd, dd MMMM yyyy HH:mm:ss

G

General (short date and short time)

MM/dd/yyyy HH:mm

G

General (short date and long time)

MM/dd/yyyy HH:mm:ss

m, M

MonthDayPattern

MMMM dd

r, R

RFC1123Pattern

ddd, dd MMM yyyy HH':'mm':'ss 'GMT'

S

SortableDateTimePattern (based on ISO 8601) using local time

yyyy'-'MM'-'dd'T'HH':'mm':'ss

T

ShortTimePattern

HH:mm

T

LongTimePattern

HH:mm:ss

U

UniversalSortableDateTimePattern using universal time

yyyy'-'MM'-'dd HH':'mm':'ss'Z'

U

Full date and time (long date and long time) using universal time

dddd, dd MMMM yyyy HH:mm:ss

y, Y

YearMonthPattern

yyyy MMMM

The output of Listing 13.8 is as follows:

Format d: 2/12/2003
Format D: Wednesday, February 12, 2003
Format f: Wednesday, February 12, 2003 11:59 PM
Format F: Wednesday, February 12, 2003 11:59:52 PM
Format g: 2/12/2003 11:59 PM
Format G: 2/12/2003 11:59:52 PM
Format m: February 12
Format s: 2003-02-12T23:59:52
Format t: 11:59 PM
Format T: 11:59:52 PM
Format u: 2003-02-12 23:59:52Z
Format U: Thursday, February 13, 2003 7:59:52 AM
Format y: February, 2003

Using the info class:

Month Day Pattern: February 12
Year Month Pattern: 2003 February
Universal Sortable Date Time Pattern: 2003-02-12 23:59:52Z
Long Date Pattern: Wednesday, 12 February 2003
Short Date Pattern: 02/12/2003

Most of these patterns have equivalent property representations in the DateTimeFormatInfo class.


13.4 Formatting Custom Objects

Objects can implement their own custom formatting merely by overriding the ToString() method to take the format string and the format provider. Listing 13.9 shows a sample Hello object that implements a format string. The format string can be s (short greeting), f (full greeting), or v (verbose greeting). The format provider can be used to specify the culture to be used for printing the greeting. The default culture will be en-US. For the runtime to recognize formatting strings, the object must implement IFormattable.

Listing 13.9 Formatting Custom Objects (C#)
using System;

public class Hello : IFormattable {

  public string ToString(string format, IFormatProvider
    provider) {
    if (format.Equals("s")) {
      return "Hi";
    }else if (format.Equals("f")) {
      return "Hello, what's up?";
    }else if (format.Equals("v")) {
      return "Hello, how are you doing? How was your weekend?";
    } else {
      return "Hi";
    }
  }

  public static void Main(string[] args) {

    Hello hello = new Hello();
    Console.WriteLine("{0:s}", hello);
    Console.WriteLine("{0:f}", hello);
    Console.WriteLine("{0:v}", hello);
  }
}

The output of Listing 13.9 is as follows:

Hi
Hello, what's up?
Hello, how are you doing? How was your weekend?

So far we have looked at formatting numbers, dates, and strings in which a value is displayed in a manner specified by the format specifiers. Now let's look at parsing, a process in which a formatted string is taken as input and an unformatted value is obtained from it.


13.5 Parsing

Date parsing is an attempt to parse a string formatted as a date. Numbers can also be parsed. Listing 13.10 shows parsing of dates and numbers. Typically in Java, a good way to quickly check whether a string is a date or a number is to parse it and check for any exceptions thrown. The same technique works in C#.

Listing 13.10 Parsing of Numbers and Dates (C#)
using System;

public class Test {
  public static void Main(string[] args) {

    Console.WriteLine(Int32.Parse("556"));
    try {
      Console.WriteLine(Double.Parse("45764"));
    } catch (FormatException e) {
      Console.WriteLine(e.StackTrace);
    }
    try {
      Console.WriteLine(DateTime.Parse("12/3/2002"));
    } catch (FormatException e) {
      Console.WriteLine(e.StackTrace);
    }
  }
}

Listing 13.10 shows a popular pattern for quickly parsing data. Using this pattern, you put the onus of parsing a string on the runtime.


13.6 Summary

To format dates and numbers in C#, you specify a pattern string called a format specifier. The format specifier is a string literal of special characters that are interpreted by the runtime. For finer control of formatting, C# also supplies classes that implement the IFormatProvider interface. These classes provide other specific information, such as the culture and the symbols to be used for formatting. Java's formatting also uses a pattern string, locale information, and information about symbols to be used for formatting. Unlike Java, C# has no format-related classes; instead, formatting is done through overloaded ToString() methods on the data type itself, an approach that seems more intuitive. The format specifiers that are used in the ToString method can also be used in other common places, such as Console.WriteLine, to format printed strings.


Chapter 14. Using Collections

Programmers seldom want to reinvent the wheel by coding custom implementations of standard data structures such as linked lists, hash tables, binary trees, and so on. They would rather concentrate on the application logic that uses these data structures. Fortunately, the makers of Java and C# address this problem by integrating the formal implementations of popular data structures into the API. Java's java.util package has a cornucopia of classes that are extremely useful for writing complex applications. C#'s System.Collections namespace has a similar arsenal of classes that can make life easy for the programmer.

This chapter introduces the classes in C#'s System.Collections namespace. We briefly outline most of the classes, giving examples and comparing the classes to their Java counterparts. This chapter assumes that you are familiar with the concept of data structures and have dabbled in Java's java.util package.


14.1 The System.Collections Interfaces

Table 14.1 lists the interfaces of the System.Collections namespace and their Java equivalents. Note that the Java equivalent interfaces are in the java.util package.

In the System.Collections namespace, the IEnumerable, ICollection, IList, and IMap interfaces constitute the core collection-based interfaces. The other interfaces are utility interfaces for iterating over the collections, comparing objects, and obtaining hash codes.

Of the core collection interfaces, both IMap and IList inherit from ICollection, which in turn inherits from IEnumerable. This inheritance structure is different from that of the interfaces in the java.util package. Whereas the java.util.Map interface does not extend the java.util.Collection interface, the java.util.List interface extends the java.util.Collection interface. This means that in Java, a java.util.Map and a java.util.List can never be interchangeable, whereas in C# it is possible to intermix the two as long as they are both treated as an ICollection or an IEnumerable.

Table 14.1. Interfaces of the System.Collections Namespace

C# Interface

Java Interface

Description

ICollection

Collection

Denotes an abstract concept of a collection. Defines the size, enumerators, and synchronization rules for the collection classes.

IComparer

Comparator

Defines a method for comparing two objects.

IDictionary

Map

Defines a maplike data structure for storing key-value pairs where the map is indexed to the hash code of the key value.

IDictionaryEnumerator

N/A

An enumerator dedicated for enumerating through the key-value pairs of a maplike data structure.

IEnumerable

Enumeration

This is the root collection interface in .NET. A collection is, after all, a sequence of objects that can be enumerated over.

IHashCodeProvider

N/A

Provides a hash code for the object using a custom hash function.

IList

List

Defines a list-based data structure. Lists usually denote sequential access.

The IEnumerator interface provides all that is needed to iterate over a collection. It has one property (Current) and two methods (MoveNext and Reset), giving you unidirectional traversal across the collection. Java provides an Enumeration and an Iterator interface. The Iterator interface allows you to remove the object from the underlying collection. Unlike Java, C# provides several concrete classes that implement the IEnumerator interface and that are geared to enumerate over specific collections.

The .NET collections library does not have any equivalent for java.util.Set.

Having a rich set of interfaces allows you to write your custom collection classes to implement those interfaces. For the most part, you will be happy with the standard set of classes that ship with the System.Collections namespace. Let's explore those in the next section.


14.2 The System.Collections Classes

Table 14.2 lists the classes of the System.Collections namespace along with their Java equivalents. Some of the classes in Table 14.2 share a common set of behavior rules, discussed next.

14.2.1 Thread Safety

By default, the C# collection classes are not thread-safe and therefore are similar to the Java 2 collections. Therefore, the C# Hashtable (Table 14.2) is equivalent to Java's non-thread-safe java.util.HashMap and not to the thread-safe java.util.Hashtable. Most of the C# collection classes have a Synchronized method that provides a thread-safe wrapper of the class. Java instead provides a method called Collections.synchronized Collection() on the java.util.Collections object, and you use this method to get a thread-safe wrapper for the collection classes.

For example, to get a thread-safe ArrayList in C# you write the following:

ArrayList states = ArrayList.Synchronized (new ArrayList());

14.2.2 Type Safety

Java collections do not enforce type safety, and therefore objects obtained from the collection must be cast to the specific type. Nor does Java provide any infrastructure for writing type-safe collections.

C#, on the other hand, provides the classes CollectionBase and DictionaryBase, which are abstract implementations of type-safe collections. These classes have the requisite hooks that subclasses can implement to enforce type safety. You will see an example of this later in the chapter.

Table 14.2. Classes of the System.Collections Namespace

C# Class

Java Class

C# Description

ArrayList

ArrayList

A list-based data structure whose size is dynamically increased. Allows array-like indexed access.

BitArray

BitSet

An array of bit values that can be either 1 (true) or 0 (false).

CaseInsensitiveComparer

N/A

Compares two objects, ignoring case sensitivity of strings.

CaseInsensitiveHashCodeProvider

N/A

Provides a hash code for an object, ignoring the case of the strings.

CollectionBase

N/A

An abstract base class for storing strongly typed objects.

Comparer

N/A

Provides case-sensitive comparison of objects.

DictionaryBase

N/A

An abstract base class for storing strongly typed key-value pairs.

Hashtable

HashMap

Represents a collection of key-value pairs.

Queue

LinkedList

Implements a first in first out (FIFO) collection.

ReadOnlyCollectionBase

N/A

An abstract class for storing strongly typed read-only values.

SortedList

TreeMap

A collection indexed by the key of a key-value pair and sorted by the key.

Stack

Stack

Implements a last in first out (LIFO) collection.

14.2.3 Immutability

Immutable collections are useful when you don't want the collection to be modified. In the following code snippet, a class creates an ArrayList, populates it with U.S. states, and returns this list. This list is mutable because the caller of the GetStates() method can add, delete, or modify the list.

public ArrayList GetStates() {
  ArrayList states = new ArrayList();
  //Populate states from database;
  return states;
}

To enforce immutability of the collections, you must make them read-only. In Java, you use the Collections.unmodifiableCollection method of the java.util.Collections class to make the collection unmodifiable (in other words, the structure of the collection cannot be modified).

C# provides two levels of immutability. Some of the C# collection classes (ArrayList, Hashtable) implement two public properties (IsFixedSize and IsReadOnly) along with the corresponding methods FixedSize and ReadOnly, which mark the collection as fixed-size and read-only, respectively. In a FixedSize collection you can modify the individual elements of a collection, but you cannot add or delete from the collection (change its size). In a ReadOnly collection you cannot add, delete, or modify the elements of the collection. To make the GetStates() method return an immutable list you would have to do the following:

public ArrayList GetStates() {
  ArrayList states = new ArrayList();
  //Populate states from database;
  return ArrayList.ReadOnly(states);
}

14.2.4 Accessibility

The collection classes ArrayList and Hashtable provide indexers. (Indexers are discussed in detail in Chapter 17.) Indexers allow these collections to be accessed like arrays. Instead of using the Java-like Get() and Set() methods, you should use the indexer notation to get and set elements of the collection. The following snippet prints the elements of the ArrayList using the array-like indexed notation. Even a Hashtable with string-based keys can be accessed this way.

ArrayList list = new ArrayList();
  for (int j = 0; j < list.Count; ++j) {
  Console.WriteLine(list[j]);
}

object value = new object();
Hashtable hash = new Hashtable();
hash["key"] = value;

You can use C# collections to store primitives directly without converting them into their object representations. Therefore, the following snippet is perfectly legal in C#:

ArrayList list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);

This is because of the automatic boxing and unboxing of value types, as described in Chapter 7.

Now let's explore the classes shown in Table 14.2 and examine them through examples.


14.3 Using the System.Collections Classes

Let's first look at the core collection classes. These are classes that actually are collections and store data.

14.3.1 ArrayList

An ArrayList is a popular list-based data structure used for storing a sequence of objects. An ArrayList is like a dynamic array that can grow and shrink. The C# ArrayList class is similar to the java.util.ArrayList class. Listing 14.1 shows the ArrayList class in action.

Listing 14.1 ArrayList (C#)
using System;
using System.Collections;
using System.IO;

public class ArrayListTest {

  public static void Main(string[] args) {
    TextWriter pr = Console.Out;
    ArrayList list = PopulateList();
    ShowProperties(pr,list);
    ModifyList(pr,list);
    SearchList(pr,list);
    MiscOperationsList(pr,list);
  }
  //Fill the list with sample data
  private static ArrayList PopulateList() {
    ArrayList ll= new ArrayList();
    for (int i =0; i < 20; ++i) {
      ll.Add(i);
    }
    return ll;
  }
  //Show list properties
  private static void ShowProperties(TextWriter pr, ArrayList
                                                    ll) {
    pr.WriteLine("Capacity "+ll.Capacity);
    pr.WriteLine(""+ll.Count);
    pr.WriteLine("IsFixedSize "+ll.IsFixedSize);
    pr.WriteLine("IsReadOnly "+ll.IsReadOnly);
    pr.WriteLine("IsSynchronized "+ll.IsSynchronized);
    pr.WriteLine("Synchronization root"+ll.SyncRoot);
  }
  //Modify the contents of the list by adding, setting, and
  //removing
  private static void ModifyList(TextWriter pr, ArrayList list){
    list.Add(13);
    ArrayList subList = new ArrayList();
    subList.Add(-100);
    list.AddRange(subList);
    pr.WriteLine("ToString() "+list.ToString());
    list.Insert(0,-1);
    list.Remove(18);
    list.RemoveAt(6);
    pr.WriteLine(list[4]);
    ShowContents(pr,list);
    list[2] = 89;

  }
  //Show all elements
  private static void ShowContents(TextWriter pr, ArrayList
      list) {
    for (int i = 0; i < list.Count; ++i) {
      pr.Write(list[i]);
    }
    pr.WriteLine();
  }
  //Call search and find related methods on the list
  private static void SearchList(TextWriter pr, ArrayList list){
    pr.WriteLine(list.BinarySearch(10));
    pr.WriteLine(list.Contains(12));
    pr.WriteLine(list.IndexOf(14));
  }
  //Conduct miscellaneous operations on the list
  private static void MiscOperationsList(TextWriter pr,
                                          ArrayList list) {
    ArrayList subList = list.GetRange(1,2);
    ShowContents(pr,subList);
    subList.InsertRange(subList.Count1,list);
    Array array = list.ToArray();
    list.Reverse();
    ShowContents(pr,list);
    list.Sort();
    ShowContents(pr,list);
    pr.WriteLine(list.BinarySearch(13));
  }
}

The output of Listing 14.1 is as follows:

Capacity 32
20
IsFixedSize False
IsReadOnly False
IsSynchronized False
Synchronization rootSystem.Collections.ArrayList
ToString() System.Collections.ArrayList
3
-101234678910111213141516171913-100
10
True
14
089
-100131917161514131211109876432891001319171615141312111098764328
90-10-1
-100-100-110022334466778899101011111212131313131414151516161717
19198989
28

Note that the ToString() implementation of the C# ArrayList does not have a ToString() on the elements of the ArrayList. Also, methods such as Reverse(), Sort(), and BinarySearch() are part of the ArrayList class; this contrasts with Java, which has a utility class (java.util.Collections) that contains static methods that sort, reverse, and search collections.

The ArrayList class is not thread-safe. To return a thread-safe wrapper of the class, you use the ArrayList.Synchronized() method. Nor is the ArrayList class type-safe. All elements of the ArrayList are stored as instances of System.Object. As shown in Listing 14.1, you can use the indexer notation to access ArrayList elements. You can have fixed-size and read-only versions of an ArrayList.

14.3.2 BitArray

A BitArray class is ideal for storing a number of grouped Boolean values. For example, consider a Web page with an HTML form consisting of several checkboxes. The state of the these checkboxes could be stored in a BitArray. Listing 14.2 shows how to use the BitArray class.

Listing 14.2 BitArray (C#)
using System;
using System.Collections;

public class BitArrayTest {
  public static void Main(string[] args) {
    BitArray falsebits = new BitArray(3);
    BitArray truebits = new BitArray(3, true);

    BitArray copyfalsebits = (BitArray) falsebits.Clone();
    BitArray copytruebits = (BitArray) truebits.Clone();

    PrintState("Initial false bits ", falsebits);
    PrintState("Initial true bits ", truebits);
    PrintState("true bits OR-ed with false bits",
                            truebits.Or(falsebits));
    PrintState("true bits AND-ed with false bits
                             ",truebits.And(falsebits));
    PrintState("true bits XOR-ed with false bits
                             ",truebits.Xor(falsebits));
    PrintState("Not false bits ",copyfalsebits.Not());
    PrintState("Not true bits ",copytruebits.Not());
  }

  private static void PrintState(string banner, BitArray bits)
{
    Console.WriteLine ("=== "+banner+" ===");
    for (int i =0; i < bits.Count; ++i) {
      Console.WriteLine ("Bit ["+i+"] "+bits [i]);
    }
  }

}

The output of Listing 14.2 is as follows:

=== Initial false bits ===
Bit[0] False
Bit[1] False
Bit[2] False
=== Initial true bits ===
Bit[0] True
Bit[1] True
Bit[2] True
=== true bits OR-ed with false bits ===
Bit[0] True
Bit[1] True
Bit[2] True
=== true bits AND-ed with false bits ===
Bit[0] False
Bit[1] False
Bit[2] False
=== true bits XOR-ed with false bits ===
Bit[0] False
Bit[1] False
Bit[2] False
=== Not false bits ===
Bit[0] True
Bit[1] True
Bit[2] True
=== Not true bits ===
Bit[0] False
Bit[1] False
Bit[2] False

The BitArray class is similar to java.util.BitSet, which can be used to execute a logical AND, logical OR, and logical exclusive-OR (XOR) on a group of bit values.

The BitArray class is not thread-safe. There is no implementation for getting a thread-safe version of this class. The BitArray class is type-safe. It is populated at the time of construction, and there is no add or remove method on the BitArray. The BitArray constructors allow only certain types to be passed to them (BitArray, bool[], int[], int, byte[]). You can use the indexer notation to access the elements of a BitArray. A BitArray is of fixed size; you cannot add or delete elements from it. You can have read-only versions of BitArray.

14.3.3 Hashtable

A Hashtable is a popular data structure used for storing key-value pairs. The keys are stored in Hashtable based on their hash code. Duplicate keys cannot be stored in Hashtable, and keys cannot be null.Hashtables allow for fast retrieval of a value based on the key, and they are ideal for storing property values or any concept that can be broken down to a unique key-value pair. The order in which the entries are inserted in a Hashtable is not the same as the order in which they are stored. There is currently no C# equivalent of the java.util.LinkedHashMap, which maintains the order in which the entries are added to the HashMap. As mentioned earlier, a C# Hashtable is not thread-safe and should not be confused with java.util.Hashtable, which is thread-safe.

Listing 14.3 shows the use of Hashtable.

Listing 14.3 Hashtable (C#)
using System;
using System.Collections;

public class HashTest {

  public static void Main(string[] args) {

    Hashtable table = new Hashtable();
    for (int i=0; i < 10; ++i) table.Add(i, 10*i);
    PrintTable(table);
    Console.WriteLine("Value of table [2] "+table[2]);
    table[4] = 54;
    table.Remove(6);
    PrintTable(table);
    try {
      table.Add(1, 67);
    } catch (Exception e) {
        Console.WriteLine(e.Message);
    }
  }

  private static void PrintTable(Hashtable table) {
    ICollection keys = table.Keys;
    foreach (object o in keys) {
        Console.WriteLine("Key "+o+" Value "+table[o]);
    }
  }

}

The output of Listing 14.3 is as follows:

Key 9 Value 90
Key 8 Value 80
Key 7 Value 70
Key 6 Value 60
Key 5 Value 50
Key 4 Value 40
Key 3 Value 30
Key 2 Value 20
Key 1 Value 10
Key 0 Value 0
Value of table [2] 20
Key 9 Value 90
Key 8 Value 80
Key 7 Value 70
Key 5 Value 50
Key 4 Value 54
Key 3 Value 30
Key 2 Value 20
Key 1 Value 10
Key 0 Value 0
Item has already been added. Key in dictionary: "1" Key being
                                                   added: "1"

One interesting behavior that Java programmers must watch out for is that the Add method of the C# Hashtable will complain if you attempt to add a key that already exists in the Hashtable. In Listing 14.3 we tried to add the key 1 again to the table, and it complained with an exception and printed the following message:

Item has already been added. Key in dictionary: "1" Key being
added: "1"

This behavior is different from that of Java's HashMap, which lets you add the same key multiple times to the map. It silently overwrites the old value of the key with the new value.

The Hashtable class is not thread-safe. You must use the Hashtable.Synchronized() method to return a thread-safe wrapper of the class. Nor is the Hashtable class type-safe. All elements of the Hashtable are stored as instances of System.Object. As shown in Listing 14.3, you can use the indexer notation to access Hashtable elements. You can have fixed-size and read-only versions of a Hashtable.

14.3.4 Queue

A Queue is a list-based data structure that is based on the first in first out (FIFO) mechanism. Just like a real-life human queue, the first object to get enqueued is the first object that gets dequeued. There is no first-class Queue object in Java, but you can simulate a Queue based on a LinkedList. In C#, the Queue implementation is based on a circular array. Queues are useful for storing messages in the order they were received for sequential processing. Objects stored in a Queue are inserted at one end and removed from the other. Listing 14.4 shows how to use a C# Queue.

Listing 14.4 Queue (C#)
using System;
using System.Collections;

public class QueueTest {

  public static void Main(string[] args) {

    Queue st = new Queue();
    for (int i = 0; i < 10; ++i) st.Enqueue(i);
    while (st.Count > 0) {
      Console.Write(st.Dequeue()+" ");
    }
  }

}

The output of Listing 14.4 is as follows:

0 1 2 3 4 5 6 7 8 9

The Queue class is not thread-safe. You must use the Queue.Synchronized() method to return a thread-safe wrapper of the class. Nor is the Queue class type-safe. All elements of the Queue are stored as instances of System.Object. You cannot use the indexer notation to access the elements of a Queue. There is no such thing as a fixed-size Queue; the very nature of the data structure suggests that its use requires the size of a Queue to vary. There is also no read-only version of a Queue.

14.3.5 Stack

A Stack is a data structure that follows the last in first out (LIFO) mechanism. Stacks can be used to evaluate mathematical expressions in reverse Polish notation (RPN). As an RPN expression is scanned from left to right, operands are simply placed in a LIFO stack, and operators can be immediately applied to the operands at the bottom of the stack. Listing 14.5 shows how to use a Stack in C#.

Listing 14.5 Stack (C#)
using System;
using System.Collections;

public class StackTest {

  public static void Main(string[] args) {

    Stack st = new Stack();
    for (int i = 0; i < 10; ++i) st.Push(i);
    while (st.Count > 0) {
      Console.Write(st.Pop()+" ");
    }
  }

}

The output of Listing 14.5 is as follows:

9 8 7 6 5 4 3 2 1 0

The C# Stack class is similar in function to java.util.Stack, although there are implementation differences. Whereas the Java stack is implemented as a Vector, the C# Stack is implemented as a circular buffer. This means that in Java you can treat a Stack as a list-based structure and access a particular element by specifying its index.

The Stack class is not thread-safe. You must use the Stack.Synchronized() method to return a thread-safe wrapper of the class. Nor is the Stack class type-safe. All elements of the Stack are stored as instances of System.Object. You cannot use the indexer notation to access the elements of a Stack. There is no such thing as a fixed-size Stack; the very nature of the data structure suggests that its use requires the size of the Stack to vary. There is also no read-only version of the Stack.

14.3.6 SortedList

A C# SortedList is like the java.util.TreeMap. A SortedList stores key-value pairs that are sorted and indexed by the keys. Listing 14.6 shows the use of a SortedList.

Listing 14.6 SortedList (C#)
using System;
using System.Collections;

public class SortedListTest {

  public static void Main(string[] args) {
    SortedList sl = new SortedList();
    Populate(sl);
    Print ("Created List ", sl);
    PruneEven(sl);
    Print ("Pruned list ", sl);
  }

  private static void Populate(IDictionary dict) {
    for (int i =0; i < 10; ++i) dict.Add(i, 10*i);
  }
  private static void PruneEven(IDictionary dict) {
    for (int i =0; i < 10; ++i) {
      if (i % 2 == 0) dict.Remove(i);
    }
  }
  private static void Print(string banner, SortedList list) {
    Console.WriteLine("==== "+banner+" ====");
    for (int i =0; i < list.Count; ++i) {
      Console.WriteLine("Key "+list.GetKey(i)+" Value
                                      "+list.GetByIndex(i));
    }
  }
}

The output of Listing 14.6 is as follows:

==== Created List ====
Key 0 Value 0
Key 1 Value 10
Key 2 Value 20
Key 3 Value 30
Key 4 Value 40
Key 5 Value 50
Key 6 Value 60
Key 7 Value 70
Key 8 Value 80
Key 9 Value 90
==== Pruned list =====
Key 1 Value 10
Key 3 Value 30
Key 5 Value 50
Key 7 Value 70
Key 9 Value 90

The SortedList data structure does not allow you to re-add a key. You must check for its existence, remove the key, and then add it again.

The SortedList class is not thread-safe. You must use the SortedList.Synchronized() method to return a thread-safe wrapper of the class. Nor is the SortedList class type-safe. All elements of the SortedList are stored as instances of System.Object. You cannot use the indexer notation to access the elements of a SortedList. A SortedList can be read-only and fixed-size.

Now let's look at the collection interfaces outside the core group. These interfaces cover utility functions such as iterating over collections, comparing objects, and calculating hash codes. We explore the interfaces instead of the concrete classes so that we can demonstrate how to write custom implementations.

14.3.7 IComparer

This interface exposes a method that contains one method called Compare and compares two objects. When you sort a collection, entries of the collection that implement this interface are compared using the Compare method. You can create custom objects to define multiple custom sort orders by having different implementations of this interface within their class. For the Student class shown in Listing 14.7, let's say there are two sort orders—SortByName and SortByAge—which we expose as properties. The two sort order properties are essentially IComparer implementations conveniently packaged as properties.

Listing 14.7 IComparer (C#)
using System;
using System.Collections;
using System.IO;

public class ComparerTest {

  public static void Main(string[] args) {
    TextWriter pr = Console.Out;
    ArrayList list = new ArrayList();
    list.Add(new Student("Sally", 10));
    list.Add(new Student("Bruce", 45));
    list.Add(new Student("Mike", 55));
    list.Add(new Student("Tiger", 36));
    list.Add(new Student("Hank", 49));
    list.Add(new Student("Bill", 27));
    list.Add(new Student("Damien", 99));
    list.Sort(Student.SortByAge);
    PrintList(list);
    list.Sort(Student.SortByName);
    PrintList(list);
  }

  public class Student {
    string name;
    int age;
    public Student(string name, int age) {
      this.name = name;
      this.age = age;
    }
    public override string ToString() {
      return name + " is "+ age;
    }

    public static IComparer SortByName {
      get
      {
        return (IComparer) new SortByNameClass();
      }
    }
    public static IComparer SortByAge {
      get
      {
        return (IComparer) new SortByAgeClass();
      }
    }
    class SortByNameClass : IComparer {
      public int Compare(Object obj1, Object obj2) {
        Student stu1 = (Student) obj1;
        Student stu2 = (Student) obj2;
        return String.Compare(stu1.name, stu2.name);
      }
    }
    class SortByAgeClass : IComparer {
      public int Compare(Object obj1, Object obj2) {
        Student stu1 = (Student) obj1;
        Student stu2 = (Student) obj2;
        if (stu1.age > stu2.age) return 1;
        else if (stu1.age < stu2.age) return 1;
        else return 0;
      }
    }
  }
  private static void PrintList(ArrayList list) {
    Console.WriteLine(";bsn");
    foreach (object o in list) {
      Console.Write(o+" ");
    }
  }
}

The output of Listing 14.7 is as follows:

Sally is 10 Bill is 27 Tiger is 36 Bruce is 45 Hank is 49 Mike
is 59

Bill is 27 Bruce is 45 Damien is 99 Hank is 49 Mike is 55 Sally
is 6

The IComparer interface is similar to the java.util.Comparator interface in its function. Note that IComparer is different from IComparable. The IComparer interface is in the System.Collections namespace, whereas IComparable is in the System namespace and is like the Equals method. The IComparer interface is usually implemented by a third class whose sole responsibility is comparing two objects of a kind. The IComparable interface is implemented by an object in order for it to compare itself with another object of the same type. IComparable looks something like this:

public class Student : IComparable {
       string name;
       int age;
  public int CompareTo(object o1) {
         Student stu2 = (Student) obj2;
         if (stu1.age > stu2.age) return 1;
         else if (stu1.age < stu2.age) return 1;
         else return 0;
}
}

14.3.8 IHashCodeProvider

The System.Collections.Hashtable class automatically generates the hash code used for storing keys. However, you can customize Hashtable to use a different hash code for the objects. Listing 14.8 shows how to calculate the hash code of the Student class.

Listing 14.8 IHashCodeProvider (C#)
class Student {
  class Student(string name) {
  this.name = name;
  }
  string name;
}

class HashByName : IHashCodeProvider {
  public int GetHashCode(object obj) {
  Student stu = (Student) obj;
      return stu.name.GetHashCode();
  }
}

The System.Collections namespace provides a CaseInsensitive HashCodeProvider that uses a hashing algorithm that ignores the case of the strings. One of the constructors of a System.Collections.Hashtable class takes an IHashCodeProvider. By passing an instance of the Case InsensitiveHashCodeProvider class to the Hashtable constructor, you can create a hash table that ignores the case of the keys.

14.3.9 IEnumerator

Collections provide a standardized way for enumerating through their elements. C# collection classes that expose such an enumerator implement the IEnumerable interface. The GetEnumerator() method of that interface returns a class that itself implements the IEnumerator interface.

Listing 14.9 shows an OddList class that extends the ArrayList class and implements its own custom enumerator, which enumerates only over odd numbers of the list of numbers passed to it. The OddNumber Enumerator class maintains the state of the total number of odd numbers by creating an inner list based on the list passed to it. It is this inner list that the enumerator iterates over. This is indeed an odd list, because the enumeration is not over all elements of the list but over only some elements. Such an implementation is more convenient than iterating through the entire number list and discovering the odd numbers every time.

Listing 14.9 IEnumerator (C#)
using System;
using System.Collections;
public class EnumeratorTest {

  public static void Main(string[] args) {
    OddList list = new OddList (100);
    for (int i =0; i < 100; ++i) list.Add(i);
    IEnumerator enums = list.GetEnumerator();
    while (enums.MoveNext()) {
      Console.WriteLine(enums.Current);
    }
  }
  public class OddList : ArrayList {
    public OddList() : base() {}
    public OddList(ICollection coll): base(coll) {}
    public OddList(int size): base(size) {}
    public override IEnumerator GetEnumerator() {
      return new OddNumberEnumerator
      ((ArrayList)this.Clone());
    }
    class OddNumberEnumerator : IEnumerator {
      ArrayList innerList;
      int index;

      public OddNumberEnumerator (ArrayList list) {
        calcOdds(list);
        Reset();
      }
      public bool MoveNext() {
        index++;
        if (index >= innerList.Count) {
          return false;
        } else {
          return true;
        }
      }
      public void Reset() {
        index = 1;
      }
      public object Current {
        get {
          return innerList[index];
        }
      }
      private void calcOdds(ArrayList list) {
        innerList = new ArrayList();
        foreach (int i in list) {
          if (i %2 != 0) innerList.Add(i);
        }
      }
    }
  }
}

The output of Listing 14.9 is as follows:

1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43
45 47 49

7 59 61 63 65 67 69 71 73 75 77 79 81 83 85 87 89 91 93 95 97 99

IEnumerator is similar in function to the java.util.Iterator interface, although IEnumerator does not allow elements to be removed from the underlying collection while they are being iterated through.


14.4 Type-Safe Collections

C# collections are by default not typed; everything is stored as System.Object, and you must cast the object obtained from the collection to your specific object. Also, C# collections don't check whether the added object is of any specific type. You could have an ArrayList containing objects of different types. In contrast, arrays are strongly typed. To restrict the type of the objects that can be added to a collection, the collection should have a mechanism to enforce type checking at the time elements are added or modified. Currently, Java has no such built-in mechanism, so programmers must roll their own implementations for typed collections.

In the CollectionBase and DictionaryBase abstract classes, C# provides a standardized infrastructure for ensuring type safety. Type safety in collections is enforced by events that are triggered whenever the structure and content of the collection are changed. These events can be implemented by subclasses, and this is where they check whether the element triggering the event is of the required type.

Listing 14.10 brings home the point. We have a custom Person class, and we want a custom PersonCollection collection class that holds only objects of type Person.

Listing 14.10 Type-Safe Collections (C#)
using System;
using System.Collections;

public class PersonCollection : CollectionBase {

  protected override void OnInsert(int index, object o) {
    Person p = o as Person;
    if (p == null) {
      throw new ArgumentException("Please add only Person
                                               objects");
    }
  }
  public static void Main(string[] args) {
    IList ll = new PersonCollection();
    ll.Add(new Person());
    try {
      ll.Add(new ArrayList());
    } catch (Exception e) {
      Console.WriteLine(e.Message);
    }
    Console.WriteLine("PersonCollection "+ll.Count);
  }
}

public class Person {

  string id;
  string name;

}

The output of Listing 14.10 is as follows:

Please add only Person objects
PersonCollection 1

In Listing 14.10 we tried to add an ArrayList object to the Person Collection, and it complained. That's because we check for the Person type in the OnInsert event, which is fired whenever we try to add an element to the collection. There are other events that are fired whenever the structure of the collection is changed. To provide complete type safety, we would also have to override these events.


14.5 Custom Collections

The .NET Collections library does not contain nearly as many classes and interfaces as the java.util package. The Collections library currently has no support whatsoever for sets. Sometimes you must roll your own collections. You do so by implementing some of the System.Collections interfaces.

In the next two listings we will try to write a C# version of the java.util.LinkedHashMap, a special HashMap introduced in JDK 1.4. Unlike the ordinary HashMap, this new one stores the key-value pairs in the order in which they are added to the map. The ability to retain this order is critical when, say, you are fetching data from a database and that data is already sorted according to certain criteria and you don't want to destroy the sort order by putting the fetched entries in an ordinary HashMap.

Listing 14.11 shows the KeySet class. Following the pattern of a java.util.Map, the keys of a HashMap form a Set, and so to create our HashMap we first need a data structure for storing the unique keys.

Listing 14.11 The KeySet Class (C#)
public class KeySet : IList {
  ArrayList innerList;
  public KeySet() {
    innerList = new ArrayList();
  }
  public void Add (object o) {
    if (!innerList.Contains(o)) innerList.Add(o);
  }
  public void Clear () {
    innerList.Clear();
  }
  public bool Contains (object o) {
    return innerList.Contains(o);
  }
  public int IndexOf (object o) {
    return innerList.IndexOf(o);
  }
  public void Insert (int index, object o) {
    if (!innerList.Contains(o)) {
      innerList.Insert(index, o);
    }
  }
  public void Remove (object o) {
    innerList.Remove(o);
  }
  public void RemoveAt (int index) {
    innerList.RemoveAt(o);
  }
}

The KeySet class is a wrapper over the ArrayList class. KeySet checks for the existence of an entry in the list before adding the entry to the list. In this way, the ArrayList always contains unique values.

Next, we use the KeySet class to write the C# LinkedHashtable (Listing 14.12). Our hash table will merely wrap C#'s Hashtable and in addition will maintain an internal KeySet object to make sure the keys are not reordered.

Listing 14.12 The LinkedHashtable Class (C#)
using System;
using System.Collections;

public class LinkedHashtable : IDictionary {
  public static void Main(string[] args) {
    LinkedHashtable lh = new LinkedHashtable();
      Console.WriteLine("**** LinkedHashtable **** ");
    print(lh);
    Hashtable map = new Hashtable();
    Console.WriteLine("**** Hashtable **** ");
    print(map);
  }
  private static void print(IDictionary map) {
    for (int i =0; i < 10; ++i)
      map.Add(i, 10*i);
    foreach (object key in map.Keys) {
      Console.WriteLine("Key ="+key+" Value = "  ap[key]);
    }
  }

  KeySet keys = new KeySet();
  Hashtable table;
  public LinkedHashtable () { table = new Hashtable(); }
  public void Add(object key, object val) {
    table.Add(key, val);
    keys.Add(key);
  }
  public void Clear() {
    table.Clear();
    keys.Clear();
  }
  public bool Contains(object key) {
    return table.Contains(key);
  }
  public IDictionaryEnumerator GetEnumerator() {
    return table.GetEnumerator();
  }

  IEnumerator IEnumerable.GetEnumerator() {
    return ((IEnumerable)table).GetEnumerator();
  }

  public void Remove(object key) {
    table.Remove(key);
    keys.Remove(key);
  }
  public bool IsSynchronized {
    get {
      return table.IsSynchronized;
    }
  }
  public object SyncRoot {
    get {
      return table.SyncRoot;
    }
  }
  public int Count {
    get {
      return table.Count;
    }
  }
  public bool IsFixedSize {
    get {
      return table.IsFixedSize;
    }
  }
  public bool IsReadOnly {
    get {
      return table.IsReadOnly;
    }
  }
  public ICollection Keys {
    get {
      return keys;
    }
  }
  public ICollection Values {
    get {
      return table.Values;
    }
  }
  public void CopyTo(System.Array array, int index) {
    table.CopyTo(array, index) ;
  }
  public object this[object index] {
    get {
      return table[index];
    }
    set {
      table[index] = value;
    }
  }

}

The output of Listing 14.12 is as follows:

**** LinkedHashtable ****
Key =0 Value = 0
Key =1 Value = 10
Key =2 Value = 20
Key =3 Value = 30
Key =4 Value = 40
Key =5 Value = 50
Key =6 Value = 60
Key =7 Value = 70
Key =8 Value = 80
Key =9 Value = 90
**** Hashtable ****
Key =9 Value = 90
Key =8 Value = 80
Key =7 Value = 70
Key =6 Value = 60
Key =5 Value = 50
Key =4 Value = 40
Key =3 Value = 30
Key =2 Value = 20
Key =1 Value = 10
Key =0 Value = 0

You can see from the output that the Hashtable class does not retain the order in which the entries were inserted, but LinkedHashtable does.


14.6 Summary

You've now been briefed on the interfaces and their implementations in the System.Collections namespace. Following are the key points in this chapter:

  • The System.Collections namespace contains the ICollection, IList, and IDictionary interfaces, which are similar to java.util.Collection, java.util.List, and java.util.Map, respectively, with one difference: In C# every collection inherits from ICollection. Java, on the other hand, maintains two root interfaces: java.util.Collection (which is used for data structures with sequential or index-based access—for example, ArrayList) and java.util.Map, which is the root interface for all classes that store key-value pairs. Similarities also exist between the Java and C# interfaces System.Collections.IComparer and java.util.Comparator, and System.Collections.IEnumerator and java.util.Iterator.

  • By default, none of the classes in the System.Collections namespace are thread-safe, but you can make them thread-safe by using a Synchronized static method in the respective class that returns a thread-safe wrapper of the class. C# supports both fixed-size and read-only collections. Fixed-size collections are those whose size cannot be changed by adding or deleting an element. Java supports only read-only collections. You can store value types in C# collections because of boxing and unboxing. Some of the list-based collection classes can be accessed using the indexer notation. Java does not allow primitives to be stored in the collections, nor does it provide an indexer-like notation for accessing collection elements.

  • The C# Hashtable is equivalent to Java's java.util.HashMap and not java.util.Hashtable because the C# class is not thread-safe. Also in C#, a Hashtable will throw an exception if an attempt is made to add a key that already exists in the Hashtable. This approach is different from that of Java, which allows you to silently overwrite the old value with the new value for that key. In C# the Stack and Queue classes are not list-based data structures and therefore cannot be accessed using indexer-like notation. In Java, elements of a java.util.Stack can be accessed like elements of a java.util.Vector. The java.util.ArrayList is equivalent to the C# ArrayList. The C# SortedList is similar to the java.util.TreeMap in function.

  • C# provides two abstract classes—CollectionBase and DictionaryBase—that make it possible to write type-safe collections in a standardized manner.


Chapter 15. Working with the C# I/O API

Input/output operations are a fundamental aspect of any programming language. I/O operations are also usually the bottleneck of any enterprise application. Operations such as accessing a disk file and accessing a database tend to be a degree slower than accessing in-memory data structures. This chapter looks at C#'s I/O classes and explores how to conduct routine I/O operations in C#. We look at streams, text, and binary file reading and writing, as well as files and directories, asynchronous I/O, and serialization.


15.1 Streams

C#'s core I/O classes and interfaces are defined in the System.IO namespace. The System.IO.Stream class is an abstract class that abstracts out the concept of a stream as merely a sequence of bytes. A stream can be read from or written to synchronously or asynchronously depending on the method call you use. A stream of bytes is usually associated with the underlying medium on which I/O operations such as a read/write are carried out. The medium can be an I/O device, a disk file, memory, or a TCP/IP socket. Java has a similar concept of streams (java.io.InputStream and java.io.OutputStream). The C# System.IO.Stream class is somewhat equivalent to the Java classes, although it does not have separate classes for reading and writing; instead, it has CanRead and CanWrite properties that indicate which operation can be carried out on the stream.

There are certain basic operations that can be carried out on all subclasses of the System.IO.Stream class. Table 15.1 shows a subset of these operations that are obvious to Java programmers.

Table 15.1. Core Operations of the System.IO.Stream Class

Class

Description

Read

Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.

[Serializable]
public abstract int Read(
  in byte[] buffer,
  int offset,
  int count
);

Write

Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.

Close

Closes the current stream and releases any system resources associated with the stream.

Flush

Writes the underlying stream's buffered data to be written to the underlying device the stream is abstracting.

Seek

Sets the position within the current stream.

Table 15.2. Equivalent Java and C# I/O Methods

Java Class

Java Method

Equivalent C# System.IO.Stream Method

java.io.InputStream

read()

Read()

java.io.OutputStream

write()

Write()

java.io.InputStream

close()

Close()

java.io.OutputStream

close()

Close()

java.io.OutputStream

flush()

Flush()

java.io.InputStream

mark, markSupported(), reset()

Seek()

Note that the System.IO.Stream class has both the Read() and Write() methods because it can function as both an input stream and an output stream.

Table 15.2 shows the equivalency between Java's I/O class methods and C#'s System.IO.Stream methods.

The C# Stream class has the Length and Position properties, which make it easy to determine your progress in accessing (reading or writing) the stream. In Java, the onus of keeping track of the position inside the stream is on the programmer.

You can use the CanSeek, CanRead, and CanWrite properties to decide whether you are dealing with an input or an output operation. Listing 15.1 shows the Stream class in action.

Listing 15.1 Using the System.IO.Stream Class (C#)
using System;
using System.IO;

public class Test {

  public static void Main(string[] args) {

    Stream s = null;
    try {
      s = new MemoryStream();
      WriteStream(s);
      ReadStream(s);
      BulkWriteStream(s);
      BulkReadStream(s);
      SeekStream(s);
    } finally {
      s.Close();
    }
  }
  private static void WriteStream(Stream s) {
    for (int i = 0; i < 100; ++i) s.WriteByte((byte)i);
  }
  private static void ReadStream(Stream s) {
    s.Position = 0;
    int i = 0;
    while ((i = s.ReadByte()) != 1) {
        Console.WriteLine(i);
    }
  }
  private static void BulkReadStream(Stream s) {
    s.Position = 0;
    byte[] arr = new byte[s.Length];
    int bytesRead = 0;
    int bytes2Read = (int) s.Length;
    while (bytesRead > 0) {
      int val = s.Read(arr,bytesRead, bytes2Read);
      if (val == 0) break;
      bytes2Read = val;
      bytesRead += val;
    }
  }
  private static void BulkWriteStream(Stream s) {
      byte[] bytes = new byte[100];
      s.Write(bytes,0, 100);
      s.Flush();
  }
  private static void SeekStream(Stream s) {
    long start = 1;
    while (s.Position != 1){
      long position = s.Seek(start, SeekOrigin.End);
      Console.WriteLine(s.ReadByte());
      ——start;
    }
  }
}

In ReadStream() and WriteStream() we read and write one byte at a time. In BulkReadStream and BulkWriteStream we read and write as much as we can using byte[].

Note how the Position property is used to determine the position in the stream. After the first method call (WriteStream), the position is 100. This must be reset to 0 so that ReadStream() can read the stream from the beginning. Note that in BulkReadStream the Read(byte[], int, int) method may not read all the bytes at once, and hence we must keep track of how many bytes are read and how many are to be read.

In the last method call, SeekStream(), we try to go backwards in the Stream. We use C#'s Seek() method call to initially position the stream pointer at the end of the Stream. We use the SeekOrigin enumeration to specify where we want to position the pointer. After the pointer is positioned, we read one byte at a time. Here, the ReadByte() method moves one byte in the opposite direction until we reach the beginning of the Stream.

The four subclasses of the Stream class are FileStream, Buffered Stream, MemoryStream, and NetworkStream. Let's look at each subclass in turn.


15.2 The FileStream Class

A FileStream abstracts the concept of a disk file or an input/output device. Using the FileStream class you can do primitive read/write operations (as shown in Listing 15.1) on a sequence of bytes read from a file. We call these "primitive" operations because they deal with the underlying data in terms of byte arrays. Also, the functionality of the set of methods available on the Stream class and its subclasses is fairly limited.

The System.IO.FileStream class is a close relative of java.io.FileInputStream and java.io.FileOutputStream. By default, the C# FileStream class deals with files in the slower synchronous mode. Listing 15.2 shows how the FileStream class can be used to write text to a file.

Listing 15.2 Using the Basic Stream Operations to Create a File (C#)
using System;
using System.IO;
public class Test {
  public static void Main(string[] args) {
    String path = @"c:\Book\test.text";
    FileAccess access = FileAccess.Write;
    FileMode mode = FileMode.Create;
    FileShare share = FileShare.Read;
    Stream fileStream = null;
    try {
      fileStream = new FileStream(path, mode, access, share);
      WriteFile(fileStream);
    } finally {
      if (fileStream != null) fileStream.Close();
    }
  }
  private static void WriteFile(Stream s) {
    for (int i =0; i < 100; ++i) {
      s.WriteByte((byte)i);
    }
  }
}

For Java programmers it might come as a welcome relief that in C# you do not have to put escape characters in string literals as long as you prefix them with @. In Java the path in Listing 15.2 would have to be specified as

String path = "c:\\Book\\test.txt";

With C# you can use the verbatim string literals and simply precede them with @.

The FileStream class constructor is an overloaded one, and you can specify several parameters to indicate what type of file you must interact with. In the preceding example, a new file is created with the FileMode enumeration set to Create and with the FileAccess enumeration set to Write. The FileMode enumeration specifies how the OS should open the file. The FileAccess enumeration specifies constants for read/write access. The FileShare enumeration is set to Read so that other processes can read the file while this file is kept opened by another process. The first argument to the FileStream constructor is the string name of the file to be created. The path can point to a physical file on the disk or device. Java programmers should note that the path cannot have the URI format (file://c://book//test.text); using that format will throw a runtime exception.

Also note that C#'s IOException is an unchecked exception. The compiler won't complain if you are not explicitly catching the IOException. This is unlike Java, where most methods of the java.io.FileInputStream or java.io.FileOutputStream throw a java.io.IOException. This somewhat simplifies C# I/O code because it does not have to be inside try-catch blocks.

Listing 15.2 generates a file in the specified directory (assuming that the directory exists; otherwise a System.IO.DirectoryNotFoundException is thrown). The file created does not contain any legible text because we are simply writing raw bytes to it.


15.3 The BufferedStream Class

A buffer is a block of bytes in memory used to cache data, thereby reducing the number of calls to the operating system. Buffers thus improve read and write performance. A buffer can be used for either reading or writing, but never both simultaneously. This is similar to Java's java.io.BufferedInputStream and java.io.BufferedOutputStream. In Java a typical practice is to wrap streams into their buffered versions so as to reduce the I/O calls. The same principle holds true for C#. System.IO.BufferedStream wraps over other streams (which do the actual I/O—say, a FileStream). Wrapping the FileStream with a BufferedStream speeds up file read and write operations.

Listing 15.3 shows how to read the file created in Listing 15.2 using a BufferedStream.

Listing 15.3 Using a BufferedStream to Read a File (C#)
using System;
using System.IO;

public class Test {

  public static void Main(string[] args) {
    String path = @"c:\anchor.text";
    FileAccess access = FileAccess.Read;
    FileMode mode = FileMode.Open;
    FileShare share = FileShare.Read;
    BufferedStream buffStream = null;
    try {
      buffStream = new BufferedStream(new
        FileStream(path, mode, access, share));
      buffStream.Position = 0;
      int i = 0;
      while ((i = buffStream.ReadByte()) != 1) {
        Console.WriteLine(i);
      }
    } finally {
      if (buffStream != null) buffStream.Close();
    }
  }
}


15.4 The MemoryStream Class

A memory stream is a stream whose backing store is your computer's memory. Memory streams can reduce the need for temporary buffers and files in an application. Memory streams encapsulate data stored as an unsigned byte array.

The MemoryStream class is ideal for temporary storage of binary data. For example, to process an image file, you could read the file and save to a MemoryStream for later manipulation. The ToArray method of Memory Stream can then be later used to retrieve the byte[] representing the images even when the MemoryStream is closed. The following code snippet shows how to read an image file and store it in a MemoryStream:

Image img = new Bitmap(@"C:\MyFace.gif");
MemoryStream output = new MemoryStream();
img.Save(output, ImageFormat.Jpeg);
output.Close();
byte[] myFaceInPieces = img.ToArray();


15.5 The NetworkStream Class

A network stream is a stream whose backing store is the network. A network stream is the stream used by network sockets to communicate with each other.

To illustrate the use of the NetworkStream class, let's look at a simple client/server chat application in which the server listens to an open socket at a port for incoming messages from the client. The server socket provides hooks for both the incoming network stream and the outgoing network stream. The client sends messages to the server using its version of the network stream. The following code snippet shows how the server end uses NetworkStream:

IPAddress ipAddress = Dns.Resolve("localhost").AddressList[0];
TcpListener listener = new TcpListener(ipAddress, 1234);
listener.Start();
Socket socket4Client = listener.AcceptSocket();
NetworkStream networkStream = new NetworkStream(socket4Client);

//Do your I/O stuff to chat with the client

The client side of the application would use the NetworkStream this way:

TcpClient client = new TcpClient("localhost", 1234);
NetworkStream networkStream = client.GetStream();
//Do your I/O stuff to chat with the server

So far we've looked at streams, which are wonderful for reading and writing bytes. But sometimes you want to deal with textual data, and the next section introduces readers and writers for achieving text-based reading and writing from streams.


15.6 Readers and Writers

The Stream class and its subclasses are used for byte-level input/output. For actual character I/O, you should use System.IO.TextWriter and System.IO.TextReader.

The TextWriter class has several overloaded Write methods that write primitives to the underlying medium. All these methods write the primitive values as strings. The WriteLine method of this class writes the data and follows it with the line terminator character. The TextReader class has matching methods for reading data one character at a time (using the Read()method), reading a sequence of characters into a char[], or reading a line of characters.

You must be careful about using the ReadLine method; sometimes a line can be extremely long, and although the TextReader class maintains an internal buffer for storing the read characters of the line, it can throw an OutOfMemoryException for long lines.

Both TextWriter and TextReader are abstract classes, and therefore the two subclasses StreamWriter and StreamReader are typically used to harness the reader/writer functionality. ReadLine() and WriteLine() are two convenient methods for reading and writing text data to a file.

Listing 15.4 shows the usage of the reader and writer classes.

Listing 15.4 Using StreamWriter and StreamReader to Read and Write Text Files (C#)
using System;
using System.IO;
public class Test {
  private const string PATH = "c:\\Book\\test.txt";
  public static void Main(string[] args) {
    StreamWriter sw = null;
    try {
      sw = CreateFile();
      WriteText(sw);
      ReadText(sw);
    } catch (IOException e) {
      Console.WriteLine(e.StackTrace);
    } finally {
      if (sw != null) sw.Close();
    }
  }
  private static StreamWriter CreateFile() {
    FileStream fs = new FileStream(PATH,FileMode.OpenOrCreate,
                         FileAccess.ReadWrite, FileShare.None);
    return new StreamWriter(fs); // (1)
  }
  private static void WriteText(StreamWriter sw) {
    sw.WriteLine("Testing the StreamWriter capabilities");
    sw.WriteLine("Writing Boolean {0}",true);
    sw.WriteLine("Writing Float {0}",6.78);
    sw.WriteLine("Writing Long {0}",4576457L);
    sw.Write(455);
    sw.WriteLine(new Person("jack"));
    sw.Flush();
  }
  private static void ReadText(StreamWriter sw) {
    StreamReader sr = new StreamReader(sw.BaseStream); //(2)
    sr.BaseStream.Seek(0, SeekOrigin.Begin);
    while (sr.Peek() > 1) {
      Console.WriteLine(sr.ReadLine());
    }
  }
}
class Person {
  string name;
  public Person(string name) { this.name = name; }
}

The output of Listing 15.4 is as follows:

Testing the StreamWriter capabilities
Writing Boolean True
Writing Float 6.78
Writing Long 4576457
455Person

Note that you can write most of the primitives in their textual form to the file. You can also write a custom object to the file, as shown by writing the Person object. The ToString() method of the custom object is automatically invoked when the object is written.

Note that this is not the "correct" way to store the state of an object. To correctly store an object to the disk file, you must serialize it, as you will see in later sections. Writing the Person object is shown here merely to illustrate the available API calls. The file was created with the FileMode of OpenOrCreate, thereby allowing the file to be opened if it was previously created. The FileAccess enumeration is changed to ReadWrite because we are reading and writing in the same program. Both StreamWriter and StreamReader default to UTF-8 encoding unless otherwise specified. UTF-8 handles Unicode characters correctly and provides consistent results on localized versions of the operating system.

If StreamWriter and StreamReader have different encodings, you can get interesting results. For example, suppose you change (1) in Listing 15.4 to the following:

return new StreamWriter(fs, System.Text.Encoding.ASCII);

Further suppose you change (2) to

StreamReader sr = new
StreamReader(sw.BaseStream,System.Text.Encoding.BigEndian
Unicode);

After these changes, you fill get the following output:

?????????????????????????????????????????????????????????

As you can see, the type of encoding can affect your "view" of the file's contents. By default, the StreamWriter and the StreamReader classes are not thread-safe. The StreamWriter.Synchronized() and StreamReader.Synchronized() static methods should be used to create thread-safe wrappers around these classes.


15.7 Files and Directories

Now let's look at the File and Directory classes of the System.IO namespace. C# provides a rich API for working with files and directories. The System.IO.File and System.IO.Directory classes are used to model files and directories. Unlike Java, C# makes a distinction between a file and a directory. The File class consists of all static methods. The static methods of the File class perform security checks on all methods. You should use the FileInfo class if you're going to reuse a file instance so that you can avoid the repetitive security checks.

The following sections briefly discuss the static methods of this class. Some of the File static methods do not throw an IOException, instead throwing a more relevant exception.

15.7.1 AppendText

Listing 15.5 shows the usage of the AppendText function. The static AppendText method is equivalent to calling new StreamWriter (path, true), where the second Boolean parameter indicates whether the file is to be opened for appending. The method returns a StreamWriter.

Listing 15.5 Using File.AppendText (C#)
using System;
using System.IO;

public class Test {
  public static void Main(string[] args) {
    AppendText();
  }
  static void AppendText() {
    StreamWriter sw = null;
    try {
      string PATH = "c:\\Book\\test.txt";
      sw = File.AppendText(PATH);
      sw.WriteLine("File static method");
      sw.Flush();
    } catch (Exception e) {
      throw new IOException(e.StackTrace);
    } finally {
      sw.Close();
    }
  }
}

Note that even though the static function is used to return a StreamWriter, you must still call Close() on the writer to ensure that all the data is correctly written out to the stream.

15.7.2 Exists, Copy, Move, and Delete

The Exists method takes a single path parameter. This method does not throw a FileNotFoundException; instead, it throws exceptions only if the path parameter is malformed (the equivalent of a java.lang.IllegalArgumentException). Also, the parameter must point to a file and not a directory. Therefore, the following lines return true and false, respectively:

Console.WriteLine(File.Exists(TO_FILE));
Console.WriteLine(File.Exists("c:\\"));

The Copy method throws several exceptions (based on the underlying error). Whereas an IOException is thrown if the destination file exists, a FileNotFoundException is thrown if the source file is not found. By default, the Copy method does not overwrite the destination file. So if you have called Copy once on a target file, then calling Copy again will throw an IOException. To overwrite the target file, use the three-parameter call. To avoid getting an IOException upon repetitive copying of a file to the same destination, use File.Copy(source, destination, overwrite) instead of File.Copy(source, destination).

And again, when writing actual classes that use these File static methods, it's imperative to catch the correct exception and log the exception. We have omitted exception handling code from Listing 15.6 so as to accentuate the File API calls. Note that the destination must be a file; it cannot be a directory.

The Move method works across disk volumes, and it does not throw an exception if the source and destination are the same. The Delete method does not throw an exception if the file to be deleted does not exist.

Listing 15.6 Using the Move, Copy, Delete, and Exists Methods (C#)
using System;
using System.IO;

public class Test {
  public const string TO_FILE = "c:\\book\\test.txt";
  public const string COPY_TO_FILE = "c:\\book\\test1.cop";
  public const string MOVE_TO_FILE = "c:\\book\\test1.mov";

  public static void Main(string[] args) {

    Console.WriteLine(File.Exists(TO_FILE));
    Console.WriteLine(File.Exists("c:\\"));
    File.Copy(TO_FILE, COPY_TO_FILE, true);
    File.Move(TO_FILE, MOVE_TO_FILE);
    File.Delete(TO_FILE);
  }
}

15.7.3 Open, OpenWrite, OpenText, and OpenRead

These methods return either a FileStream or a StreamReader for writing or reading. Listing 15.7 shows the use of the OpenText method.

Listing 15.7 Using the OpenText() Method (C#)
using System;
using System.IO;

public class Test {
  public const string TO_FILE = "c:\\book\\test.txt";
  public static void Main(string[] args) {
    StreamReader sr = File.OpenText(TO_FILE);
    while (sr.Peek() != 1)
      Console.WriteLine(sr.ReadLine());
  }
}

The FileInfo class is more similar to the java.io.File class. All methods on this class are instance-based. Most of the static methods of the File class are duplicated as instance methods of the FileInfo class.

The System.IO.Directory class should be used for basic directory-based operations. Again, the class has only static methods, which make the appropriate security checks. To avoid repeating security checks for a directory that has already been scanned for security, use the DirectoryInfo class. Listing 15.8 shows operations for manipulating directories.

Listing 15.8 Manipulating Directories (C#)
using System;
using System.IO;

public class Test {
  const string BASE_PATH = "c:\\chuck";
  public static void Main(string[] args) {
    //Create directory
    DirectoryInfo dir = Directory.CreateDirectory(BASE_PATH);
    //Create subdirectories
    string path = null;
    for (int i =0; i < 10; ++i) {
      path = BASE_PATH+ "\\Sub"+i;
      DirectoryInfo subdir = dir.CreateSubdirectory("Sub"+i);
      Directory.SetCurrentDirectory(subdir.FullName);
      for (int j = 0; j < i; ++j) {
        subdir.CreateSubdirectory("SubSub"+j);
      }
      Directory.SetCurrentDirectory(path);
    }
    Console.WriteLine(Directory.GetCurrentDirectory());
    CreateFile();
    Directory.Delete(BASE_PATH+"\\Sub"+4, true);
    Directory.Move(BASE_PATH+"\\Sub"+3,BASE_PATH+"\\Sub"+4);
  }

  private static void CreateFile() {
    FileStream fs = new FileStream("test.txt",
      FileMode.OpenOrCreate, FileAccess.ReadWrite,
      FileShare.None);
    StreamWriter sw = new StreamWriter(fs);
    sw.WriteLine("Testing");
    sw.Flush();
    sw.Close();
  }
}

In Listing 15.8 we first create a directory using the following:

DirectoryInfo dir = Directory.CreateDirectory (BASE_PATH);

The for loop then uses the DirectoryInfo object to create subdirectories. Note that when creating a subdirectory you do not specify the fully qualified directory name (the UNC name); instead, you specify only the relative leaf directory name.

for (int i =0; i < 10; ++i) {
  path = BASE_PATH+"\\Sub"+i;
  DirectoryInfo subdir = dir.CreateSubdirectory("Sub"+i);
  for (int j = 0; j < i; ++j) {
    subdir.CreateSubdirectory("SubSub"+j);
  }
  Directory.SetCurrentDirectory(path);
}

The next part of the code sets the current working directory and then deletes a directory (recursively) using

Directory.Delete(BASE_PATH+"\\Sub"+4, true);

Then subdirectory 3 and its contents are moved to subdirectory 4.

As you can see, manipulating directories is a breeze in C#. Listing 15.9 shows how to browse directories to get useful information.

Listing 15.9 A Recursive Approach to Browsing Files in a Directory (C#)
using System;
using System.IO;

public class Test {
  const string BASE_PATH = "c:\\chuck";
  public static void Main(string[] args) {
       BrowseDirectories(BASE_PATH);
  }
  private static void BrowseDirectories(String dirPath) {
    string[] directories = Directory.GetDirectories(dirPath);
    foreach (string dir in directories) {
      string[] files = Directory.GetFiles(dir, "*.txt");
      foreach(string file in files) {
        Console.WriteLine("File Path "+file);
      }
      BrowseDirectories(dir);
    }
  }
}

Note that we use the Directory.GetFiles (path, searchpattern) call to recursively filter out files having a certain extension from a given directory. To search for a directory, use the GetDirectoryNames instance method of IsolatedStorageFile. The GetDirectoryNames method takes a string representing a search pattern. Both single-character (?) and multicharacter (*) wildcard characters are supported. These wildcard characters cannot appear in the path portion of the name; that is, directory1/*ect* is a valid search string, but *ect*/directory2 is not, according to the Microsoft .NET documentation.

15.7.4 Asynchronous I/O

All the operations we have seen so far are synchronous in nature; the calling thread blocks until the synchronous I/O operation is complete. Many times you want the calling thread to carry on with whatever it was doing after making the I/O call. In general, I/O calls are slower than in-memory calls. For most applications, it is I/O operations that slow things down, whether file or network access. Therefore, applications that are built with asynchronous I/O will be faster than their synchronous counterparts.

Listing 15.10 shows how the FileStream class is used to make both synchronous and asynchronous calls. Note that as of JDK 1.3.x, the only way to simulate asynchronous I/O in Java is through threads. However, JDK 1.4 has the java.nio.* package, which offers built-in asynchronous I/O.

Listing 15.10 Asynchronous I/O (C#)
using System;
using System.IO;
using System.Threading;

public class FileReader {

  const string PATH = "c://book//test.jsp";
  public void SyncCall() {
       FileStream fs = new FileStream(
         PATH,
         FileMode.Open,
         FileAccess.Read,
         FileShare.Read,
         1024,
         false
         );
       int x = 0;
       while ((x = fs.ReadByte()) != 1) {}
       fs.Close();
       Console.WriteLine("Sync oper finished "+DateTime.Now);
   }

   public void AsyncCall() {
     FileStream fs = new FileStream(
       PATH,
       FileMode.Open,
       FileAccess.Read,
       FileShare.Read,
       1024,
       true
       );
     WordDocState state = new WordDocState();
     state.fs = fs;
     state.content = new byte[fs.Length];
     state.resetEvent = new AutoResetEvent(false);
     AsyncCallback readDocCallBack = new
            AsyncCallback(ReadDocCallback);
     fs.BeginRead(state.content, 0, (int)state.fs.Length,
                                 readDocCallBack, state);
     state.resetEvent.WaitOne();
     Console.WriteLine("Async oper finished "+DateTime.Now);
   }
   private void ReadDocCallback(IAsyncResult asyncResult){
     WordDocState state = (WordDocState) asyncResult.AsyncState;
     FileStream stream = state.fs;
     int bytesRead = stream.EndRead(asyncResult);
     stream.Close();
     state.resetEvent.Set();
   }

   public class WordDocState {
     public FileStream fs;
     public byte[] content;
     public AutoResetEvent resetEvent;
   }

   public static void Main(string[] args) {
     FileReader fr = new FileReader();
     fr.AsyncCall();
     fr.SyncCall();
   }
 }

The asynchronous calls start with the following call:

fs.BeginRead(state.content, 0, (int)state.fs.Length,
                                        readDocCallBack, state);

To the BeginRead method we pass callback, which calls EndRead, which waits for the asynchronous call to end.


15.8 Serialization

Serialization is the process by which an object's state is persisted to an underlying medium (such as a network or a disk file), typically in binary format. After the object's state is serialized, the object can be reread from the disk file or the network through deserialization.

The .NET environment comes with formatters that take care of serializing and deserializing objects to the medium. Currently there are two formatters: the binary formatter and the SOAP formatter. The binary formatter serializes the object to a binary format. This is a good solution for transferring objects to disk or across the network between two applications that understand the binary format. The SOAP formatter puts the object into a SOAP envelope. The SOAP formatter is good for transferring objects across heterogeneous systems (such as a Java servlet reading an object serialized through the SOAP formatter in C#).

15.8.1 Basic Serialization

In Java you must implement the java.io.Serializable interface to make an object serializable. In C# you can either use the Serializable attribute or implement the ISerializable interface. Listing 15.11 shows simple binary serialization using the Serialization attribute.

Listing 15.11 Basic Serialization (C#)
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

public class Test {

  public static void Main(string[] args) {

    Person oldP = new Person("tom", 22, 1000.23f);
    Console.WriteLine("Person before serialization");
    oldP.Print();

    //Serialize
    Stream stream = File.Open("data.bin", FileMode.OpenOrCreate);
    BinaryFormatter formatter = new BinaryFormatter();
    formatter.Serialize(stream, oldP);
    stream.Close();

    //Deserialize
    stream = File.Open("data.bin", FileMode.OpenOrCreate);
    formatter = new BinaryFormatter();
    Person p = (Person)formatter.Deserialize(stream);
    Console.WriteLine("Person after de-serialization");
    p.Print();
    stream.Close();
  }
  [Serializable()]
  public class Person {
    string name;
    int age;
    [NonSerialized()] float salary;
    public Person (string name, int age, float salary) {
      this.name = name;
      this.age = age;
      this.salary = salary;
    }
    public void Print() {
      Console.WriteLine("Name {0}", name);
      Console.WriteLine("Age {0}",age);
      Console.WriteLine("Yearly Salary {0}",salary);
    }
  }
}

Note that, unlike Java, C# does not store the serialization classes in the I/O namespace. In Listing 15.11, we create a custom class called Person and mark it as serializable using the [Serializable()] attribute. The salary field of the Person class is marked as [NonSerialized()]. This is equivalent to the transient keyword in Java. The Person object is then deserialized from the disk. Note that the salary field is not saved when the Person object is deserialized. If you open the file data.bin, the contents are in binary form. If, however, we used the SoapFormatter, we could get a file. In Listing 15.11, replace the boldface lines with the following ones:

//Serialize
SoapFormatter formatter = new SoapFormatter();
formatter.Serialize(stream, oldP);

//Deserialize
formatter = new SoapFormatter();
Person p = (Person)formatter.Deserialize(stream);

Use the System.Runtime.Serialization.Formatters.Soap namespace instead of System.Runtime.Serialization.Formatters.Binary.

It is important to note that the Serializable attribute cannot be inherited. If we derive a new class from MyObject, the new class must also be marked with the attribute, or else it cannot be serialized. So if, say, Man were to extend Person and you tried to serialize and deserialize Man, it would throw a SerializationException:

public class Man : Person {
  string name;
}

Serialization is convenient because it also takes care of dependent objects, and it serializes a complex object tree if all the objects in that tree are serializable. Listing 15.12 shows a complex Person object.

Listing 15.12 Serializing with Dependent Objects (C#)
using System;
using System.IO;
using System.Collections;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

public class Test {

  public static void Main(string[] args) {

    Person oldP = new Person("tom" , 22, 1000.23f);
    ArrayList addresses = new ArrayList();
    addresses.Add( new Address("1 Plum", "LA", "CA", "90856",
                                                      "USA"));
    addresses.Add( new Address("2 Pine", "SF", "CA", "90857",
                                                      "USA"));
    addresses.Add( new Address("3 SugarCreek", "Austin", "TX",
                                             "80857", "USA"));
    oldP.addresses = addresses;
    Console.WriteLine("Person before serialization");
    oldP.Print();

    //Serialize
    Stream stream = File.Open("data.bin", FileMode.
                                     OpenOrCreate);
    BinaryFormatter formatter = new BinaryFormatter();
    formatter.Serialize(stream, oldP);

    stream.Close();

    //Deserialize
    stream = File.Open("data.bin", FileMode.OpenOrCreate);
    formatter = new BinaryFormatter();
    Person p = (Person)formatter.Deserialize(stream);
    Console.WriteLine("Person after deserialization");
    p.Print();
    for (int i = 0; i < p.addresses.Count; ++i) {
      Address add = (Address) p.addresses[i];
      add.Print();
    }
    stream.Close ();

  }

  [Serializable ()]
    public class Person {
    public string name;
    public int age;
    [NonSerialized()] public float salary;
    public ArrayList addresses;

    public Person (string name, int age, float salary) {
      this.name = name;
      this.age = age;
      this.salary = salary;
      this.addresses =;new ArrayList();
    }
    public void Print() {
      Console.WriteLine("Name {0}", name);
      Console.WriteLine("Age {0}",age);
      Console.WriteLine("Yearly Salary {0}",salary);
    }
  }
    [Serializable()]
    public class Address {
    string street, city, zip, state, country;
    public Address (string street, string city, string state,
                                   string zip, string country) {
      this.street = street;
      this.city = city;
      this.state = state;
      this.zip = zip;
      this.country = country;
    }
    public void Print() {
      Console.WriteLine("Street {0}", street);
      Console.WriteLine("City {0}",city);
      Console.WriteLine("State {0}",state);
      Console.WriteLine("Zip {0}",zip);
      Console.WriteLine("Country {0}",country);
    }
  }
}

The output of Listing 15.12 is as follows:

Person before serialization
Name tom
Age 22
Yearly Salary 1000.23
Person after deserialization
Name tom
Age 22
Yearly Salary 0
Street 1 Plum
City LA
State CA
Zip 90856
Country USA
Street 2 Pine
City SF
State CA
Zip 90857
Country USA
Street 3 SugarCreek
City Austin
State TX
Zip 80857
Country USA

15.8.2 Custom Serialization

In Java you must implement an interface to make classes automatically serializable; this will cause the classes to be serialized using the default serialization mechanism. In C#, too, you can trigger this default serialization mechanism by declaring the Serializable attribute that you saw in the earlier listings. You can also control the serialization and deserialization process by implementing the ISerializable interface. Implementing the interface provides the GetObjectData method, which is used during serialization. The runtime passes a SerializationInfo object to the method, and this object is filled with the fields that need to be serialized. During deserialization a constructor is called with the SerializationInfo object passed to the constructor. The SerializationInfo object is then used to pop the field values out.

Note that implementing the interface provides only the GetObjectData method, and hence the compiler will complain only if that method is not implemented. The compiler will not complain if you forget to implement the deserialization constructor that gets called during the deserialization process.

Listing 15.13 shows custom serialization.

Listing 15.13 Custom Serialization (C#)
using System;
using System.IO;
using System.Collections;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

  public class Person : ISerializable {
      public string name;
      public int age;
      public Address address;

    public Person(string name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
      //Deserialization constructor.
      public Person (SerializationInfo info, StreamingContext
                                                     context) {
      name = (string) info.GetString("name");
      age = (int) info.GetInt32("age");

      //Deserialize child Address object
      new Address(info, context);
    }

    //Serialization method
    public void GetObjectData (SerializationInfo info,
                                StreamingContext context) {

      info.AddValue("name", name);
      info.AddValue("age", age);

      //Serialize child address object
      address.GetObjectData(info, context);
    }
  }

  public class Address : ISerializable {
    public string street;
    public string zip;

    public Address(string street, string zip) {
      this.street = street;
      this.zip = zip;
    }

    //Deserialization constructor.
    public Address (SerializationInfo info, StreamingContext
                                                   context) {
      street = (string) info.GetString("street");
      zip = (string) info.GetString("zip");
    }

    //Serialization method
    public void GetObjectData (SerializationInfo info,
                                StreamingContext context) {
      info.AddValue("street", street);
      info.AddValue("zip", zip);
    }
  }

public class Test {
    public static void Main(string[] args) {

      Address add = new Address("252 Beaumont Ave", "65555");
      Person p = new Person("Jack",18, add);

      //Serialize
      FileStream stream = File.Open("data.bin",
                                FileMode.OpenOrCreate);
      BinaryFormatter formatter = new BinaryFormatter();
      formatter.Serialize(stream, p);

      //Deserialize
      Person p = (Person) formatter.Deserialize(stream);
    }
}

In Listing 15.13 the Person class has a child Address object, and both the class and the object implement the ISerializable interface. Both the classes provide implementation of the GetObjectData method, which is called during serialization and during the deserialization constructor.

Note the signature of the constructor. In classes that inherit from each other, it is important that the subclass call the superclass's GetObjectData method first; the deserialization constructor also accordingly calls the corresponding constructor of the superclass.

Serialization is the key mechanism by which objects are transferred over the network or are stored onto the disk file. Serialization also is a powerful mechanism to make deep copies of complex object graphs. Let's say you have a complex object graph as in Listing 15.14, in which a Region object can have several Forest objects. A Forest object can have several Tree objects, and a Tree object in turn can have several Leaf objects. Trying to make a deep copy of a Region object "manually" would be quite a chore. You would have to have a method called DeepCopy on each of the classes and have that method implemented for each class.

Listing 15.14 Complex Object Graphs (C#)
using System;
using System.IO;
using System.Text;
using System.Collections;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

[Serializable()]
public class Region {
  ArrayList forests;
  public string name;
  public Region(string name) {
    this.name = name;
    forests = new ArrayList();
    for (int i = 0; i < 2; ++i) forests.Add(
                            new Forest("Forest "+i));
  }
  public override string ToString() {
    StringBuilder sb = new StringBuilder();
    sb.Append("Region ").Append(name).Append(" has following
                                         forests \n");
    for (int i = 0; i < forests.Count; ++i) {
      sb.Append(forests[i].ToString()).Append("; bsn");
    }
    return sb.ToString();
  }
}

[Serializable()]
public class Forest {
  ArrayList trees;
  string name;
  public Forest(string name) {
    this.name = name;
    trees = new ArrayList();
    for (int i = 0; i < 3; ++i) trees.Add(new ("Tree"+i));
  }
  public override string ToString() {
    StringBuilder sb = new StringBuilder();
    sb.Append("\t").Append("Forest ") .Append (name)
                            .Append(" has following trees \n");
    for (int i = 0; i < trees.Count; ++i) {
      sb.Append(trees[i].ToString()).Append("\n");
    }
    return sb.ToString();
  }

}

[Serializable()]
public class Tree {
  ArrayList leaves;
  string name;
  public Tree(string name) {
    this.name = name;
    leaves = new ArrayList();
    for (int i = 0; i < 10; ++i) leaves.Add(new Leaf("Leaf
                                                         "+i));
  }
  public override string ToString() {
    StringBuilder sb = new StringBuilder();
    sb.Append("\t\t").Append ("Tree .Append(name).Append(
                                " has following leaves \n");
    for (int i = 0; i < leaves.Count; ++i) {
      sb.Append(leaves[i].ToString()).Append("\n");
    }
    return sb.ToString();
  }

}

[Serializable()]
public class Leaf {
  string name;
  public Leaf(string name) {
    this.name = name;
  }
  public override string ToString() {
    return name;
  }
}

Serialization can ease the pain of making deep copies. Listing 15.15 shows a DeepCopy utility that serializes an object graph to a MemoryStream and then deserializes it from that same stream. The object returned is a deep copy of the object passed in the argument.

Listing 15.15 A Deep Copy Utility That Makes Deep Copies of Any Object (C#)
using System;
using System.IO;
using System.Text;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

public class DeepCopyUtility {

  public static object DeepCopy(object o) {
    Stream stream = null;
    try {
      stream = new MemoryStream();
      BinaryFormatter formatter = new BinaryFormatter();
      formatter.Serialize(stream, o);
      stream.Position = 0;
      return formatter.Deserialize(stream);

    } finally {
       stream.Close();
    }
  }

  public static void Main(string[] args) {
    Region obj = new Region("Reg 1");
    Console.WriteLine(obj);
    Region copiedObj = (Region) DeepCopy(obj);
    Console.WriteLine(copiedObj);
    Console.WriteLine(obj == copiedObj);

  }
}

The only requirement of the DeepCopy utility is that all objects in the complex object graph be serializable. We took care of that requirement in Listing 15.14, where we declared the Serializable attribute on each class.


15.9 Summary

The System.IO namespace contains all the classes needed for doing I/O operations in C#. The classes provided in this namespace follow a similar pattern as those in Java's java.io package. The abstract System.IO.Stream class represents a sequence of bytes read from or written to a medium (file, memory, network). Wrapper classes, such as the readers and writers, sit on top of the Streams classes to provide bulk reading and writing operations. The basic I/O provided by C# is synchronous; the calling thread blocks until the I/O operation completes. C# also provides asynchronous I/O through specialized constructors, which take delegates as one of their parameters for doing asynchronous callback. Unlike Java, C# distinguishes between a file and a directory. Unlike Java's java.io.IOException, in C#, I/O operations do not have to be wrapped in a try-catch block because the exceptions thrown are runtime unchecked exceptions. Serialization in C# is provided by attaching an attribute to a class or having it implement the ISerializable interface.


Chapter 16. Thread Programming

A typical GUI application is designed to give prompt reaction to the actions taken by the user. By default, the execution path of a GUI program is synchronous; the result of its execution is displayed immediately to the user. For example, clicking on a pop-up menu pulls up the menu immediately. However, in some applications a user-initiated action cannot be completed immediately but must be batched for execution later. To achieve this asynchronous behavior, a program must somehow schedule a slice of its execution path to execute at a later time. It is almost as if this slice of execution adopts a life of its own and executes tasks on its own. Threads are constructs that allow you to programmatically allocate slices of execution to be run later.

Every executing program always has at least one thread associated with it. This is the main thread of execution. This thread can be thought of as the life force of an application. The analogy between the main thread and its application is similar to that between a life force and a human body. Without the life force, a human body is not functional and does not execute tasks. To make the application tick, something must run it. That something is the main thread of execution.

Within this main thread, an application can choose to fork off other threads that are designed to do various tasks. In this way, the application appears to execute faster because more things are getting done at the same time. For example, consider a splash screen that shows up whenever you start a program. Here, the application initializes its environment and simultaneously displays an activity to the user. To achieve this, the initialization is usually done by a background thread that is forked off when the main application is started.

The first few sections of this chapter discuss the various techniques for creating threads in C#, and the subsequent sections discuss constructs for writing thread-safe code. The API for writing thread-based code is defined in the System.Threading namespace.

Following are four ways you can simulate threading in C#:

  1. Manually creating threads and starting them using the System.Threading.Thread class

  2. Using the System.Threading.ThreadPool class to request a callback

  3. Using the System.Threading.Timer class to schedule a callback

  4. Calling BeginInvoke on a delegate

Java, on the other hand, provides two ways to create thread-based code: by extending the java.lang.Thread class or by implementing the java.lang.Runnable interface.

C# approach 1—of manually creating threads—is the one most Java programmers would relate to. Approach 2 is a C# feature that the Java API sorely lacks. There is no notion of a systemwide thread pool in Java. C# provides a first-class ThreadPool class that makes it easier to manage created threads. In fact, approach 2 is the recommended method to create threads in C#. Approach 3 is similar to use of the java.util.Timer utility. Java has no equivalent for approach 4. (Note that we discuss the terms "callback" and "delegates" in this chapter, but they are formally discussed in Chapter 18.)

Associated with threads is data that needs to be thread-safe. Typically, you make data thread-safe by locking a block of code and allowing that block of code to be owned or changed by only one thread at a time. Upon execution of that block of code, the thread releases the lock and allows other threads to execute the code. This mechanism of locking a block of code is also known as thread synchronization.

Let's begin by briefly enumerating the members of the System.Threading namespace, where everything thread-related originates.


16.1 The System.Threading Namespace

System.Threading is the namespace where all thread-related C# classes reside. The following tables list the classes (Table 16.1), delegates (Table 16.2), and enumerations (Table 16.3) of this namespace.

The following sections discuss the key classes, delegates, and enumerations in the namespace.

Table 16.1. System.Threading Classes

Class

Description

AutoResetEvent

Notifies one or more waiting threads that an event has occurred. This class cannot be inherited.

Interlocked

Provides atomic operations for variables that are shared by multiple threads.

ManualResetEvent

Occurs when one or more waiting threads are notified that an event has occurred. This class cannot be inherited.

Monitor

Provides a mechanism that synchronizes access to objects.

Mutex

A synchronization primitive that can also be used for interprocess synchronization.

ReaderWriterLock

Defines the lock that implements single-writer and multiple-reader semantics.

RegisteredWaitHandle

Represents a handle that has been registered when calling RegisterWaitForSingleObject. This class cannot be inherited.

SynchronizationLockException

The exception that is thrown when a synchronized method is invoked from an unsynchronized block of code.

Thread

Creates and controls a thread, sets its priority, and gets its status.

ThreadAbortException

The exception that is thrown when a call is made to the Abort method. This class cannot be inherited.

ThreadExceptionEventArgs

Provides data for the ThreadException event.

ThreadInterruptedException

The exception that is thrown when a thread is interrupted while it is in a waiting state.

ThreadPool

Provides a pool of threads that can be used to post work items, process asynchronous I/O, wait on behalf of other threads, and process timers.

ThreadStateException

The exception that is thrown when a thread is in an invalid ThreadState for the method call.

Timeout

Contains a constant used to specify an infinite amount of time. This class cannot be inherited.

Timer

Provides a mechanism for executing methods at specified intervals. This class cannot be inherited.

WaitHandle

Encapsulates operating-system-specific objects that wait for exclusive access to shared resources.

Table 16.2. System.Threading Delegates

Delegate

Description

IOCompletionCallback

Receives the error code, number of bytes, and overlapped value type when an I/O operation completes on the thread pool.

ThreadExceptionEventHandler

Represents the method that will handle the ThreadException event of an application.

ThreadStart

Represents the method that will handle the Start event of the Thread class.

TimerCallback

Represents the method that will handle the state of a Timer.

WaitCallback

Defines the callback method for thread pool user work items. That method must match this delegate.

WaitOrTimerCallback

Receives a Boolean parameter that determines whether the handle was signaled or the WaitHandle timed out.


16.2 Creating Threads Using the System.Threading.Thread Class

In C# you create a thread by using the Thread class and the ThreadStart delegate. A delegate is a C# construct that is essentially a reference to a function. This reference can be treated like a regular object reference. For example, the delegate defined in the following code says that you can create a reference to a function whose signature is similar to that of the CalcIncome method:

Table 16.3. System.Threading Enumerations

Enumeration

Description

ApartmentState

Specifies the apartment state of a thread. An apartment is a logical container within a process for objects sharing the same thread access requirements. All objects in the same apartment can receive calls from any thread in the apartment. The .NET Framework does not use apartments, and managed objects are responsible for using all shared resources in a thread-safe manner themselves.

ThreadPriority

Specifies the scheduling priority of a thread.

ThreadState

Specifies the execution states of a thread.

public delegate float CalcIncome (float hourlyWages);

To use the delegate, you write the following:

1. CalcIncome del = new CalcIncome (FunctionName);
2. del (25.4f);

In line 1, del is a reference to the function CalcIncome. The FunctionName is the name of the function that actually gets called. The signature of FunctionName should match that of the CalcIncome delegate. In line 2 the value 25.4f is passed to the delegate. This call merely gets delegated to the function FunctionName, with the value 25.4f passed as the argument.

16.2.1 Starting a Thread

The Thread class takes an instance of the ThreadStart delegate as the only parameter in its constructor:

Thread t = new Thread (new ThreadStart(functionName));

Here, functionName is the name of the function or object method that the ThreadStart delegate will call. After the thread is instantiated, it can be run by a call to the Start method:

t.Start();

Listing 16.1 demonstrates how to start a thread and execute a function. The function executed is Calculate(), which is an instance method of the Test class. To use it in the ThreadStart delegate, you create a Test object and pass the function name. If Calculate() were a static method, then the delegate function would have been specified as Test.Calculate.

Listing 16.1 Starting a Thread (C#)
using System;
using System.Threading;

public class Test {
  int i = 0;
  static void Main(string[] args) {
    Thread t = new Thread(new ThreadStart(new
Test().Calculate));
    t.Start();
  }
  public void Calculate() {
    Console.WriteLine("The value of i is {0}", ++i);
  }
}

The output of Listing 16.1 is as follows:

The value of i is 1

Before we proceed to do anything serious with threads, an understanding of the thread states in C# is essential. So let's discuss the various thread states in detail.

16.2.2 The Life Cycle of a Thread

Threads in C# have definite states of execution, as shown in Table 16.4. After a thread is created, it is in at least one of the states until it terminates. Threads created are initially in the Unstarted state. An Unstarted thread is transitioned into the Running state by a call to Start.

A thread can be in more than one state at a given time. For example, if a thread is blocked on a call to Wait and if another thread calls Abort on the blocked thread, the blocked thread will be in both the WaitSleepJoin and the AbortRequested states at the same time. In this case, as soon as the thread returns from the call to Wait or is interrupted, it will receive the ThreadAbortException to begin aborting. ThreadState can be used for thread management. Knowing the state of a thread can be used to decide the course of action to be taken with the thread.

Table 16.4. C# Thread States

Thread State

Description

Aborted

The thread is in the Stopped state.

AbortRequested

The Thread.Abort method has been invoked on the thread, but the thread has not yet received the pending System.Threading.ThreadAbortException that will attempt to terminate it.

Background

The thread is being executed as a background thread, as opposed to a foreground thread. This state is controlled by setting the Thread.IsBackground property.

Running

The thread has been started, it is not blocked, and there is no pending ThreadAbortException, or the thread is interrupted by another thread or has been asked to resume by another thread.

Stopped

The thread has stopped. This happens when a thread responds to an Abort request and the thread has terminated.

StopRequested

The thread is being requested to stop. This is for internal use only.

Suspended

The thread has been suspended.

SuspendRequested

The thread is being requested to suspend.

Unstarted

The Thread.Start method has not been invoked on the thread.

WaitSleepJoin

The thread is blocked as a result of a call to Wait, Sleep, or Join.

After a thread is created, its properties can be queried and several public methods can be called on the thread object. To illustrate a subset of the thread methods and properties, we will build on an example. We assume that we have a Robber class (Listing 16.2), a Cop class (Listing 16.3), and a driver class that jump-starts the interaction between the Cop and the Robber classes (Listing 16.4).

Listing 16.2 The Robber Class (C#)
using System;
using System.Threading;

public class Robber {

  string name;
  Thread thread;

  public Robber(string name) {
    this.name = name;
    thread = new Thread(new ThreadStart(this.Run));
    thread.Name = name;
    thread.Start();
  }
  public string Name
  {
  get {
      return name;
    }
  }
  public void Terminate() {
    thread.Abort();
  }

  public void Run() {
    while (true) {
      Console.WriteLine("Robber " ame+" "+thread.ThreadState);
      Thread.Sleep(1000);
    }
  }
  public ThreadState GetState() { return thread.ThreadState; }
}

The Robber class wraps a thread. In the constructor of the Robber class, a thread is created and started. The Run method of the Robber class is passed to the thread delegate ThreadStart, and this means that the started thread will run the contents of the Run method. Inside the Run method is an infinite loop, which basically tells the started thread to never stop running. The thread runs the function passed to the ThreadStart delegate once. As soon as the function call ends, the thread dies a natural death. By adding a while loop, we keep the thread around. Inside the Run method we don't want this thread to hog all the CPU time, so we make it sleep every 1 second. The Thread.Sleep method makes the current calling thread of the method sleep. In this case the Run method is called by the thread created in the constructor, and hence it sleeps for 1 second.

Listing 16.3 The Cop Class (C#)
using System;
using System.Threading;

public class Cop {

  string name;
  Thread thread;
  Robber[] robbers;

  public Cop(string name, Robber[] robberList) {
    this.name = name;
    this.robbers = robberList;
    thread = new Thread(new ThreadStart(this.Run));
    thread.Name = name;
    thread.Start();
  }
  public void Run() {

    while (true) {
      Thread.Sleep(2000);
      int terminatedRobbers = 0;
      for (int i =0; i < this.robbers.Length; ++i) {
        Robber rob = this.robbers[i];
        Console.WriteLine("Robber "+rob.Name+" state = "+
                           rob.GetState());
        if ((rob.GetState() & ThreadState.Aborted) > 0) {
        ++terminatedRobbers;
        }
      {
      if (terminatedRobbers == this.robbers.Length) {
        Console.WriteLine("Cop " ame+ " has terminated all
                                                    robbers ");
        break;
      }
      Console.WriteLine("Cop " ame+" "+thread.ThreadState);
      for (int i =0; i < this.robbers.Length; ++i) {
        Robber rob = this.robbers[i];
        rob.Terminate();
      }
    }
    Console.WriteLine("Cop thread signing out");
  }
  public ThreadState GetState() { return thread.ThreadState; }
  public void Finish() { thread.Join(); }
}

The Cop class is going to monitor the robbers. So its constructor takes an array of Robber objects it must monitor. The Cop class creates a thread of its own and passes a Run method to the ThreadStart delegate. The Run method makes the calling thread sleep for 2 seconds, so as to give the Robber threads some time to flee. The Run method then checks the status of all robbers, and if all the robbers are Terminated then it breaks out of the infinite loop. If there are some robbers still running, it will try to Terminate the robbers by issuing the Terminate call on the Robber object. The Terminate() method on the Robber object merely aborts the underlying robber thread. Because a thread can exist in multiple states, the bitmask operator is used to check whether a thread is in a particular state.

Listing 16.4 provides the entry point for initializing all the Cops and Robbers. Because everything happens in the constructors of the Cop and the Robber classes, Listing 16.4 merely creates the relevant objects.

Listing 16.4 Entry Point for Listings 16.2 and 16.3 (C#)
using System.Threading;
using System;

public class CopsAndRobbers {

  public static void Main(string[] args) {
    Robber[] robbers = new Robber[10];
    for (int i =0; i < 10; ++i) {
      Robber rob = new Robber("Robber "+i);
      robbers[i] = rob;
    }
    Cop cop = new Cop("Cop 1", robbers);
    Console.WriteLine("State of the cop "+cop.GetState());
  }
}

The output of this minimal adventure is as follows:

Robber Robber 0 Running
Robber Robber 1 Running
Robber Robber 2 Running
Robber Robber 3 Running
Robber Robber 4 Running
Robber Robber 5 Running
Robber Robber 6 Running
Robber Robber 7 Running
Robber Robber 8 Running
Robber Robber 9 Running
Robber Robber 0 Running
Robber Robber 1 Running
Robber Robber 2 Running
Robber Robber 3 Running
Robber Robber 4 Running
Robber Robber 5 Running
Robber Robber 6 Running
Robber Robber 7 Running
Robber Robber 8 Running
Robber Robber 9 Running
Robber Robber 0 state = WaitSleepJoin
Robber Robber 1 state = WaitSleepJoin
Robber Robber 2 state = WaitSleepJoin
Robber Robber 3 state = WaitSleepJoin
Robber Robber 4 state = WaitSleepJoin
Robber Robber 5 state = WaitSleepJoin
Robber Robber 6 state = WaitSleepJoin
Robber Robber 7 state = WaitSleepJoin
Robber Robber 8 state = WaitSleepJoin
Robber Robber 9 state = WaitSleepJoin
Cop Cop 1 Running
Robber Robber 0 state = Stopped
Robber Robber 1 state = Stopped
Robber Robber 2 state = Stopped
Robber Robber 3 state = Stopped
Robber Robber 4 state = Stopped
Robber Robber 5 state = Stopped
Robber Robber 6 state = Stopped
Robber Robber 7 state = Stopped
Robber Robber 8 state = Stopped
Robber Robber 9 state = Stopped
Cop Cop 1 Running
Robber Robber 0 state = Aborted
Robber Robber 1 state = Aborted
Robber Robber 2 state = Aborted
Robber Robber 3 state = Aborted
Robber Robber 4 state = Aborted
Robber Robber 5 state = Aborted
Robber Robber 6 state = Aborted
Robber Robber 7 state = Aborted
Robber Robber 8 state = Aborted
Robber Robber 9 state = Aborted
Cop Cop 1 has terminated all robbers
Cop thread signing out

The actual output may vary depending on the machine the code is running on. Notice how the state of the Robber thread changes from WaitSleepJoin to Stopped and then Aborted, signaling that the Robber threads have terminated. The Cop thread seems to have been terminated because it prints the last line of its Run method (Cop thread signing out).

However, the state of the cop thread isn't printed in the output. The following never gets printed:

Console.WriteLine("State of the cop "+cop.GetState());

For the line to print, we must block the main execution thread until the cop thread finishes. We can do this by calling Finish() on the Cop object, which calls Join() on the underlying thread. The last three lines of Listing 16.4 would now look like this:

Cop cop = new Cop("Cop 1", robbers);
cop.Finish();
Console.WriteLine("State of the cop "+cop.GetState());

The output now prints the state of the cop as Stopped. The call to Join() blocks the calling thread until the thread on which the Join() is called stops running.

In the preceding example we used thread states to decide the course of action. This approach might work when we know the number and types of threads we are dealing with. We will explore more sophisticated techniques of thread interaction later in this chapter.

Now let's modify the Cop class to take an attacker field. If the Cop is an attacker, then the Cop terminates the Robber (Aborts the thread); otherwise, the cop Arrests the Robber (suspends the Robber thread). Listing 16.5 shows the modified Robber, Cop, and the entry-point class.

Listing 16.5 The Modified Cop Class (C#)
using System;
using System.Threading;

public class Cop {

  string name;
  Thread thread;
  Robber[] robbers;
  bool attacker;

  public Cop(string name, Robber[] robberList, bool attacker) {
    this.name = name;
    this.robbers = robberList;
    this.attacker = attacker;
    thread = new Thread(new ThreadStart(this.Run));
    thread.Name = name;
    thread.Start();
  }
  public void Run() {

    while (true) {
      Thread.Sleep(2000);
      int terminatedRobbers = 0;
      for (int i =0; i < this.robbers.Length; ++i) {
        Robber rob = this.robbers[i];
        Console.WriteLine("Robber "+
          rob.Name+" state = "+rob.GetState());
        if (rob.GetState().Equals(ThreadState.Aborted)) {
          ++terminatedRobbers;
        }
      }
      if (terminatedRobbers == this.robbers.Length) {
        Console.WriteLine("All robbers are terminated ");
        break;
      }
      Console.WriteLine("Cop " ame+" "+thread.ThreadState);
      for (int i =0; i < this.robbers.Length; ++i) {
        Robber rob = this.robbers[i];
        if (attacker) {
          rob.Terminate();
        } else {
          rob.Arrest();
        }
      }
    }
    Console.WriteLine("Cop " ame+" is signing out");
    }
      public ThreadState GetState() { return thread.ThreadState
                                                               }
      public void Finish() { thread.Join(); }
}

The Run method of the new Cop class is modified so that if the Cop is an attacker, then it Terminates the Robber objects; otherwise, it Arrests the Robber objects. The Robber class is given in Listing 16.6.

Listing 16.6 The Modified Robber Class (C#)
using System;
using System.Threading;

public class Robber {

  string name;
  Thread thread;

  public Robber(string name) {
    this.name = name;
    thread = new Thread(new ThreadStart(this.Run));
    thread.Name = name;
    thread.Start();
  }
  public string Name {
    get {
      return name;
    }
  }
  public void Terminate() {
    if ((thread.ThreadState & ThreadState.Suspended) > 0) {
      thread.Resume();
    }
    thread.Abort();
  }
  public void Arrest() {
      thread.Suspend();
  }
  public void Resume() {
    if ((thread.ThreadState & ThreadState.Suspended) > 0) {
      thread.Resume();
    }
    }
  public void Finish() {
    thread.Join();
  }

  public void Run() {
    try {
      while (true) {
        Console.WriteLine("Robber "+
                name+" "+thread.ThreadState);
        Thread.Sleep(1000);
      }
    } catch (ThreadAbortException e) {
      Console.WriteLine("Got the exception e");
      Thread.ResetAbort();
    }
  }
  public ThreadState GetState() { return thread.ThreadState; }
}

Note that the Terminate () method now checks first whether the underlying thread was suspended; if it is, Terminate() resumes that thread and then signals it to Abort. The contents of the Run method are now wrapped inside a try-catch block. When an Abort signal is given to a thread, it usually receives the ThreadAbortException. Absence of this exception results in the thread getting into an AbortRequested state. The modified driver class is shown in Listing 16.7.

Listing 16.7 The Modified Entry Point Class (C#)
using System.Threading;
using System;

public class CopsAndRobbers {

  public static void Main(string[] args) {
    Robber[] robbers = new Robber[10];
    for (int i =0; i < 10; ++i) {
      Robber rob = new Robber("Robber "+i);
      robbers[i] = rob;
    }
    Cop cop1 = new Cop("Cop 1", robbers, true);
    Cop cop2 = new Cop("Cop 2", robbers, false);
    cop1.Finish();
    cop2.Finish();
    Console.WriteLine("State of the cop "+cop1.GetState());
    Console.WriteLine("State of the cop "+cop2.GetState());
  }
}

There are two Cop classes, each of them giving either an Abort signal or a Suspend signal to the Robber threads. The Robber class's Terminate() method is coded so that it checks whether the underlying thread has already been suspended. Doing so prevents the attacker Cop from giving an Abort signal to a thread that has been suspended by the nonattacker cop. This prevents any of the Robber threads from ever having an AbortRequested and a Suspended state at the same time.

Table 16.5 compares the key Java thread methods and their equivalents in C#.

The methods in Table 16.5 indicate that the C# Thread class is not very different from the Java class. In fact the problems with Java's thread stop() method are prevalent even in C#.

Table 16.5. The Key Thread Methods in Java and C#

Java

C#

Description

Interrupt

Interrupt

In Java this puts the thread in the interrupted state. In C#, this will interrupt the thread but does not put it in an interrupted state. If a thread is not blocking when this method is called on it in C#, the thread will be interrupted when it begins to block.

Suspend

Suspend

Functionally the same: The C# methods exhibit the same properties that caused the suspend() method in java.lang.Thread to be deprecated. This method is deadlock-prone: If a thread holding a lock on a system-critical resource is suspended, then no other threads can access the resource until the former thread is resumed.

Resume

Resume

Functionally the same.

Sleep

Sleep

The sleep call is overloaded in C# and Java. In C#, this method puts the thread in the WaitSleepJoin state.

Join

Join

Unlike the Java join methods, which are simply timeouts, the C# versions return a Boolean upon termination to signify whether the thread died (true) or the timeout expired (false).


16.3 Stopping a Thread Safely in C#

Java's stop method was deprecated because upon its call, an unchecked java.lang.ThreadDeath error will propagate up the running thread's stack, unlocking any locked monitors as it goes along. As these locks are being indiscriminately released, the data they are protecting may be left in an inconsistent state. The same problem applies to C#'s Abort method. Thus, the recommended way of stopping threads in C# is not through the Abort method. Instead, the recommended way is to use a state variable that another thread can use to signal the current thread that it's time to die. Another way is to let a thread run its course.

In the earlier examples, to illustrate the various C# methods we use the Abort method to signal the killing of the thread. But we recommend that you do not use the Abort method to kill manually created threads. Better yet, as we will explain, you should avoid creating threads manually, thereby avoiding this problem.

Creating threads manually using the System.Threading.Thread class gives maximum flexibility because the thread creator can set various properties of the thread. However, the creator must make sure that the created thread does not cannibalize system resources and therefore must provide for a way to monitor and manage the thread. Also, as discussed, many applications create threads that spend a great deal of time in the sleeping state, waiting for an event to occur. Other threads may enter a sleeping state only to be awakened periodically to poll for a change or update status information.

Following are rough guidelines for when to create and not to create threads using the System.Threading.Thread class.

You should use manually created threads under these circumstances:

  • When you need finer control over the thread to accomplish a task

  • For coarse-grained tasks that in turn monitor other asynchronous, "bulk," fine-grained tasks

  • For long-running, asynchronous tasks

You should not use manually created threads under these circumstances:

  • To handle bulk, fine-grained, short-running tasks (for example, servicing 10,000 requests)

  • To handle repetitive, time-controlled tasks for which the System.Threading.Timer class is more suitable

  • For fine-grained tasks that can be encapsulated as a delegate begininvoke or a threadpool waitcallback

16.3.1 Creating Threads Using the System.Threading.ThreadPool Class

Manually creating threads can hog system resources if the created thread is not managed properly. In Java, the onus of maintaining a thread's life cycle is on the programmer. C# gets rid of this error-prone approach by providing a first-class ThreadPool class. Thread pooling enables a more efficient use of threads and does not require the programmer to worry about the thread's life cycle because it is maintained by the thread pool.

A C# ThreadPool provides a pool of threads that can be used to post work items, process asynchronous I/O, wait on behalf of other threads, and process timers. One thread monitors the status of several wait operations queued to the thread pool. When a wait operation completes, a worker thread from the thread pool executes the corresponding callback function.

You can also queue work items that are not related to a wait operation. To request that a work item be handled by a thread in the thread pool, you call the QueueUserWorkItem method. This method takes as a parameter a reference to the method or delegate that will be called by the thread selected from the thread pool. There is no way to cancel a work item after it has been queued.

Timer-queue timers and registered wait operations also use the thread pool. Their callback functions are queued to the thread pool.

The thread pool is created the first time you create an instance of the ThreadPool class. The thread pool has a default limit of 25 threads per available processor, a limit you can change using CorSetMaxThreads, as defined in the mscoree.h file. Each thread uses the default stack size and runs at the default priority. Each process can have only one operating system thread pool.

Listing 16.8 shows how to use the QueueUserWorkItem method of the ThreadPool class. In a scenario where a thread A is spawned and does some activity, it is often required that thread B (which spawned thread A) needs to know when thread A is finished with the activity. To achieve this communication in Java, you would use the wait/notify mechanism or, in some cases, use Boolean flags to signal the completion. C# provides the concept of an AutoResetEvent class. AutoResetEvent notifies one or more waiting threads that an event has occurred. AutoResetEvent remains signaled until a single waiting thread is released, at which time the system automatically sets the state to nonsignaled. If no threads are waiting, the state remains signaled.

Listing 16.8 Using ThreadPool to Queue Callbacks (C#)
using System;
using System.Threading;

public class Test {

  public static void Main(string[] args) {
   for (int i =0; i < 50; ++i) {
     AutoResetEvent notify = new AutoResetEvent(false);
     ThreadPool.QueueUserWorkItem(
     new WaitCallback(AsyncOper),false);
     notify.WaitOne();
   }
  }

  // The callback method's signature MUST match that of a
  //System.Threading.WaitCallback
  // delegate (it takes an Object parameter and returns void)

  static void AsyncOper(Object state) {
    Console.WriteLine("WorkItem thread:
      Performing asynchronous operation.");
    // Sleep for 1000 milliseconds to simulate doing work
    Thread.Sleep(1000);
    // Signal that the async operation is now complete.
    ((AutoResetEvent)state).Set();
    Console.WriteLine("Finished the async operation");
  }

}

In Listing 16.8 the signature of the delegate called asynchronously should match the signature of the WaitCallBack delegate. The notify.WaitOne blocks the Main thread until all the async operations are completed.

Note that we dispatched 50 operations to the ThreadPool, whose capacity is 25 threads. The ThreadPool class takes care of managing the worker threads, and all we have to do is provide the callback method that ThreadPool needs to run. Had we not used the ThreadPool class, we would have had to create one Thread for each dispatched operation. This would be wasteful if this example were used to handle, say, 1,000 concurrent operations.

Listing 16.9 shows a combination of the Thread class and the ThreadPool class to depict a somewhat realistic class in which there is a Server class executing queued jobs. The Server itself runs a thread that allocates queued jobs to a ThreadPool. A main Thread enqueues the job for the Server.

Listing 16.9 The Server Class Using a ThreadPool
using System;
using System.Collections;
using System.Threading;

public class Server {

  public static int MAX = 1000;
  Thread mainThread;
  Queue jobs;
  int dequeued =0;

  public Server() {
    mainThread = new Thread(new ThreadStart(JobAllocate));
    mainThread.Name = "MyServer";
    jobs = new Queue();
    mainThread.Start();
  }

  public void AddJobs(object job) {
    lock (jobs) {
      jobs.Enqueue(job);
    }
  }

  public void JobAllocate() {
    while (true) {
      lock (jobs) {
          while (jobs.Count > 0) {
            ThreadPool.QueueUserWorkItem(
                new WaitCallback(DoJob), jobs.Dequeue());
          }
      }
      if (dequeued >= MAX) break;
    }
  }

  public void DoJob(Object state) {
    Console.WriteLine("Finished job "+state);
    ++dequeued;
  }

  public static void Main(string[] args) {
    Server server = new Server();
      for (int i = 0; i < Server.MAX; ++i) {
        server.AddJobs("Job "+i);
      }
  }
}

Note that we must put an exit condition in the JobAllocate() method for the main Thread to exit after processing the MAX number of jobs.

Following are rough guidelines of when to use and not to use thread pools.

You should use a thread pool under the following circumstance:

  • For handling bulk, fine-grained tasks of short duration.

You should not use a thread pool under the following circumstance:

  • For handling long-running, coarse-grained tasks. You don't want any thread pool threads to be dedicated for any one task.

In the example we used the lock keyword. This is a synchronization primitive of C# similar to the synchronize keyword in Java. We discuss thread synchronization in more detail in Section 16.4.

16.3.2 Creating Threads Using the System.Threading.Timer Class

There are times when an activity must be run in the background periodically. An example is that of a script running on your machine that trims the size of the log files of your application every day at midnight. You can use System.Threading.Timer to create threads that run and execute a particular action periodically. This is similar to the java.util.Timer class in its functionality.

Listing 16.10 is a slightly different version of our Server class using the Timer utility. Here, instead of the server handing off the jobs to a thread pool, a timer thread executes the queued jobs at regular intervals.

Listing 16.10 The Server Class Using the Timer Utility (C#)
using System;
using System.Threading;
using System.Collections;

class Server {
  Timer timer = null;
  Queue jobs;

  public Server() {
    jobs = new Queue();
    TimerCallback timerDelegate = new TimerCallback(DoJob);
    timer = new Timer(timerDelegate, null, 1000, 1000);
  }

  public int Count() {
    lock(jobs) {
      return jobs.Count;
    }
  }
  public void Dispose() {
      timer.Dispose();
  }
  public void DoJob(object state) {
    lock (jobs) {
      if (jobs.Count > 0) {
        Console.WriteLine("Finished job "+jobs.Dequeue());
      }
    }
  }
  public void AddJob(object job) {
    lock (jobs) {
      jobs.Enqueue(job);
    }
  }
  public static void Main(string[] args) {
    Server server = new Server();
    for (int i =0; i < 60; ++i) server.AddJob("Job "+i);
    while (server.Count() > 0) {
      Thread.Sleep(0);
    }
    server.Dispose();
  }
}

Note that the timer executes the task on a system-allocated thread much as the Java timer class does. The Server disposes of the timer thread after it has finished executing the tasks. The timer delegate indicates the method that needs to be periodically executed (in this case, every 1 second after an initial delay of 1 second). Note that the timer-initiated thread and the main thread must lock on the jobs queue to achieve predictable results. The timer class should be used for executing tasks that are periodic.

16.3.3 Creating Threads Using the Asynchronous Callback

Both ThreadPool and the Timer class use specific callback delegates as a mechanism to pass information between the caller thread and the execution thread. The same mechanism is provided in a generic manner for all delegates and can be used for attaining simple asynchronous processing (which is essentially the goal of thread programming).

Listing 16.11 shows how to create a simple asynchronous class with a simple callback delegate. The callback function can obtain a handle to the delegate by casting the IAsyncResult to the specific delegate.

Listing 16.11 Hello World Using C# Asynchronous Callback Delegates
using System;
using System.Threading;

public class MyAsyncCaller {

  public delegate string MyDelegate(string s);

  public void MyCallback(IAsyncResult iar) {
    MyDelegate del = (MyDelegate) iar.AsyncState;
    string result = del.EndInvoke(iar);
    Console.WriteLine(result);
  }

  public void Test(MyDelegate func, string param) {
    AsyncCallback cb = new AsyncCallback(MyCallback);
    func.BeginInvoke("World", cb, func);
  }

  public static string SayHello(string s) {
    return "Hello "+s;
  }

  static void Main(string[] args) {
    MyAsyncCaller caller = new MyAsyncCaller();
    caller.Test(new MyAsyncCaller.MyDelegate(SayHello),
    "World");
    Thread.Sleep(2000);
  }
}

The Main method creates an instance of MyAsyncCaller, which is a wrapper class for doing all our asynchronous calls. The Test method of that class takes the delegate and the parameter that will be passed to the delegate. The delegate passed to the Test method is a member of the MyAsyncCaller class and is called MyDelegate. The function passed is the SayHello function, which has the same signature as the MyDelegate delegate. It is this SayHello function that gets called asynchronously. AsyncCallback is a delegate provided by C#. To this delegate we pass our Callback method, which is the MyCallback method.

Inside the MyCallback method the result is cast to the delegate, and EndInvoke essentially invokes the SayHello method. The result of that method is then printed. Because there is no synchronization in this method, we must call Thread.Sleep to make sure the callbacks execute before Main finishes. To get rid of the seemingly hacky Thread.Sleep, we can use WaitHandles, as shown in Listing 16.12.

Listing 16.12 Hello World Using C# Asynchronous Callback Delegates and WaitHandles
using System;
using System.Threading;

public class MyAsyncCaller {
  public delegate string MyDelegate(string s);

  public void MyCallback(IAsyncResult iar) {
    MyDelegate del = (MyDelegate) iar.AsyncState;
    string result = del.EndInvoke(iar);
    Console.WriteLine(result);
  }

  public WaitHandle[] Test(MyDelegate func, string param) {
    AsyncCallback cb = new AsyncCallback(MyCallback);
    IAsyncResult res = func.BeginInvoke("World", cb, func);
    WaitHandle[] array = new WaitHandle[1];
    array[0] = res.AsyncWaitHandle;
    return array;
  }

  public static string SayHello(string s) {
    return "Hello "+s;
  }
  static void Main(string[] args) {
    MyAsyncCaller caller = new MyAsyncCaller();
    WaitHandle[] arr = caller.Test(new
      MyAsyncCaller.MyDelegate(SayHello), "World");
    WaitHandle.WaitAll(arr);
  }
}

The WaitHandle.WaitAll() makes the Main thread wait until all the asynchronous calls complete.


16.4 Thread Synchronization

Certain programming instructions are guaranteed to be never preempted by a thread X while another thread Y is running them. Such atomic operations have built-in thread safety. However, in OOP, operations (method calls) are seldom atomic, and the state of the object is rarely as simple as incrementing an integer.

Objects that are designed for thread safety do not allow multiple threads to simultaneously change their state. Such objects identify certain sections of the code as critical and put appropriate locks around that code so that only one thread can call that block of code at any one time. Running the block of code requires the caller thread to acquire a lock that prevents other threads from accessing that code. Upon finishing the block of code, the caller thread relinquishes the lock for other threads to use. The larger the block of code on which the lock is obtained, the longer the lock will be held by the calling thread.

C# provides the following ways to achieve thread synchronization:

  • Using very basic synchronization primitives such as WaitHandles, Mutexes, ReaderWriterLocks, and Interlocked. These constructs can be used for interprocess synchronization.

  • Using SynchronizedAttribute to enable synchronization of ContextBoundObject objects. You can use this technique to synchronize only instance fields and methods. All objects in the same context domain share the same lock.

  • Using the Monitor class. This approach is better appreciated by Java programmers. The Monitor class consists of static methods to which you pass the object you want to synchronize. C# provides a lock keyword. This is similar to Java's synchronize keyword, but unlike synchronize, the lock keyword cannot be used in a method signature. Programmers can choose to use the Monitor.Enter and Monitor.Exit methods or use the lock keyword.

The following two syntaxes achieve equivalent results. Syntax 1:

lock (this) {
}

Syntax 2:

try {
  Monitor.Enter(this);
} finally {
  Monitor.Exit(this);
}

Listing 16.13 shows four threads trying to access an instance field. In this example, at any given time the state of the field val in the BloodBank class is unpredictable because it is modified by four threads without any synchronization. The behavior of Listing 16.13 will be different on different systems.

Listing 16.13 Multithreaded Code without Synchronization (C#)
using System;
using System.Threading;

public class EntryPointClass {
  public static void Main(string[] args) {
    BloodBank bb = new BloodBank();
    Donor d1 = new Donor("D1",1000, bb);
    Donor d2 = new Donor("D2",2000, bb);
    Taker t1 = new Taker("T1",1000, bb);
    Taker t2 = new Taker("T2",2000, bb);
  }
}

class Donor {
  Thread t;
  BloodBank bb;
  string name;
  int sleep;

  public Donor(string name, int sleep, BloodBank bb) {
    this.name = name;
    this.bb = bb;
    this.sleep = sleep;
    t = new Thread(new ThreadStart(Donate));
    t.Start();
  }
  public void Donate() {
    while (true) {
      Thread.Sleep(this.sleep);
      this.bb.add();
    }
  }
}
class Taker {
  Thread t;
  BloodBank bb;
  string name;
  int sleep;

  public Taker(string name, int sleep, BloodBank bb) {
    this.name = name;
    this.bb = bb;
    this.sleep = sleep;
    t = new Thread(new ThreadStart(Take));
    t.Start();
  }
  public void Take() {
    while (true) {
      Thread.Sleep(this.sleep);
      Console.WriteLine("Blood count "+this.bb.remove());
    }
  }
}

class BloodBank {

  int val;
  public void add() {
    ++this.val;
  }
  public int remove() {
    return—this.val;
  }

}

A safer method is to synchronize on the add and remove methods of the BloodBank object. You can do this in C# by providing the following constructs:

public void add() {
  lock (this) {
  ++this.val;
      }
}

public int remove() {
  lock (this) {
    return—this.val;
  }
}

This is equivalent to having synchronized add() and remove() methods in Java.

It is important to note that threads always block each other over synchronized methods. Other threads can continue to access other "unsynchronized" methods of the object while a thread is having a lock on the object. This means that if there are two methods such as the preceding add() and remove(), only one thread can call any one of those two methods at any given time, although multiple threads could call an unsynchronized method on the same object.

It is not necessary to always lock on an object's state. Some objects are designed to be thread-unsafe and leave the onus of thread safety on the usage of the object. Servlets in Java, for example, can be accessed by multiple threads at one time as long as those threads do not share the servlet state with each other. Because servlets traditionally do not have any state associated with them, multiple threads can call the doGet() or doPost() method on them. However, servlets can be made thread-safe by implementing the SingleThreadModel, which automatically creates a servlet instance per request (this will also slow your Web application tremendously).

The wait(), notify(), and notifyAll() methods of Java's java.lang.Object have the following equivalents in C#: Monitor.Wait(object), Monitor.Pulse(object), and Monitor.PulseAll (object). Here, object is the object to wait on (usually the object on which the lock is obtained). Thread interaction, discussed earlier, is conducted using Thread.Sleep(). This is a crude approach to make threads cooperate with each other. Also, it is not always feasible to make threads cooperate with each other by querying their ThreadState, especially when the number of threads to be managed is unknown.

To make threads communicate, C# provides the Monitor.Wait and Monitor.Pulse constructs. Listing 16.14 shows an example of these two as well as the lock keyword.

Listing 16.14 Maintaining WaterQuality Using Monitor.Wait() and Monitor.Pulse (C#)
using System;
using System.Threading;
public class WaterQuality {

  private long PotableLimit;
  private long PollutedLimit;
  private long pollution;

  public WaterQuality(long PotableLimit, long PollutedLimit,
                                       long currentPollution) {
    this.PotableLimit = PotableLimit;
    this.PollutedLimit = PollutedLimit;
    this.pollution = currentPollution;
  }

  public void Purify() {
    lock(this) {
      while (true) {
        while (pollution < PotableLimit) {
          Monitor.Wait(this);
        }
        — —pollution;
        Console.WriteLine("Purifying = "+pollution);
        Monitor.PulseAll(this);
      }
    }
  }

  public void Pollute() {
    lock(this) {
      while (true) {
        while (pollution > = PollutedLimit) {
          Monitor.Wait(this);
        }
        ++pollution;
        Console.WriteLine("Polluting = "+pollution);
        Monitor.PulseAll(this);
      }
    }
  }
  static void Main(string[] args) {
    WaterQuality ct = new WaterQuality (10, 200, 3000);
    Thread polluter = new Thread(new ThreadStart(ct.Pollute));
    Thread purifier = new Thread(new ThreadStart(ct.Purify));
    polluter.Start();
    purifier.Start();

  }

}

The WaterQuality class is subject to pollution and purification through the Pollute() and Purify() methods, which are called by two different threads. We want to maintain the water quality between the potable and the polluted limit. The Pollute() and Purify() methods use the Wait() and PulseAll() methods to make the two threads take turns polluting and purifying. Note a couple of things:

  • The lock keyword locks onto the object instance this. This is similar to using synchronize in Java on the entire method.

  • Monitor.Wait() and Monitor.Pulse must be called within the lock (this) block. This is similar to wait() and notify() in Java, which must be called within synchronized blocks.

The program execution can be explained as follows. Initially, both the polluter and the purifier thread start. The polluter thread obtains a lock on this (the WaterQuality object), thereby preventing the Purifier thread from doing anything. At that point the Purifier thread is in the Running state but is not doing any purification. The Polluter thread blocks on the Monitor.Wait(this) call because the condition in the while loop of the Pollute method is true (the initial pollution, 3,000, is greater than 200). The Polluter thread relinquishes the lock and enters the object's wait queue (a queue of threads waiting to obtain the lock). The Purifier thread is notified of the lock availability through the Monitor.PulseAll(this) method. The Purifier exits the wait queue and then enters the ready queue and takes over, purifying the water all the way to the potable limit (10), after which it does not purify any more. At this point, it blocks on the WaterQuality object (through the Monitor.Wait(this) call in the Purify method because the condition in the while loop of the Purify method is true).

The Purifier thread then relinquishes the previously obtained lock from the Polluter and notifies the Polluter to take over. The Polluter takes over again, now incrementing the pollution up to 200, after which it blocks again, asking the purifier to start purifying. This cycle keeps repeating, and the two threads work endlessly to keep the WaterQuality pollution between 10 and 200.

You can change the interaction between the threads by changing the values for the potable limit, the pollution limit, and the current pollution. A current pollution of less than 10 will trigger the Polluter to work first and bump the pollution to 200.

Had we not used the Monitor.Wait and the Monitor.Pulse methods in this example, the Polluter thread would have cannibalized CPU time because it would never have relinquished the lock on the WaterQuality object while it was busy polluting the water. The Monitor.Wait(this) method tells the calling thread to wait on this. The calling thread then goes into a wait queue and stays in the wait queue until it is told to move to the ready queue through the Monitor.Pulse(this) call. The Monitor.Pulse(this) notifies the thread waiting in the wait queue for this object's lock. Note that you are encouraged to use Monitor.PulseAll(this) instead of Monitor.Pulse().

Locking can be done on any object. Typically, you lock the object that is going to be changed or accessed by multiple threads. You can create a static lock by locking on a static object. This is equivalent to synchronizing on a static object in Java.

16.4.1 The ReaderWriterLock Class

Earlier we found that in C#, synchronizing a block of code is as easy as wrapping a lock construct around the code. The most thread-safe object would have all its methods synchronized. Synchronizing all the methods would also slow the application, however, because only one thread could work on the object at any given time. There are several situations when you want multiple threads read from the object but have only one thread modify the object. Let's say we are modeling a database table as shown in Listing 16.15.

Listing 16.15 Database Model (C#)
public class Table {

  public Row Read() {
    lock (this) {
    //Read
    }
  }
  public void Write(Row) {
    lock (this) {
    //Write
    }
  }
}

If we had 100 threads trying to access the Table object, only one thread could actually either read or write to the table at a time. What we want is a mechanism for multiple threads to be able to read from the table and only a single thread to be able to write to it. The C# ReaderWriterLock class can be used for classes that allow multiple reads and a single write; this means that the state of the object can be read by multiple threads as long as there is no thread trying to mutate the state of the object.

Listing 16.16 has been borrowed from the sample programs that ship with the .NET SDK. The program uses a thread pool to simulate 20 threads trying to read and write to a table. The odd-numbered threads read from the table, and the even-numbered threads write to the table. Note the use of AutoResetEvent to signal execution completion of the ThreadPool thread. A new class, Interlocked, is used to decrement the number of threads that have accessed the table object. We discuss the Interlocked class in Section 16.5.

Listing 16.16 ReaderWriter Lock Demonstration
using System;
using System.Threading;

public class Table {
  ReaderWriterLock rwl = new ReaderWriterLock();

  public void Read(int threadNum) {
    rwl.AcquireReaderLock(Timeout.Infinite);
    try {
      Console.WriteLine("Start Resource reading
                               (Thread = {0})", threadNum);
      Thread.Sleep(1000);
       Console.WriteLine("Stop Resource reading
                               (Thread = {0})", threadNum);
    }
    finally {
       rwl.ReleaseReaderLock();
    }
  }

  public void Write(Int32 threadNum) {
    rwl.AcquireWriterLock(Timeout.Infinite);
    try {
      Console.WriteLine("Start Resource
          writing (Thread = {0})", threadNum);
      Thread.Sleep(750);
      Console.WriteLine("Stop Resource writing (Thread =
{0})", threadNum);
    }
    finally {
       rwl.ReleaseWriterLock();
    }
  }
}

public class Test {

  static Int32 numThreads = 20;
  static AutoResetEvent done = new AutoResetEvent(false);
  static Table table;

  static void Main(string[] args) {
      table = new Table();
      for (Int32 i = 0; i < 20; i++) {
        ThreadPool.QueueUserWorkItem(
              new WaitCallback(UpdateResource), i);
      }
      done.WaitOne();
  }

  static void UpdateResource(Object state) {
    Int32 threadNum = (Int32) state;
    if ((threadNum % 2) ! = 0) table.Read(threadNum);
    else table.Write(threadNum);
    if (Interlocked.Decrement(ref numThreads)==0)
         done.Set();
  }
}

The output of Listing 16.16 is as follows:

Start Resource writing (Thread=0)
Stop Resource writing (Thread=0)
Start Resource reading (Thread=1)
Stop Resource reading (Thread=1)
Start Resource writing (Thread=2)
Stop Resource writing (Thread=2)
Start Resource reading (Thread=7)
Start Resource reading (Thread=3)
Start Resource reading (Thread=5)
Stop Resource reading (Thread=7)
Stop Resource reading (Thread=3)
Stop Resource reading (Thread=5)
Start Resource writing (Thread=4)
Stop Resource writing (Thread=4)
Start Resource reading (Thread=15)
Start Resource reading (Thread=9)
Start Resource reading (Thread=11)
Start Resource reading (Thread=13)
Stop Resource reading (Thread=15)
Stop Resource reading (Thread=9)
Stop Resource reading (Thread=11)
Stop Resource reading (Thread=13)
Start Resource writing (Thread=6)
Stop Resource writing (Thread=6)
Start Resource reading (Thread=17)
Start Resource reading (Thread=19)
Stop Resource reading (Thread=17)
Stop Resource reading (Thread=19)
Start Resource writing (Thread=8)
Stop Resource writing (Thread=8)
Start Resource writing (Thread=10)
Stop Resource writing (Thread=10)
Start Resource writing (Thread=12)
Stop Resource writing (Thread=12)
Start Resource writing (Thread=14)
Stop Resource writing (Thread=14)
Start Resource writing (Thread=16)
Stop Resource writing (Thread=16)
Start Resource writing (Thread=18)
Stop Resource writing (Thread=18)

The blocks that have contiguous reads (multiple threads reading at the same time) are shown in boldface. Note that multiple threads can read, but only one thread can write at a time.


16.5 Interlocked Operations

Many processors support certain operations that are guaranteed by the runtime not to be interrupted. These operations are useful when you're dealing with threads, because you can use them without using locking. In the CLR these operations are encapsulated in the Interlocked class. This class exposes the Increment(), Decrement(), Exchange(), and Compare Exchange() methods, which can be used on the long and int data types.

In Listing 16.16, the Interlocked.Decrement operation is used to decrement the number of threads accessed in a thread-safe manner. The runtime guarantees that the interlocked decrement operation will not be interrupted.

You should use interlocked operations to improve performance, and avoid the use of trivial locks for basic operations such as incrementing and decrementing int and long data types. For example, we could make Listing 16.13 instantly thread-safe by using the Interlocked increment and decrement operations in the add() and remove() methods.


16.6 Summary

Thread-related classes and delegates are defined in the System.Threading namespace. The four ways to create threads in C# are as follows:

  1. Manually creating threads. This approach gives the most flexibility because the creator of the thread can control the thread's attributes. This approach is also how Java programmers create threads. But because the onus of managing the life cycle of the thread is on the programmer, this approach of creating threads should be avoided if possible. Improperly managing the life of the thread could cannibalize system resources.

  2. Using the System.Threading.ThreadPool class. This is the recommended approach to creating threads because it exploits the system-specified ThreadPool class, which takes care of managing the life cycle of the created threads.

  3. Using the System.Threading.Timer class. This is the recommended approach for creating threads that execute a task periodically.

  4. Using the asynchronous callback mechanism. Although this approach does not directly create a thread, it simulates asynchronous behavior and provides a generic mechanism for information sharing between the caller thread and the execution thread.

In most cases, you can synchronize threads by using the lock keyword, which is similar to Java's synchronized keyword. To obtain finer control over the synchronization blocks, C# provides the Monitor class. C# provides ReaderWriterLocks, which are useful for a typical programming problem of multiple threads trying to read an object and only one thread trying to write to it. The C# Interlocked class provides operations that can be executed on the int and long data types. These operations cannot be interrupted; they are atomic and therefore preclude the need for specifying locks for trivial tasks.


Chapter 17. Using C# Properties, Indexers, and Attributes

In the past few chapters we have seen how similar C# is to Java in many aspects. The makers of C# have introduced quite a few new concepts in C# that are very critical to writing good C# code. A Java programmer might be tempted to write C# code using Java conventions, and that is fine, but you might carry over the inefficiencies of Java into the C# code. The constructs that we discuss in this chapter are specifically designed to address these inefficiencies. Three constructs—properties, indexers, and attributes—have no Java equivalents.


17.1 Properties

An object's attributes can be accessed directly (if they are declared public) or through get and set methods (accessors and mutators). These methods provide a single point of access to the attributes. You can change the implementation of these methods without affecting the code that calls them. C# provides the property construct, which makes the calling of these get and set methods transparent to the end user.

A property is typically used to model simple attributes. For example, you can set or get the color of a car (an object) using both properties and methods, as shown next. Using properties:

1) car.Color = "red";
2) string color = car.Color;

Using method calls:

3) car.SetColor ("red");
4) string color = car.GetColor();

The property syntax is more readable. In line 1, setting the Color property actually calls a setter inside the car object without the end user having to know about it. Similarly, in line 2 you call the getter implicitly merely by retrieving the property and assigning it to a variable.

Let's look at an example. Listing 17.1 shows a Student class with various properties defined.

Listing 17.1 A Student Class with Three Properties (C#)
using System;

public class Student {

  int size;
  int count;
  string name;
  string id;

  public int Count {
    get {
      return Int32.Parse("te4t");
    }
    set {
      count = value;
    }
  }
  public string ID {
    get {
      return id;
    }
    set {
      id = value;
    }
  }
  public string Name {
    get {
      return name;
    }
  }
  public int Size {
    get {
      if (size == 1) calcSize();
      return size;
    }
    set {
      size = value;
      calcCount();
    }
  }
  private void calcSize() {
    //Do something with the size here.
  }
  private void calcCount() {
    //Do something with the count here.
  }

  public static void Main(string[] args) {

    Student s = new Student();
    Console.WriteLine("Name ="+(s.Name == null));
    s.Count = 2;
    try {
      Console.WriteLine(s.Count);
    } catch (Exception e) {
      Console.WriteLine(e.Message);
    }
    s.ID = "Test";
  }
}

The output of Listing 17.1 is as follows:

Name =True
Input string was not in a correct format.

In Listing 17.1, the ID property is a simple property with both the get{} and set{} blocks defined. Note that in the set{} block, the term value is a C# keyword. The value keyword should be assigned to the attribute being wrapped by the property. The CLR is smart enough to resolve the type of the attribute to which the value keyword is assigned, so you don't have to do any type conversion in the set{} block. The get{} block merely returns the value of the id attribute. The ID property is a simple property that wraps the id attribute. External code can get or set the value of the attribute id by working on the ID property. Note that both the get and set locks have the same access modifier (public).

The Size property wraps access to the size attribute. The get{} and set{} blocks for this property involve some extra processing. This is perfectly legal. Remember that the get{} and set{} blocks are like getter and setter methods. Fortunately, the processing in the get{} and set{} blocks consists of dummy method calls. In reality, the processing can be anything from trivial in-memory operations to database access.

As you can see, the simplicity of setting and getting a property can wrap a lot of complex, behind-the-scenes operations that the user is totally unaware of. Although a property is meant to simplify syntax, a property that hides CPU- and memory-intensive operations can be wrongfully used in a manner that could affect performance. For example, in the following snippet, Balance and Interest are both properties of the account object, and both can access the database:

for (i =0; i < 100000; ++i) {
  sum += account.Balance + account.Interest;
}

Although the syntax looks simple, going to the database 100,000 times in a loop can result in poor performance.

The Name property wraps access to the name attribute. The Name property does not have a set{} block. This makes the property read-only. This is similar to a Java class having only an accessor for an attribute. When you print the Name property for a student, its value is null. However, unlike Java, the word "null" is not printed to the console; instead, a blank space is printed (not shown in the output).

The Count property wraps access to the count attribute. The Count property will throw a Parsing exception in the get{} block because it is trying to parse an alphanumeric text to an int. Unfortunately, the fact that there is an exception being thrown is not indicated anywhere in the signature of the property. Because C# does not have a throws clause, the user of the property has no way to know which exceptions must be accounted for in using that property.

Visual Basic programmers envisage properties as simple wrappers over attributes of objects, such as Color, Width, and Height. In fact, that was the motivation behind the C# property construct. However, the flexibility of overriding the get{} and set{} blocks with custom code can be abused. You should never put complex CPU- and memory-intensive code in the get{} and set{} blocks of a property. Properties should be thought of as simple wrappers to the object's attributes.

In Listing 17.1, all the get{} and set{} blocks had the same access modifier. This is where properties are less flexible than methods. You can change the access modifier of a setter or getter method, but a public property will lead to public get{} and set{} blocks.

Listing 17.2 shows that, like member functions, properties can also be declared using virtual, override, or abstract modifiers. These modifiers are placed on the property and affect both the getter and the setter. When a derived class declares a property with the same name as in the base class, it hides the entire property; it is not possible to hide only the getter or the setter. Properties can also be static. Static properties are just like member methods in that they cannot be declared with the virtual, abstract, or override modifier. Listing 17.2 shows an abstract property, Area, of the Graphic object, which is overridden by the Area property of the Circle class.

Listing 17.2 A Simple Abstract Property (C#)
using System;

public abstract class Graphic {

  public abstract double Area {
    get;
    set;
  }
}

class Circle : Graphic {
  double area = 1;
  double radius;
  public Circle(double rad) { radius = rad; }

  public override double Area {
    get {
      if (area == 1) {
        area = Math.PI * Math.Pow(radius,2);
      }
      return area;
    }
    set {
      area = value;
      radius = Math.Sqrt((area/Math.PI));
    }
  }

  public static void Main(string[] args) {
    Graphic g = new Circle(5.6);
    Console.WriteLine(g.Area);
  }
}

The output of Listing 17.2 is as follows:

98.5203456165759

In sum, here are the pros and cons of the C# property construct. Pros:

  • It has simplified syntax.

  • You do not have to be aware of the method signature to use it.

  • A property can throw an exception without a caller having to know about that exception.

Cons:

  • Because properties are deceptively simple, they are most likely to be used as regular variables in operations or in loops. Care should be taken that a property does not hide CPU- and memory-intensive operations in its get{} and set{} blocks.

  • A property forces the same access modifier on the get{} and set{} blocks.


17.2 Indexers

Arrays provide a convenient way to get and set a value of an element. Indexers allow a list of ordinary objects to be accessed as though they were arrays. Just as a property looks like a field but has methods to perform get and set operations, an indexer looks like an array but has methods to perform array indexing operations.

17.2.1 Indexers with an Integer Index

The class in Listing 17.3 shows an indexer with an integer index. The indexer is specified using this[..] syntax. The indexer has get and set blocks. If you remove the set block, the indexer becomes read-only. The return type is object, and again the keyword value appears in the set block. The index of the indexer is specified as the argument of this[..].

Listing 17.3 shows how any custom object can define an indexer and can be treated as an array.

Listing 17.3 An Indexer with an Integer as an Index (C#)
using System;
using System.Collections;

public class Rows {

  ArrayList list;
  public Rows(ArrayList param) {
    list = new ArrayList(param);
  }

  public object this[int index] {
    get
    {
      return list[index];
    }
    set
    {
      list[index] = value;
    }
  }
  public static void Main(string[] args) {
    ArrayList list = new ArrayList();
    list.Add(2);
    list.Add(245);
    list.Add(44);
    Rows rows = new Rows(list);
    for (int i =0; i < list.Count; ++i)
      Console.WriteLine(rows [i]);
  }
}

The output of Listing 17.3 is as follows:

2
245
44

In Listing 17.3 the indexer is specified by the following lines:

public object this[int index] {
  get
  {
    return list[index];
  }
  set
  {
    list[index] = value;
  }
}

It is an integer-based index because the index is declared as an int. Essentially, we know that an ArrayList defines an indexer that makes it possible to access it like an array. The Rows class wraps an ArrayList and also wraps the indexer functionality of the ArrayList. The Rows class merely delegates the indexer calls to the underlying ArrayList object.

17.2.2 Indexers with a String Index

Unlike arrays, an indexer index can be a string. Listing 17.4 shows an indexer with a string index. The string index is maintained as a Hashtable, and the two indices retrieve their data from different sources. The integerbased index gets data from an ArrayList, and the string-based index gets data from a Hashtable.

Listing 17.4 Indexer with String Index (C#)
using System;
using System.Collections;

public class RGBColorCodes {

  Hashtable table;
  public RGBColorCodes() {
    table = new Hashtable();
  }
  public void Add(object key, object val) {
    table.Add(key, val);
  }

  public object this[string key] {
    get {
      return table[key];
    }
    set {
      table[key] = value;
    }
  }
  public int Count {
    get {
      return table.Count;
    }
  }

  public static void Main(string[] args) {
    RGBColorCodes codes = new RGBColorCodes();
    codes.Add("maroon","800000");
    codes.Add("darkred","8B0000");
    codes.Add("deeppink","FF1493");
    codes.Add("mediumpurple","9370DB");
    codes.Add("springgreen","00FF7F");
    codes.Add("lavenderblush","FFF0F5");
    Console.WriteLine(codes["springgreen"]);
  }
}

Here is the output of Listing 17.4:

00FF7F

The RGBColorCodes class stores RGB color codes and their names in a Hashtable, which is indexed by the color name. You can obtain the color code of a particular color using the indexer notation. The indexer is defined in the listing using the following lines:

public object this[string key] {
  get {
    return table[key];
  }
  set {
    table[key] = value;
  }
}

Note that in both Listing 17.3 and Listing 17.4, the get{} and set{} block implementations are trivial; in reality, they could be more complex. Again, you should be careful in not hiding complex CPU- or memory-intensive operations inside these blocks. The indexer notation is most likely to be used in the context of a loop, and having CPU-intensive implementations in the get{} and set{} blocks of the indexer will slow the iteration.

17.2.3 Indexers with Multiple Indices

Indexers simplify the API. Consider a convoluted data structure such as a Hashtable of ArrayLists. Let's say this structure is used to store database rows so that the primary key is stored as the Hashtable key and the values of the columns are stored in the ArrayList. To access a column of this dataset based on the primary key or hash table key, you have the option of the following sample method calls (of course, you can get arcane in your method signatures if you want to):

Dataset.GetRow(int row).GetColumn(int col);
Dataset.GetData(int row, int column);

You would also have to provide the setter methods. You could replace the preceding mess using one indexer:

DataSet[int row, int column]

Chess and checkers players will immediately appreciate this indexer notation. In fact, a dataset is essentially a table, so why not make it accessible as a table structure? Listing 17.5 shows a DataSet object with multiple indexers. Notice that in the Main method, the client calls to the get and set objects in the DataSet have been simplified.

Listing 17.5 Indexer with Multiple Indices (C#)
using System;
using System.Collections;

public class DataSet {

  ArrayList list;
  Hashtable table;

  public DataSet() {
    table = new Hashtable();
    LoadData() {
  }

  private void LoadData(){
    table.Add(1, GenerateList(1));
    table.Add(2, GenerateList(2));
    table.Add(3, GenerateList(3));
    table.Add(4, GenerateList(4));
  }

  private ArrayList GenerateList(int j) {
    ArrayList list = new ArrayList();
    for (int i =0; i < j; ++i) {
      list.Add(i);
    }
    return list;
  }

  public object this[int row, int column] {
    get {
      return ((ArrayList) table[row])[column];
    }
    set {
      ((ArrayList) table[row])[column] = value;
    }
  }

  public void print() {
    foreach (object o in table.Keys) {
      ArrayList list = (ArrayList) table[o];
      Console.WriteLine("Key {0} has the following values ", o);
      for (int i =0 ; i < list.Count; ++i) {
        Console.WriteLine(list[i]);
      }
    }
  }

  public static void Main(string[] args) {
    DataSet ds = new DataSet();
    ds.print();
    ds[2,0] = 3;
    ds.print();
  }
}

The output of Listing 17.5 is as follows:

Key 4 has the following values
0
1
2
3
Key 3 has the following values
0
1
2
Key 2 has the following values
0
1
Key 1 has the following values
0
Key 4 has the following values
0
1
2
3
Key 3 has the following values
0
1
2
Key 2 has the following values
3
1
Key 1 has the following values
0

In Listing 17.5 the indexer is defined using the following lines:

public object this[int row, int column] {
    get {
      return ((ArrayList) table[row])[column];
     }
    set {
      ((ArrayList) table[row])[column] = value;
    }
  }

Indexers with multiple indices give you the freedom to represent the access to data in whatever form is most relevant to the problem domain being addressed. For example, a chess square could be referred to as chessboard [row, column], as opposed to chessboard.getRow().getColumn(). Such indexers can trim the code and make it more intuitive and readable.


17.3 Attributes

Modern programming languages often use declarative constructs to change the runtime behavior of the program. These constructs often exist in property files, .ini files, or configuration files. The Enterprise JavaBeans specification of Java uses declarative syntax to define the transactional behavior of the EJB. The standard policy file that ships with the JDK distribution contains security-related declarative constructs that dictate which Java classes get which permissions at runtime.

The C# language provides the System.Attribute class, which is used to model a declarative construct. Java does not have a first-class object for modeling declarative constructs. C# attributes can be used in the source code on specific code elements such as methods, classes, constructors, and assemblies. These are annotations that can be used to change or define the runtime behavior of the object. They have an API, and so you can programmatically access the attributes defined on an object. By judiciously defining the attributes, you can get very granular and specific with the runtime behavior.

By placing the attribute on a specific code element, you inherently define the scope at which the attribute will apply. Code elements to which attributes apply are often called targets and typically include the following: Assembly, Module, Class, Struct, Enum, Constructor, Method, Property, Field, Event, Interface, Parameter, ReturnValue, Delegate, All, and ClassMembers. You specify attributes by placing the name of the attribute and enclosing it in square brackets in front of the code element where it applies. For example, [DllImport] is a standard attribute that can be placed in front of a method:

[DllImport] public class WindowsFileReader{. . . }

Attributes can have parameters:

[DllImport("user32.dll", SetLastError=false, ExactSpelling=false)]

Attribute parameters can be of the following types:

  • Bool

  • Byte

  • Char

  • Double

  • Float

  • Int

  • Long

  • Short

  • String

  • Object

  • System.Type

  • A public enum

  • A one-dimensional array of any of these types

More than one attribute can be placed in front of a code element, and an attribute can be used multiple times on different code elements. These are called multiuse attributes. In certain situations, the target of an attribute (that is, the code element to which the attribute applies) appears to be ambiguous. For example, in the following method declaration, the SomeAttr attribute could apply to the method or to the method's return value:

[SomeAttr] int Method1( string s )

To resolve the ambiguity, C# has a set of default targets for each kind of declaration. You can override these default targets by explicitly specifying attribute targets:

[method: SomeAttr] int Method1( string s ) // applies to method
[return: SomeAttr] int Method1( string s ) // applies to return
                                                           value
[SomeAttr] int Method1( string s ) // default: applies to method

Table 17.1 lists all declarations in which attributes are allowed; for each declaration, the possible targets for attributes on the declaration are listed in the second column.

Attributes whose target is the assembly or the module are considered global attributes. The CLSCompliant attribute indicates whether all types defined in the assembly comply with the .NET Framework Common Language Specification:

[assembly: System.CLSCompliant(true)]

Table 17.1. Declaration List

Declaration

Possible Targets

Assembly

assembly

Module

module

Class

type

Struct

type

Interface

type

Enum

type

Delegate

type, return

Method

method, return

Parameter

param

Field

field

Property—indexer

property

Property—get accessor

method, return

Property—set accessor

method, param, return

Event—field

event, field, method

Event—property

event, property

Event—add

method, param

Event—remove

method, param

For the most part, we will be dealing with the predefined attributes in the .NET environment. However, you can define custom attributes. To define an attribute you extend the System.Attribute class. It's good to name your custom attribute class with the Attribute suffix after the class name so that it is easy to identify as an attribute class.

An attribute class is like a regular class except that there is a predefined attribute called [AttributeUsage] that your custom attribute must specify over its class. This defines the ways in which your custom attribute will be used. The [AttributeUsage] attribute is a single-use attribute. [AttributeUsage] is also an alias for System.AttributeUsageAttribute. The syntax of the [AttributeUsage] attribute is

[AttributeUsage(target,
AllowMultiple=allowmultiple,Inherited=inherited)]

where target specifies the language element on which the attribute can be placed. A true value for AllowMultiple means that the attribute is multiuse. A true value for Inherited means that the attribute is inherited by derived classes.

Listing 17.6 shows a custom Modified attribute that you can place on a class to indicate when the class was modified and by whom. A source-control program such as CVS or Visual SourceSafe easily keeps track of all this information. However, if you want to do this programmatically, you can do it in C#. The ClassModifiedAttribute takes one argument (modifiedby). The attribute can be used multiple times on the class, and also its sole target is a class. The attribute is then used on a custom class called Algorithm. Using reflection, you can get the custom attributes defined on a class and have the values of attributes printed. Listing 17.6 allows you to collect historical data about when the class was modified.

Listing 17.6 Custom Attributes (C#)
[View full width]
using System;

[AttributeUsage(AttributeTargets.Class, AllowMultiplerue)]
class ClassModifiedAttribute : Attribute {
  System.DateTime date;
  string modifiedBy;
  public ClassModifiedAttribute (string modifiedBy) {
    date = System.DateTime.Now;
    this.modifiedBy = modifiedBy;
  }
  public string ModifiedBy {
    get {
      return this.modifiedBy;
    }
  }
  public System.DateTime ModifiedDate {
    get {
      return this.date;
    }
  }
  public override string ToString() {
    return "The class was modified by "+ modifiedBy +" on "+
                                                             date;
  }
}
[ClassModified ("John")]
[ClassModified ("Damien")]
[ClassModified ("Jack")]
class Algorithm {
  public static void Main (string[] args) {
      Type type = typeof (Algorithm);
    foreach (ClassModifiedAttribute att in
    type.GetCustomAttributes(
              typeof(ClassModifiedAttribute),false)) {
      Console.WriteLine ("Class was modified by "+att.ModifiedBy + " on " +att.
  ModifiedDate);
    }
  }
}

The output of Listing 17.6 is as follows:

Class was modified by Damien on 11/2/2002 7:33:53 PM
Class was modified by Jack on 11/2/2002 7:33:53 PM
Class was modified by John on 11/2/2002 7:33:53 PM

Looking at the output you will quickly recognize a problem with this attribute. Because you are setting the date to System.DateTime.Now, every time you try to get the history, it gives you the current time when the program was run instead of the true date when the class was modified. To fix this, we change the attribute to take a string date, which the modifier of the class specifies. The modified code is shown in Listing 17.7.

Listing 17.7 Modified ClassModified Custom Attribute (C#)
using System;

[AttributeUsage(AttributeTargets.Class, AllowMultiplerue)]
class ClassModifiedAttribute : Attribute {
  string date;
  string modifiedBy;
  public ClassModifiedAttribute (string modifiedBy, string date ) {
    this.date = date;
    this.modifiedBy = modifiedBy;
  }
  public string ModifiedBy {
    get {
      return this.modifiedBy;
    }
  }
  public string ModifiedDate {
    get {
      return this.date;
    }
  }
  public override string ToString() {
    return "The class was modified by "+ modifiedBy +" on "+
                                                             date;
  }
}

[ClassModified ("John", "2/5/2002")]
[ClassModified ("Damien", "3/6/2002")]
[ClassModified ("Jack", "4/7/2002")]
class Algorithm {
  public static void Main (string[] args) {
    Type type = typeof (Algorithm);
    foreach (ClassModifiedAttribute att in
             type.GetCustomAttributes(
                  typeof(ClassModifiedAttribute),false)) {
      Console.WriteLine ("Class was modified by "+
                att.ModifiedBy + " on " +att.ModifiedDate);
    }
  }
}

The output of Listing 17.7 is as follows:

Class was modified by Damien on 3/6/2002
Class was modified by Jack on 4/7/2002
Class was modified by John on 2/5/2002

The class is now fixed to show the correct historical data.


17.4 Summary

C# has three programming constructs that do not have direct equivalents in the Java world: properties, indexers, and attributes.

Properties are a façade for an object's attributes or fields. Properties provide get{} and set{} blocks, which are like the accessor and mutator methods. The code in the get{} and set{} blocks is called by the runtime transparently whenever the user of the property sets or gets the value of the property. Properties have the pros and cons shown in Table 17.2.

You should use properties only for wrapping the fields of an object so that the get and set blocks do minimal or no processing in getting and setting the value of the property.

Indexers let you use array-like notation to access a collection of objects. Indexers can be integer- or string-based. A single object can define multiple indexers, and an indexer can be defined with multiple indices. Indexers have the same pros and cons as properties. They simplify the overall look and feel of the code, but if there is CPU- or memory-intensive code in the underlying get{} and set{} blocks, then it could negatively affect the performance of the application depending on how the indexer is used. It is recommended that indexers be used when the get and set blocks of the indexer do minimal or no processing.

Table 17.2. Pros and Cons of C# Properties

Pros

Cons

Simplify syntax. Properties can be get or set like ordinary programming variables, thereby simplifying the overall look and feel of the code.

Properties do not require the user of the property to know anything about the property signature because there is none.

Properties simplify syntax by hiding the calls to the code in the get{} and set{} blocks. Placing any CPU- or memory-intensive code in these blocks could result in poor performance of the resulting application depending on how the property is being used.

Properties do not throw any exceptions, and so the user does not know beforehand which exceptions to look out for when using a property.

Properties do not allow for different access modifiers on the get{} and set{} blocks.

Attributes provide a declarative way to dynamically change the behavior of code at runtime. The System.Attribute class also provides an API to programmatically access and change the attribute. Associated with every C# object there is metadata (information about the methods, fields, interfaces, types), and attributes allow you to extend that metadata.


Chapter 18. Delegates and Event Programming

This chapter explores one of the new constructs of C# that has no equivalent in the Java programming language. C#'s language API is peppered with uses of this construct. If you examine the threading and I/O APIs, you'll find that the entire asynchronous callback mechanism is based on this new construct, which is called delegates.

Another reason for the support of delegates in C# concerns function pointers, which have been a popular construct in C++ and C but are not supported in Java. Because Java treats everything as a class, the makers of Java did not find the need for a separate construct for function pointers. C#, however, was not meant to lure only Java programmers. C#'s delegates make C and C++ programmers feel at home. Delegates are essentially references to a function (instead of an object) that can be called. Just like a regular object reference, this reference to a function can be an instance reference, static, or a property.


18.1 Using Delegates

Listing 18.1 shows a simple delegate.

Listing 18.1 A Simple Delegate Example (C#)
1 using System;
2 public class Test {
3   public delegate float CalcIncome(float hourlyWages);
4   public static void Main(string[] args) {
      CalcIncome del = new
      CalcIncome(Person.CalcAnnualIncome);
      Console.WriteLine(del(26));
5   }
6   public class Person {
7   public float income;
8   public static float CalcMonthlyIncome(float hourlyWages) {
      return hourlyWages * 160;
9   }
10  public static float CalcAnnualIncome(float hourlyWages) {
        resturn hourlyWages * 160 * 12;
11  }
12}

The first step is to declare the delegate, which is done in line 3:

public delegate float CalcIncome(float hourlyWages);

This declares a public delegate called CalcIncome that takes a float as a parameter and returns a float as a result.

The entry point instantiates the delegate using the following line:

CalcIncome del = new CalcIncome(Person.CalcAnnualIncome);

Note that when you instantiate a delegate you must essentially pass the name of the function that matches the signature of the delegate—that is, the name of the function that will actually get called after the delegate is activated. A delegate is like a function pointer; in the preceding line we are simply getting a reference del to a function pointer pointing to the function Person.CalcAnnualIncome. This function can be any function as long as its signature matches that of the delegate (takes a float and returns a float). The Person.CalcAnnualIncome and Person.CalcMonthlyIncome methods are both defined in a separate Person class. Note that these two methods do not have to be static.

The following line invokes the delegate:

Console.WriteLine(del(26));

Essentially the runtime passes the argument 26 to the actual function wrapped by our delegate del, which is Person.CalcAnnualIncome, and then calls that method. This returns the value, which is then printed as 49920.

This is the gist of delegates.

At this point you may wonder how delegates handle exceptions thrown by the underlying method they are pointing to. Listing 18.2 answers that question.

Listing 18.2 Exception Handling in Delegates (C#)
using System;

public class Test {

  public delegate float CalcIncome(string hourlyWages);

  public static void Main(string[] args) {
    CalcIncome del = new CalcIncome(Person.CalcAnnualIncome);
    Console.WriteLine(del("hghg"));
  }
}

public class Person {

  public float income;
  public static float CalcMonthlyIncome(string hourlyWages) {
    return (float) Double.Parse(hourlyWages) * 160;
  }
  public static float CalcAnnualIncome(string hourlyWages) {
    return (float) Double.Parse(hourlyWages) * 160 * 12;
  }
}

In Listing 18.2 the delegate is modified to accept a string instead of a float as a parameter. Passing an invalid numeric string causes a System.FormatException, which is then thrown by the CLR.

Delegates can be packaged as properties or static members of a class. In the previous listings the delegate was an instance member of the Test class. Listing 18.3 shows delegates as static read-only members of the Test class.

Listing 18.3 Delegates as static Members (C#)
using System;

public class Test {

  public delegate float CalcIncome(float hourlyWages);

  public static readonly CalcIncome MonthlyIncome = new
                          CalcIncome(Person.CalcMonthlyIncome);

  public static readonly CalcIncome AnnualIncome = new
                           CalcIncome(Person.CalcAnnualIncome);

  public static void Main(string[] args) {

    try {
      Console.WriteLine(Test.MonthlyIncome(40));
      Console.WriteLine(Test.AnnualIncome(40));
    } catch(Exception e) {
      Console.WriteLine(e.StackTrace);
    }
  }
}

public class Person {

  public float income;

  public static float CalcMonthlyIncome(float hourlyWages) {
    return hourlyWages * 160;
  }
  public static float CalcAnnualIncome(float hourlyWages) {
    return hourlyWages * 160 * 12;
  }
}

In Listing 18.3, the delegates are created as two static members (MonthlyIncome and AnnualIncome), and these are invoked.


18.2 Multicasting

So far we have seen that delegates encapsulate a single function call. However, a delegate can also refer to multiple functions. Just as an object method call can encapsulate multiple method calls within it, a delegate can actually call multiple functions.

You can use the operators "+" and "-" to add and remove delegates. Whenever a delegate points to multiple functions, all the functions get called in the order in which they are added to the invocation list. The invocation list is a CLR-maintained list of functions of a delegate that need to get called.

Listing 18.4 shows a delegate pointing to multiple function calls.

Listing 18.4 Adding Delegates (C#)
using System;

public class Test {

  public delegate float CalcIncome(float hourlyWages);

  public static readonly CalcIncome MonthlyIncome = new
                          CalcIncome(Person.CalcMonthlyIncome);

  public static readonly CalcIncome AnnualIncome = new
                           CalcIncome(Person.CalcAnnualIncome);

  public static readonly CalcIncome AllIncomes = MonthlyIncome
                                                 + AnnualIncome;

  public static void Main(string[] args) {

    try {
      Console.WriteLine(Test.AllIncomes(40));
    } catch(Exception e) {
      Console.WriteLine(e.StackTrace);
    }
  }
}

public class Person {

  public float income;

  public static float CalcMonthlyIncome(float hourlyWages) {
    Console.WriteLine(" Monthly income is "+hourlyWages * 160);
    return hourlyWages * 160;
  }

  public static float CalcAnnualIncome(float hourlyWages) {
    Console.WriteLine(" Annual income is "+hourlyWages * 12*
                                                          160);
    return hourlyWages * 160 * 12;
  }
}

In Listing 18.4 the AllIncomes delegate is obtained by adding MonthlyIncome and AnnualIncome. As a result, when the AllIncomes delegate is invoked, both MonthlyIncome and AnnualIncome get called. If any of the methods throws an exception, then the subsequent methods do not get called. You can use the invocation list of the delegate to gain finer control of the list of functions that can be called, as shown in Listing 18.5.

Listing 18.5 Using the GetInvocationList (C#)
using System;

public class Test {

  public delegate float CalcIncome(float hourlyWages);

  public static readonly CalcIncome MonthlyIncome = new
                     CalcIncome(Person.CalcMonthlyIncome);

  public static readonly CalcIncome AnnualIncome = new
                       CalcIncome(Person.CalcAnnualIncome);

  public static readonly CalcIncome AllIncomes = MonthlyIncome
                                                 + AnnualIncome;

  public static void Main(string[] args) {
    try {
        foreach (CalcIncome del in
          AllIncomes.GetInvocationList())
        {
          del(40);
        }
      } catch(Exception e) {
      Console.WriteLine(e.StackTrace);
    }
  }
}

public class Person {

  public float income;
  public static float CalcMonthlyIncome(float hourlyWages) {
    Console.WriteLine(" Monthly income is " +hourlyWages *
                                                          160);
    return hourlyWages * 160;
  }
  public static float CalcAnnualIncome(float hourlyWages) {
    Console.WriteLine(" Annual income is "+hourlyWages * 12*
                                                          160);
    return hourlyWages * 160 * 12;
  }
}

The next section looks at how delegates can be used to do event-based programming in C#.


18.3 Event Programming

If you have done Win32 GUI-based or Java Swing/AWT-based programming, you are probably familiar with event programming. If you have dabbled in JavaBeans, you are probably familiar with the concept of listeners listening to changes to a JavaBeans property, and you know how these listeners can be added or deleted dynamically. Listeners in Java are essentially classes whose methods get called when a particular event happens. An event is also a class that is generated by yet another class, called the source. Because everything in Java is modeled as a class, it is sometimes confusing to differentiate a listener (the event receiver) from the source (the event generator).

Whether in Java or C#, an event is a message sent by an object to signal the occurrence of an action. The action can be triggered by a GUI control or by a daemon process. Every event has an event sender and one or more event receivers. The senders and receivers are totally unaware of each other.

In C#, delegates are used to tie the sender and the receiver. As explained earlier in this chapter, a delegate is a type-safe function pointer. When a source generates an event, an event object is instantiated and filled with properties called event arguments. The event object is then passed to a delegate, which gets invoked. An event listener registers or deregisters its interest in the event by adding or removing its own delegate to the event handler delegate. The .NET Framework provides an event keyword. An event, then, is essentially a delegate with a very specific signature. Events are usually declared public, although other access modifiers can be used.

To illustrate the basics of event programming, we will introduce the various components. Listing 18.6 shows an event source class. All events originate from a source.

Listing 18.6 The Event Source (C#)
using System;

public delegate void TornadoHandler(object sender, EventArgs
                                                             e);

public delegate void HurricaneHandler(object sender, EventArgs
                                                             e);

//The source of all events
abstract public class Weather {
  abstract protected void OnEvent(EventArgs args);
  public void Happen() {
    OnEvent(EventArgs.Empty);
  }
}
public class Tornado : Weather {
  public event TornadoHandler myEvent ;
  protected override void OnEvent(EventArgs args) {
    if (myEvent != null) {
        myEvent (this, args);
    }
  }
}
public class Hurricane : Weather {
  public event HurricaneHandler myEvent ;
  protected override void OnEvent(EventArgs args) {
    if (myEvent != null) {
        myEvent(this, args);
    }
  }
}

In this case the Weather class is the source of the event. It is an abstract class and has two subclasses (Tornado and Hurricane), each specifying its respective events. The Happen() method calls the OnEvent() method with an empty EventArgs argument. Because we are overriding the method in the subclass, the OnEvent method of the subclass gets called, and the respective event is generated. In this case there is no event object as such; instead, the communication is done through the EventArgs argument.

The listener of the events is shown in Listing 18.7, which is the BasicWeatherStation class.

Listing 18.7 The Event Listener (C#)
using System;
public class BasicWeatherStation {

  public BasicWeatherStation () {
  }
  public void AddWeather2Watch(Weather weather) {
    Tornado tor = weather as Tornado;
    if (tor != null) {
      tor.myEvent += new
           TornadoHandler(BasicTornadoHandler);
    }
    Hurricane cane = weather as Hurricane;
    if (cane != null) {
      cane.myEvent += new
             HurricaneHandler(BasicHurricaneHandler);
    }
  }

  public void BasicTornadoHandler(object sender, EventArgs e) {
    Console.WriteLine("Station has issued a tornado watch");
}
  public void BasicHurricaneHandler(object sender, EventArgs e)
{
    Console.WriteLine("Station has issued a hurricane warning");
  }

  public static void Main(string[] args) {

    Weather tornado = new Tornado();
    Weather hurricane = new Hurricane();
    BasicWeatherStation station = new
                 BasicWeatherStation();
    station.AddWeather2Watch(tornado);
    station.AddWeather2Watch(hurricane);
    tornado.Happen();
    hurricane.Happen();
  }
}

The weather station class is interested in knowing about all sorts of weather and therefore has weather event handlers for each type of weather. The BasicWeatherStation class registers its interest in knowing about the weather by adding its event handlers BasicTornadoHandler and BasicHurricaneHandler to the respective event delegates of the specific weathers, as shown in Listing 18.8.

Listing 18.8 Adding Event Handlers to Event Delegates (C#)
Tornado tor = weather as Tornado;
if (tor != null) {
  tor.myEvent += new
  TornadoHandler(BasicTornadoHandler);
}
Hurricane cane = weather as Hurricane;
if (cane != null) {
  cane.myEvent += new
  HurricaneHandler(BasicHurricaneHandler);
}

The output of Listing 18.8 is as follows:

Station has issued a tornado watch
Station has issued a hurricane warning

In this example the subclasses take care of declaring the specific events and must implement the code in the OnEvent method even though the code is similar for both subclasses.

It is possible for the weather class to maintain an internal map of delegates that will get called based on a specific key. Listing 18.9 shows a smarter Weather class, which is no longer abstract. The Weather class now implements an internal map of weather types to actual handlers. All tornadoes are handled by BasicWeatherStation.BasicTornadoHandler, and all hurricanes are handled by BasicWeatherStation.BasicHurricaneHandler.

Listing 18.9 The New Weather Class (C#)
using System;
using System.Collections;

public delegate void WeatherHandler(object sender, EventArgs e);
public class Weather {

  string key;
  static Hashtable weathers = new Hashtable();
  public Weather (string key, WeatherHandler wh) {
    this.key = key;
    weathers[key] = (WeatherHandler)weathers[key] + wh;
  }
  public event WeatherHandler weatherEvent
  {
    add
    {
      weathers[key]
        Delegate.Combine(( Delegate)weathers[key], value);
    }
    remove
    {
      weathers[key] = Delegate.Remove((
        Delegate)weathers[key], value);
    }
  }

  public void Happen() {
    OnEvent(EventArgs.Empty);
  }
  protected void OnEvent(EventArgs args) {
    WeatherHandler wh = (WeatherHandler) weathers[key];
    wh(this, args);
  }
}

Note that the compiler provides default implementation of the add and remove methods of the event keyword. In the class in Listing 18.9 we chose to implement our own versions of these methods so that the delegate instances are stored in a central hash table. This class also uses the static methods of the Delegate class as opposed to using the "+" and "-" operators. The Weather constructor now registers the passed-in WeatherHandler with the internal map. The Happen() method calls the OnEvent method, which, based on the key field of the Weather class, picks up the appropriate event delegate and invokes it.

Listing 18.10 shows the corresponding BasicWeatherStation class. The usage of the delegates is simplified. Most of the work is done by the Weather class. The BasicWeatherStation is used only for the callback methods.

Listing 18.10 The New BasicWeatherStation Class (C#)
using System;
public class BasicWeatherStation {

  public static void BasicTornadoHandler(object sender,
                                               EventArgs e) {
    Console.WriteLine("Station has issued a tornado watch");
  }
  public static void BasicHurricaneHandler(object sender,
                                           EventArgs e) {
    Console.WriteLine("Station has issued a hurricane
                                                     warning");
  }

  public static void Main(string[] args) {

    Weather tornado = new Weather ("Tornado", new
      WeatherHandler(BasicWeatherStation.BasicTornadoHandler));

    Weather hurricane = new Weather("Hurricane", new
    Weather(BasicWeatherStation.BasicHurricaneHandler));

    tornado.Happen();
    hurricane.Happen();
  }
}

So far we have registered the weather listeners based on the type of the weather—tornado or hurricane. This may not seem specific enough because multiple listeners could be interested in a particular tornado, and a listener could be interested in knowing about multiple tornadoes. Also, there is no way to inform all stations of a particular event. Listing 18.11 shows a modified version of the Weather class.

Listing 18.11 Modified Version of the Weather Class (C#)
using System;
using System.Collections;

public delegate void WeatherHandler(object sender,
                                       EventArgs e);

public class Weather {

  string key;
  object station;
  static Hashtable weathers = new Hashtable();
  private Weather(){}

  public static void AddHandlers(string key, IStation station) {
    Hashtable stationHandlers = (Hashtable) weathers[station];
    if (stationHandlers == null) {
      stationHandlers = new Hashtable();
      stationHandlers[key] = station.GetHandler(key);
      weathers[station] = stationHandlers;
    } else {

      stationHandlers[key] = (WeatherHandler)
      stationHandlers[key] + station.GetHandler(key);
    }
  }

  public static void Happen(string key, IStation station) {

    Hashtable stationHandlers = (Hashtable) weathers[station];
    if (stationHandlers == null) {
      stationHandlers = new Hashtable();
      stationHandlers[key] = station.GetHandler(key);
      weathers[station] = stationHandlers;
    }
    WeatherHandler wh = (WeatherHandler) stationHandlers[key];
    wh(new Weather(), EventArgs.Empty);
  }

  public static void Happen(string key) {
    ICollection keys = weathers.Keys;
    foreach (object o in keys) {
      Hashtable handlers = (Hashtable) weathers[o];
      WeatherHandler wh = (WeatherHandler) handlers[key];
      wh(new Weather(), EventArgs.Empty);
    }
    if (keys.Count == 0)
      throw new ArgumentException("No station is interested in
                                                   {0} ", key);
  }

}

To maintain the many-to-many relationship between the weather type and the weather station, the Weather class maintains a static Hashtable of Hashtables. The primary Hashtable is keyed off the station object. The secondary (or inner) Hashtable is keyed off the weather type, where the value is the handler delegate. This data structure allows us to query for the following:

  • All handlers of a given weather type regardless of the station

  • The handler of a specific station and a specific weather

  • All handlers of a specific station and a specific weather

Handlers of a particular station and weather type are registered using the AddHandlers method. This method takes care of storing the delegates to be called based on the station and the weather type. The Happen method is overloaded. The two-argument version notifies a specific station of a specific event. The single-argument version notifies all handlers of all stations for a given weather.

Listing 18.12 implements the listener classes. The two weather stations implement an IStation interface and indicate which delegate is to be used for which weather type. In the Main method we then register the stations (listeners) with the Weather. Depending on the Happen method called, the weather stations are notified.

Listing 18.12 Entry Point and Driver for Listing 18.11 (C#)
using System;

public class BasicWeatherStation : IStation {

  public void BasicTornadoHandler(object sender, EventArgs e) {
    Console.WriteLine("Station has issued a tornado watch");
  }

  public void BasicHurricaneHandler(object sender, EventArgs e) {
    Console.WriteLine("Station has issued a hurricane warning");
  }

  public WeatherHandler GetHandler(string key) {
    if ("Tornado".Equals(key)) {
      return new WeatherHandler(BasicTornadoHandler);
    } else if ("Hurricane".Equals(key)) {
      return new WeatherHandler(BasicHurricaneHandler);
    }
    throw new ArgumentException("Can handle only
                  Tornadoes and Hurricanes at this time");
  }

  public static void Main(string[] args) {
    BasicWeatherStation station1 = new BasicWeatherStation();
    AdvancedWeatherStation station2 = new AdvancedWeather
                                                   Station();

    Weather.AddHandlers("Tornado", station1);
    Weather.AddHandlers("Tornado", station2);
    Weather.AddHandlers("Hurricane", station1);
    Weather.AddHandlers("Hurricane", station2);

    //Response of all stations interested in the tornado
    Weather.Happen("Tornado");

    //Station2's response to the hurricane
    Weather.Happen("Hurricane", station2);
  }
}

public class AdvancedWeatherStation : IStation {

  public void AdvancedTornadoHandler(object sender,
                                       EventArgs e) {
    Console.WriteLine("Advanced Station
                   has issued a tornado watch");
  }

  public void AdvancedHurricaneHandler(object sender, EventArgs
                                                           e) {
    Console.WriteLine("Advanced Station
                  has issued a hurricane warning");
  }

  public WeatherHandler GetHandler(string key) {

    if ("Tornado".Equals(key)) {
      return new WeatherHandler(AdvancedTornadoHandler);
    } else if ("Hurricane".Equals(key)) {
      return new WeatherHandler(AdvancedHurricaneHandler);
    }
    throw new ArgumentException("Can handle only
                  Tornadoes and Hurricanes at this time");
  }
}
public interface IStation {
  WeatherHandler GetHandler(string key);
}

The output of the forecast in Listing 18.12 is as follows:

Advanced Station has issued a tornado watch
Station has issued a tornado watch
Advanced Station has issued a hurricane warning

From the prior discussions it should be clear that the event programming paradigm in C# is similar to JavaBeans programming. Essentially, listeners interested in the property change register themselves with the property. When the property is changed, each of the registered listeners is notified of the change. In Java, the event receiver (listener), event generator (source), and the event itself are all classes. In C#, the event itself is modeled as a delegate callback.


18.4 Summary

C# delegates can be thought of as type-safe function pointers. Function pointers are essentially references to actual functions. A delegate can also wrap a call to a series of functions that can be added or deleted dynamically. Because of the indirect nature of the function call, delegates are useful for simulating asynchronous method calls (such as calls to the System.Threading and System.IO namespaces). Delegates are also used in event-based programming in C#. Event programming in C# is similar to the JavaBeans property change listener paradigm. Because you can add or delete functions to a delegate, which in turn points to multiple functions, delegates can conveniently simulate the JavaBeans property change listener paradigm.


Part II: Advanced C# Topics

    Chapter 19. Accessing Databases

    Chapter 20. Processing XML

    Chapter 21. GUI Programming in C#

    Chapter 22. Reflection

    Chapter 23. Assemblies, Application Configuration, and Process Management

    Appendix Java and C# API Comparison


Chapter 19. Accessing Databases

An enterprise application usually has a relational database management system (RBDMS) as its backend data storage. Most enterprise applications involve a component that interacts with these RDBMS systems. Not surprisingly, both Java and C# come equipped with APIs that make it a breeze to access and work with databases. C# uses the ADO.NET API, which is not limited to accessing relational databases. In fact the API is not limited to accessing databases at all. In theory, you can use ADO.NET to access any kind of data store, including object-oriented database management systems (OODBMSs), data files, and e-mail messages. Java's JDBC 3.0 API is focused on accessing RDBMSs.

This chapter discusses the C# APIs only in the context of accessing an RDBMS. It is assumed that you are familiar with the concept of an RDBMS and with Structured Query Language (SQL) syntax.


19.1 Inside ADO.NET

ActiveX Data Objects (ADO) was designed to be a high-level interface to provide ease of access to a wide variety of data sources. ADO.NET was evolved from ADO and has become Microsoft's standard API of access to data sources. Traditional database access is a two-tier approach in which a live connection to the database is maintained. This approach does not scale well for multitier applications in which data must be transferred from one tier to another. By using an XML representation of the data obtained from the database or an XML stream, ADO.NET provides disconnected access to data, thereby scaling better in n-tier applications.

ADO.NET abstracts the concept of data storage into a data source. Examples of data sources are an RDBMS, an OODBMS, data files, and e-mail messages.

The C# API for accessing databases is packaged in four namespaces (see Table 19.1).

The two key concepts of ADO.NET are the .NET data provider and the DataSet object as shown in Figure 19.1. We look first at the .NET data provider, which is a set of classes for interfacing with a data source. A .NET data provider consists of all classes necessary to connect to the data source, issue commands to the data source, and retrieve and update the data from the data source. Similarly, the classes of a typical .NET data provider can be categorized as follows:

  • Connection. Class for connecting to the database

  • Command. An abstraction for specifying random SQL commands, with parameters to the underlying RDBMS source

  • DataReader. A lightweight class for read-only forward movement on the set of results obtained by running a query

  • DataAdapter. A set of classes used for selecting data or doing updates, inserts, and deletes to the data source

Figure 19.1. The ADO.NET Object Model

Table 19.1. C# Namespaces for Accessing Databases

Namespace

Description

System.Data

This namespace consists of most of the core classes for accessing the database.

System.Data.SqlTypes

This namespace provides classes for native data types within SQL Server. These classes provide a safe, fast alternative to other data types. Using the classes in this namespace helps prevent type conversion errors in situations where loss of precision could occur. Because other data types are converted to and from SqlTypes behind the scenes, explicitly creating and using objects within this namespace also results in faster code.

System.Data.Commons

This namespace contains classes shared by the .NET data providers.

System.Data.OleDb

This namespace contains all the classes for the OLE DB provider.

System.Data.SqlClient

This namespace contains all the classes for the SQL Server provider.

There are two .NET data providers. The SQL Server data provider consists of classes that are optimized specifically for accessing the Microsoft SQL Server database. These classes are in the System.Data.SqlClient namespace. The OLE DB data provider consists of classes for accessing any other database (Oracle, Sybase, and so on). Classes for this data provider can be found in the System.Data.OleDb namespace.

The other concept of ADO.NET is the DataSet object. A DataSet object is an in-memory cache of data retrieved from the database. The DataSet object tries to mimic the tabular structure of the underlying database. The DataSet object consists of a collection of objects that mimic RDBMS concepts such as tables, table relations, columns, rows, and constraints. A DataSet reads and writes data and schemata as XML documents. The data and schemata can be transported via HTTP and used by any application on any platform that is XML-enabled.


19.2 JDBC 2.0+

The Java Database Connectivity (JDBC) API contains two major sets of interfaces: the JDBC API for application writers, and the lower-level JDBC driver API for driver writers.

The central piece of database access in Java is the database driver, the software that translates the JDBC calls from the application into native calls that the database can understand. The JDBC driver is the equivalent of the .NET data provider, but unlike the .NET data provider, the JDBC driver is more of a black box that sits between the database and the application. Most application developers do not need to know the guts of the driver because they interact with it only through the JDBC API. Various RDBMS vendors (Oracle, IBM, Sybase, and so on) supply JDBC-compliant drivers for their individual databases.

An application typically uses one of four types of database drivers:

  • Type 1: JDBC-ODBC bridge plus ODBC driver. This combination provides JDBC access via ODBC drivers.

  • Type 2: A native-API, partly Java-technology-enabled driver. This type of driver converts JDBC calls into calls on the client API for Oracle, Sybase, Informix, DB2, or other DBMSs. This style of driver requires that some binary code be loaded on each client machine.

  • Type 3: Pure Java driver for database middleware. This style of driver translates JDBC calls into the middleware vendor's protocol, which is then translated to a DBMS protocol by a middleware server.

  • Type 4: Direct-to-database pure Java driver. This style of driver converts JDBC calls into the network protocol used directly by DBMSs, allowing a direct call from the client machine to the DBMS server and providing a practical solution for intranet access.

Modern-day drivers tend to be of type 4. The JDBC API provides everything you need to access a database. The API is not geared to favor any one particular database, and therefore you can use the same JDBC code for different databases.

ADO.NET supports an API for accessing most data sources; however, it also supplies a set of separate, performance-optimized classes for accessing the Microsoft SQL Server database. If your application is pretty much tied to Microsoft SQL Server and if code portability is not a concern for you, then you can leverage this vendor-centric API to boost application performance.


19.3 Getting Started with ADO.NET

Before you can understand the features of the C# API, you need to have a database server and a sample database to access. The focus of this chapter is to demonstrate the API and not the tools, so we do not describe the details of setting up a database, database schema, and tables. Instead, we use the standard Northwind database that ships with all Microsoft RDBMSs. We use Microsoft SQL Server as our database. To make the examples work, you need an instance of Microsoft SQL Server running locally on your machine.

We first explore how to accomplish these routine database tasks:

  • Establishing a connection to the database

  • Running a SELECT query and getting data

  • Running an INSERT, UPDATE , or DELETE query

  • Running a stored procedure

  • Processing transactions

  • Exploring database metadata

19.3.1 Establishing a Connection to the Database

Listing 19.1 shows how to open a connection to the database using the SqlClient provider and the OleDb provider. You use different connection classes depending on the provider you want to use for connecting. Fortunately, IDbConnection is a common interface implemented by these two classes, so we can use that interface to do the two most important connection-related tasks, which are basically opening and closing the connection.

Note that we specify the connection properties using the ConnectionString property of the connection object. The ConnectionString uses the following format:

"key1=valuel;key2=value2";

Keys are not case-sensitive. If a key name occurs more than once in the connection string, the value associated with the last occurrence is used. Note that the ConnectionString property is read/write for a closed connection, and read-only for an open connection.

Listing 19.1 Establishing a Connection to the Database with ADO.NET (C#)
using System;
using System.Data;
using System.Data.OleDb;
using System.Data.SqlClient;

class Test {

  public static void Main(string[] args) {

    IDbConnection conn = null;
    try {
      conn = new OleDbConnection();
      conn.ConnectionString = "Provider=SQLOLEDB;Initial
        Catalog=Northwind;Data Source=localhost;Integrated
                                           Security=SSPI;";
      conn.Open();
      Console.WriteLine("Connection "+conn);
      conn = new SqlConnection();
      conn.ConnectionString = "Initial Catalog=Northwind;Data
                 Source=localhost;Integrated Security=SSPI;";
      conn.Open();
      Console.WriteLine("Connection "+conn);
    } catch(Exception e) {
      Console.WriteLine(e.StackTrace);
    } finally {
      conn.Close();
    }
  }
}

The output of Listing 19.1 is as follows:

Connection System.Data.OleDb.OleDbConnection
Connection System.Data.SqlClient.SqlConnection

Note that the IDbConnection interface is conceptually equivalent to java.sql.Connection. In Java you must load the driver class and specify the database URL, user, and password to connect to the database:

Class.forName(driverClass).newInstance();
Connection conn = DriverManager.getConnection(url, user,
password);

Unlike Java, which does not have built-in support for connection pooling, ADO.NET provides automatic connection pooling.

19.3.2 Running a SELECT Query and Getting Data

Listing 19.2 shows a sample of how to use the OleDb provider and the SqlClient provider to read rows of data returned from a SELECT query.

Listing 19.2 Running a SELECT Query to Get Data (C#)
using System;
using System.Data;
using System.Data.OleDb;
using System.Data.SqlClient;

class Test {

  public static void Main(string[] args) {
    GetDataUsingOleDb();
    Console.WriteLine("————");
    GetDataUsingSqlClient();
  }
  private static void GetDataUsingOleDb() {
    OleDbConnection conn = null;
    OleDbDataReader reader = null;
    try {
      conn = GetOledbConnection();
      OleDbCommand command = conn.CreateCommand();
      command.CommandText = "SELECT * FROM CUSTOMERS WHERE
                                  UPPER(COUNTRY) = 'GERMANY'";
      reader = command.ExecuteReader();
      Console.Write("Customer ID \t\t Contact Name \n");
      while (reader.Read()) {
        Console.Write(reader.GetString(1));
        Console.Write("\t\t\t");
        Console.WriteLine(reader.GetString(3));
      }
    } finally {
      if (reader != null) reader.Close();
      if (conn != null) conn.Close();
    }
  }
  private static void GetDataUsingSqlClient() {
    SqlConnection conn = null;
    SqlDataReader reader = null;
    try {
      conn = GetSQLConnection();
      SqlCommand command = conn.CreateCommand();
      command.CommandText = "SELECT * FROM CUSTOMERS WHERE
                                  UPPER(COUNTRY) = 'GERMANY'";
      reader = command.ExecuteReader();
      Console.Write("Customer ID \t\t Company Name \n");
      while (reader.Read()) {
        Console.Write(reader.GetString(1));
        Console.Write("\t\t\t");
        Console.WriteLine(reader.GetString(2));
      }
    } finally {
       if (reader != null) reader.Close();
       if (conn != null) conn.Close();
    }
  }

  private static OleDbConnection GetOledbConnection() {
    OleDbConnection conn = new OleDbConnection();
    conn.ConnectionString = "provider=SQLOLEDB;Initial
      Catalog=Northwind;Data Source=localhost;Integrated
                                               Security=SSPI;";
    conn.Open();
    return conn;
  }
  private static SqlConnection GetSQLConnection() {
    SqlConnection conn = new SqlConnection();
    conn.ConnectionString = "Initial Catalog=Northwind;Data
                   Source=localhost;Integrated Security=SSPI;";
    conn.Open();
    return conn;
  }

}

The output of Listing 19.2 is shown next. Note that the output was formatted for display purposes here.

Customer ID                       Contact Name
Alfreds Futterkiste               Sales Representative
Blauer See Delikatessen           Sales Representative
Drachenblut Delikatessen          Order Administrator
Frankenversand                    Marketing Manager
Kîniglich Essen                   Sales Associate
Lehmanns Marktstand               Sales Representative
Morgenstern Gesundkost            Marketing Assistant
Ottilies Käseladen                Owner
QUICK-Stop                        Accounting Manager
Toms Spezialitäten                Marketing Manager
Die Wandernde Kuh                 Sales Representative
————
Customer ID                       Company Name
Alfreds Futterkiste               Maria Anders
Blauer See Delikatessen           Hanna Moos
Drachenblut Delikatessen          Sven Ottlieb
Frankenversand                    Peter Franken
Königlich Essen                   Philip Cramer
Lehmanns Marktstand               Renate Messner
Morgenstern Gesundkost            Alexander Feuer
Ottilies Käseladen                Henriette Pfalzheim
QUICK-Stop                        Horst Kloss
Toms Spezialitäten                Karin Josephs
Die Wandernde Kuh                 Rita Muller

The basic idea is to create a SqlCommand or an OleDbCommand that is conceptually equivalent to the java.sql.PreparedStatement class of Java. The command then executes the query using the ExecuteQuery method. The method returns a SqlDataReader or an OleDbDataReader, respectively. This is conceptually equivalent to the action of the java.sql.ResultSet class.

One similarity that might help Java developers is that the readers do not provide methods to retrieve the column data given the column name. This implies that you don't have to be familiar with the order of the columns that are being returned by the query. Note that the first column is obtained using the order 0 and not 1 (as in Java). The java.sql.ResultSet allows access by either column order or column name.

Listing 19.3 shows how to use the ExecuteScalar method of the command. This method is a convenience method that is useful when you know that you are fetching only one-column data from the query. Java does not provide such a convenience method, and therefore you must use code something like this:

ResultSet rs = stmt.executeQuery();
If (rs.next()) {
  int count = rs.getInt(1);
}

Listing 19.3 also shows how you can use interfaces instead of concrete classes to write provider-agnostic code.

Listing 19.3 Using the ExecuteScalar Function (C#)
using System;
using System.Data;
using System.Data.OleDb;
using System.Data.SqlClient;

class Test {

  public static void Main(string[] args) {
    GetData();
  }
  private static void GetData() {
    IDbConnection conn = null;
    try {
      conn = GetConnection();
      IDbCommand command = conn.CreateCommand();
      command.CommandText = "SELECT COUNT(*) FROM CUSTOMERS
                            WHERE UPPER(COUNTRY) = 'GERMANY'";
      Console.WriteLine("Number of customers from Germany =
                                    "+command.ExecuteScalar());
    } finally {
      if (conn != null) conn.Close();
    }
  }

  private static IDbConnection GetConnection()

    SqlConnection conn = new SqlConnection();
    conn.ConnectionString = "Initial Catalog=Northwind;Data
                   Source=localhost;Integrated Security=SSPI;";
    conn.Open();
    return conn;
  }

}

The output of Listing 19.3 is as follows:

Number of customers from Germany = 11

So far we have seen SELECT queries in which the query is complete in itself, meaning that no parameters are bound at runtime. Listing 19.4 shows how to bind parameters at runtime.

Listing 19.4 Binding Parameters at Runtime (C#)
using System;
using System.Data;
using System.Data.OleDb;
using System.Data.SqlClient;

class Test {

  public static void Main(string[] args) {
    GetData();
  }
  private static void GetData() {
    IDbConnection conn = null;
    try {
      conn = GetConnection();
      IDbCommand command = conn.CreateCommand();
      command.CommandText = "SELECT * FROM CUSTOMERS
                                       WHERE UPPER(Country)=?";
      command.Parameters.Add(new
                 OleDbParameter("Country", "USA"));
      IDataReader reader = command.ExecuteReader();
      while (reader.Read()) {
        Console.WriteLine(reader.GetString(1));
      }
    } finally {
      if (conn != null) conn.Close();
    }
  }

  private static IDbConnection GetConnection() {
    OleDbConnection conn = new OleDbConnection();
    conn.ConnectionString = "Provider=SQLOLEDB;Initial
      Catalog=Northwind;Data Source=localhost;Integrated
                                               Security=SSPI;";
    conn.Open();
    return conn;
  }

}

The output of Listing 19.4 is as follows:

Great Lakes Food Market
Hungry Coyote Import Store
Lazy K Kountry Store
Let's Stop N Shop
Lonesome Pine Restaurant
Old World Delicatessen
Rattlesnake Canyon Grocery
Save-a-lot Markets
Split Rail Beer & Ale
The Big Cheese
The Cracker Box
Trail's Head Gourmet Provisioners
White Clover Markets

You bind the parameters by instantiating either an OleDbParameter or a SQLParameter and then simply adding it to the collection of the command object. The order in which the parameters are added is the same order in which they appear in the query.

19.3.3 Running an INSERT, UPDATE, or DELETE Query

Listing 19.5 shows an example that runs an INSERT, an UPDATE, and a DELETE query.

Listing 19.5 Doing Insert, Update, and Delete (C#)
using System;
using System.Data;
using System.Data.OleDb;
using System.Data.SqlClient;

class Test {

  public static void Main(string[] args) {
    InsertData();
    UpdateData();
    DeleteData();
  }
  private static void InsertData() {
    IDbConnection conn = null;
    try {
      conn = GetConnection();
      IDbCommand command = conn.CreateCommand();
      command.CommandText = "INSERT INTO CUSTOMERS (CUSTOMERID,
                     COMPANYNAME, CONTACTNAME) VALUES (?,?,?)";
      command.Prepare();
      command.Parameters.Add(new OleDbParameter("CustomerID",
                                                     "ACME3"));
      command.Parameters.Add(new OleDbParameter("CompanyName",
                                           "Acme Object Inc"));
      command.Parameters.Add(new OleDbParameter("ContactName",
                                                "Mr. Object"));
      command.ExecuteNonQuery();
    } finally {
       if (conn != null) conn.Close();
    }
  }
  private static void UpdateData() {
    IDbConnection conn = null;
    try {
      conn = GetConnection();
      IDbCommand command = conn.CreateCommand();
      command.CommandText = "UPDATE CUSTOMERS SET
                  COMPANYNAME = ? WHERE CUSTOMERID = ?";
      command.Prepare();
      command.Parameters.Add(new OleDbParameter("CompanyName",
                                              "Acme Object "));
      command.Parameters.Add(new OleDbParameter("CustomerID",
                                                     "ACME3"));
      command.ExecuteNonQuery();
    } finally {
       if (conn != null) conn.Close();
    }
  }
  private static void DeleteData() {
    IDbConnection conn = null;
    try {
      conn = GetConnection();
      IDbCommand command = conn.CreateCommand();
      command.CommandText = "DELETE FROM CUSTOMERS
                                         WHERE CUSTOMERID = ?";
      command.Prepare();
      command.Parameters.Add(new
        OleDbParameter("CustomerID", "ACME3"));
      command.ExecuteNonQuery();
    } finally {
       if (conn != null) conn.Close();
    }
  }

  private static IDbConnection GetConnection() {
    OleDbConnection conn = new OleDbConnection();
    conn.ConnectionString = "Provider=SQLOLEDB;Initial
                  Catalog=Northwind;Data Source=localhost;
                                     Integrated Security=SSPI;";
    conn.Open();
    return conn;
  }

}

The output of Listing 19.5 must be verified by querying the database. It effectively inserts a row, updates it, and then deletes it. The following line returns an int indicating the number of rows that were affected by the insert, update, or delete query.

command.ExecuteNonQuery();

Note that the use of the Prepare() method on the IDbCommand compiles the statement for future execution. This may not be a concern in this program, but in an application that executes a SQL query several times, you can improve perfor mance by running the compiled version of the command object.

19.3.4 Running a Stored Procedure

Listing 19.6 shows how to execute a stored procedure that returns a RecordSet. This is similar to JDBC's stored procedure that returns a ResultSet. Note that by default the parameter set for the stored procedure is an in parameter.

Listing 19.6 Running a Stored Procedure (C#)
using System;
using System.Data;
using System.Data.OleDb;
using System.Data.SqlClient;

class Test {

  public static void Main(string[] args) {
    StoredProc();
  }
  private static void StoredProc() {
    IDbConnection conn = null;
    try {
      conn = GetConnection();
      IDbCommand command = conn.CreateCommand();
      command.CommandText = "CustOrdersDetail";
      command.CommandType = CommandType.StoredProcedure;
      command.Parameters.Add(new SqlParameter("@OrderID",
                                               SqlDbType.Int));
      SqlParameter param = command.Parameters[0] as SqlParameter;
      param.Value = 10248;
      IDataReader reader = command.ExecuteReader();
      while(reader.Read()) {
        Console.WriteLine(reader.GetValue(0));
      }
    } finally {
       if (conn != null) conn.Close();
    }
  }

  private static IDbConnection GetConnection() {
    SqlConnection conn = new SqlConnection ();
    conn.ConnectionString = "Initial Catalog=Northwind;Data
                   Source=localhost;Integrated Security=SSPI;";
    conn.Open();
    return conn;
  }

}

The output of Listing 19.6 is as follows:

Queso Cabrales
Singaporean Hokkien Fried Mee
Mozzarella di Giovanni

19.3.5 Processing Transactions

In JDBC, you can simulate local database-level transactions using the following code:

boolean originalAutoCommit = conn.getAutoCommit();
try {
conn.setAutoCommit(false);
conn.commit();
} catch(Exception e) {
conn.rollback();
} finally {
conn.setAutoCommit(originalAutoCommit);
}

You set transaction boundaries by turning off the auto-commit feature of the connection and then putting it back to its original state. Upon exception, the rollback() method is called, and that rolls back the changes made in the try block.

As is evident, there is no first-class Transaction object in JDBC. C#, however, has a Transaction object and an interface. Listing 19.7 shows a C# transaction.

Listing 19.7 Transactions in ADO.NET (C#)
using System;
using System.Data;
using System.Data.OleDb;
using System.Data.SqlClient;
class Test {

  public static void Main(string[] args) {
    Transaction();
  }
  private static void Transaction() {
    SqlConnection conn = null;
    SqlTransaction trans = null;
    try {
      conn = GetConnection();
      trans = conn.BeginTransaction();
      SqlCommand command = new SqlCommand();
      command.Connection = conn;
      command.Transaction = trans;
      command.CommandText = "UPDATE CATEGORIES
        SET CategoryName = 'Drink' WHERE CategoryID = 1";
      command.ExecuteNonQuery();
      trans.Save("Savepoint1");
      command.CommandText = "UPDATE CATEGORIES
        SET CategoryName = 'Spices' WHERE CategoryID = 2";
      command.ExecuteNonQuery();
      trans.Commit();
    } catch(Exception e) {
      trans.Rollback("Savepont1");
    } finally {
       if (conn != null) conn.Close();
    }
  }

  private static SqlConnection GetConnection() {
    SqlConnection conn = new SqlConnection ();
    conn.ConnectionString = "Initial Catalog=Northwind;Data
                   Source=localhost;Integrated Security=SSPI;";
    conn.Open();
    return conn;
  }

}

C# supports six transaction isolation levels (see Table 19.2). The default isolation level is ReadCommitted.

The Transaction classes also specify savepoints, a feature that is available only in JDBC 3.0. Savepoints offer a mechanism to roll back portions of transactions. You create a savepoint using the Save method and then later call the Rollback method to roll back to the savepoint instead of rolling back to the start of the transaction. Savepoints are useful when errors are unlikely to occur. Using a savepoint to roll back part of a transaction in the case of an infrequent error can be more efficient than having each transaction test to see whether an update is valid before making the update. Updates and rollbacks are expensive operations, so savepoints are effective only if the probability of encountering an error is low and the cost of checking the validity of an update beforehand is relatively high.

19.3.6 Exploring Database Metadata

Metadata is a term that refers to "the data about the data," and hence database metadata is basically information stored in the database about the database. The JDBC API has the java.sql.DatabaseMetaData and java.sql.ResultSetMetaData interfaces, which support discovery about the components of the database and the result set, respectively. Metadata is usually useful for developers who are writing tools for such a database explorer. In C#, the metadata information is spread throughout the API objects. The equivalent of result set metadata will become apparent when we discuss DataSets, next.

Table 19.2. ADO.NET Transaction Isolation Levels

Member Name

Descriptionxxx

Chaos

The pending changes from more highly isolated transactions cannot be overwritten.

ReadCommitted

Dirty reads are prevented; nonrepeatable reads and phantom reads can occur. This level prohibits a transaction only from reading a row that contains uncommitted changes.

ReadUnCommitted

Dirty reads, nonrepeatable reads, and phantom reads can occur. This level allows a row changed by one transaction to be read by another transaction before any changes in that row have been committed (a dirty read). If any of the changes are rolled back, the second transaction will have retrieved an invalid row.

RepeatableRead

Dirty reads and nonrepeatable reads are prevented; phantom reads can occur. This level prohibits a transaction from reading a row that contains uncommitted changes, and it also prohibits the situation in which one transaction reads a row, a second transaction alters the row, and the first transaction rereads the row, getting different values the second time (a nonrepeatable read).

Serializable

Dirty reads, nonrepeatable reads, and phantom reads are prevented. This level includes the prohibitions in RepeatableRead and further prohibits the situation in which one transaction reads all rows that satisfy a WHERE condition, a second transaction inserts a row that satisfies that WHERE condition, and the first transaction rereads for the same condition, retrieving the additional phantom row in the second read.

Unspecified

A different isolation level than the one specified is being used, but the level cannot be determined.


19.4 DataSets

DataSets provide a disconnected in-memory representation of a structure that closely mimics the underlying RDBMS tables. A DataSet object can be used to load data and save data to an RDBMS, and it's even powerful enough to programmatically generate an entire database complete with table, view, and constraint definitions. DataSet is conceptually similar to JDBC's RowSet and CachedRowSet classes.

19.4.1 Loading Data into a DataSet

Listing 19.8 shows how to load data into a DataSet object.

Listing 19.8 Loading Data into a DataSet (C#)
using System;
using System.Data;
using System.Data.OleDb;
using System.Data.SqlClient;
class Test {

  public static void Main(string[] args) {
    ReadDataSet();
  }

  private static void ReadDataSet() {
    SqlConnection conn = GetConnection();
    DataSet ds = new DataSet("Test");
    try {
      Fill(conn, ds, "Products", "SELECT PRODUCTID,
                                  PRODUCTNAME FROM PRODUCTS ");
      Fill(conn, ds, "Customers", "SELECT CUSTOMERID,
                                     CONTACTNAME FROM CUSTOMERS
                                WHERE UPPER(COUNTRY) ='USA'" );
    } finally {
      conn.Close();
    }
    Browse(ds);
  }

  private static void Fill(SqlConnection conn, DataSet ds,
                                 String tableName, String sql) {
    SqlDataAdapter adapter = new SqlDataAdapter ();
    adapter.TableMappings.Add("Table", tableName);
    SqlCommand command = new SqlCommand(sql, conn);
    adapter.SelectCommand = command;
    adapter.Fill(ds);
  }
  private static void Browse(DataSet ds) {
    foreach (DataTable table in ds.Tables) {
      BrowseTable(table);
    }
  }

  private static void BrowseTable(DataTable table) {
    Console.WriteLine("Table name "+table.TableName+"\n");
    Console.Write("Column Name,Column Type,Ordinal\n");
    foreach (DataColumn column in table.Columns) {
      Console.Write(column.ColumnName);
      Console.Write(",");
      Console.Write(column.DataType.FullName);
      Console.Write(",");
      Console.Write(column.Ordinal);
      Console.Write("\n");
    }
  }

  private static SqlConnection GetConnection() {
    SqlConnection conn = new SqlConnection ();
    conn.ConnectionString = "Initial Catalog=Northwind;Data
                   Source=localhost;Integrated Security=SSPI;";
    conn.Open();
    return conn;
  }

}

The output of Listing 19.8 is as follows:

Table name Products
Column Name,Column Type,Ordinal
PRODUCTID,System.Int32,0
PRODUCTNAME,System.String,1

Table name Customers
Column Name,Column Type,Ordinal
CUSTOMERID,System.String,0
CONTACTNAME,System.String,1

The program starts by creating a DataSet:

DataSet ds = new DataSet("Test");

The program then signals to fill the DataSet with the results obtained by running two SELECT queries:

Fill(conn, ds, "Products", "SELECT PRODUCTID, PRODUCTNAME FROM
PRODUCTS ");
Fill(conn, ds, "Customers", "SELECT CUSTOMERID, CONTACTNAME
                 FROM CUSTOMERS WHERE UPPER(COUNTRY) ='USA'" );

To populate the DataSet we need to use the appropriate adapter object, SqlAdapter:

SqlDataAdapter adapter = new SqlDataAdapter ();
adapter.TableMappings.Add("Table", tableName);
SqlCommand command = new SqlCommand(sql, conn);
adapter.SelectCommand = command;
adapter.Fill(ds);

The last line fills the dataset with the contents of the query:

adapter.Fill(ds);

Next, we browse the contents of the DataSet. We browse through the tables and the columns. Note that the returned columns contain only the columns that were returned by the queries and not all the columns of that table.

In our example we close the connection in the finally block just before browsing the DataSet. Because a DataSet is a disconnected in-memory representation of whatever data we obtained, the connection does not have to be open for us to browse through the DataSet.

19.4.2 Saving Data to the RDBMS Using a DataSet

Listing 19.9 shows how to insert, update, and delete rows using a DataSet.

Listing 19.9 Saving Data Using a DataSet (C#)
using System;
using System.Data;
using System.Data.OleDb;
using System.Data.SqlClient;
class Test {

  public static void Main(string[] args) {
    SqlConnection conn = GetConnection();
    try {

      SqlDataAdapter adapter = new SqlDataAdapter("
                               SELECT * FROM CUSTOMERS", conn);
      DataSet ds = new DataSet();
      //Set the MissingSchemaAction property to AddWithKey
                                 //because
                                  //Fill will not cause primary
      //key & unique key information to be retrieved unless
                                      //AddWithKey is specified.
      adapter.MissingSchemaAction =
                    MissingSchemaAction.AddWithKey;

      adapter.Fill(ds, "Customers");

      //Insert a row
      DataRow row = ds.Tables["Customers"].NewRow();
      row["CustomerId"] = "2ldID";
      row["ContactName"] = "2ld name";
      row["CompanyName"] = "2ld company";
      ds.Tables["Customers"].Rows.Add(row);

      //Find a row with primary key ALFKI and
        //update its ContactName
      row = ds.Tables["Customers"].Rows.Find("ALFKI");
      row["ContactName"] = "Updated name";

      SqlCommandBuilder builder = new
      SqlCommandBuilder(adapter);
      adapter.Update(ds, "Customers");

      //Delete a row from the order detail
      //The row has OrderId of 1 and Product Id = 06897
      adapter = new SqlDataAdapter("SELECT * FROM
                                   EmployeeTerritories", conn);
      adapter.MissingSchemaAction = MissingSchemaAction.Add
                                                       WithKey;
      adapter.Fill(ds, "EmployeeTerritories");

      row = ds.Tables["EmployeeTerritories"].Rows.Find(new
                                         object[] {1, 06897} );
      row.Delete();

      adapter.Update(ds, "EmployeeTerritories");

    } finally {
       conn.Close();
    }

  }

  private static SqlConnection GetConnection() {
    SqlConnection conn = new SqlConnection ();
    conn.ConnectionString = "Initial Catalog=Northwind;Data
                   Source=localhost;Integrated Security=SSPI;";
    conn.Open();
    return conn;
  }

}

Listing 19.9 updates and inserts rows in the Customers table and deletes a row from the EmployeeTerritories table. The program starts by creating a SqlDataAdapter and a DataSet. The DataSet is then filled with the Customers data:

SqlDataAdapter adapter = new SqlDataAdapter("SELECT * FROM
                                              CUSTOMERS", conn);
DataSet ds = new DataSet();
adapter.MissingSchemaAction =
MissingSchemaAction.AddWithKey;
adapter.Fill(ds, "Customers");

Note that we must specify the following because without that, the Fill method will not retrieve primary key and unique key information about the table:

adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;

Inserting a row in the dataset is trivial:

DataRow row = ds.Tables["Customers"].NewRow();
row["CustomerId"] = "2ldID";
row["ContactName"] = "2ld name";
row["CompanyName"] = "2ld company";
ds.Tables["Customers"].Rows.Add(row);

Here, we create a row, fill out its relevant information, and add it to the row collection of the Customers table. To update a row of the table we use the following:

row = ds.Tables["Customers"].Rows.Find("ALFKI");
row["ContactName"] = "Updated name";

The first line finds the row whose primary key is ALFKI. We modify the ContactName column of the row by changing its value to "UPdated name".

The DataSet is now ready to be persisted to the underlying tables. The following two lines take the DataSet changes and persist them to the database:

SqlCommandBuilder builder = new SqlCommandBuilder(adapter);
adapter.Update(ds, "Customers");

SqlCommandBuilder is a utility for generating trivial SQL statements automatically from a DataSet.

In JDBC, java.sql.DatabaseMetaData and java.sql.ResultSetMetaData give a read-only view of the details of the database structure and the table structure, respectively. The DataSet object has methods to browse a database or table structure and also to create new databases complete with tables, views, constraints, and relationships between the tables. We leave it to you to explore these methods of the DataSet object that help define the database metadata.

The .NET DataSet is a flexible class that can be used for using Data Modification Language (DML) and Data Definition Language (DDL) on the RDBMS. In a large code base, it becomes tedious to repeat code for doing DML on objects in different places. What is needed is an abstraction that is more intuitive to the object-oriented programmer. Listings 19.10 and 19.11 show how to encapsulate DataSet manipulation in an abstract object so that the logic can be reused for any custom object. The end user simply builds the custom object, and loading and saving of the object become automatic.

First, Listing 19.10 shows the abstract SqlPersistentObject.

Listing 19.10 An Abstract Class for Encapsulating DataSet Manipulation (C#)
namespace DietPersistentFrameWork {

  using System;
  using System.Collections;
  using System.Data;
  using System.Data.SqlClient;

  public abstract class SqlPersistentObject {

    public enum STATE {NEW, LOAD, DELETE, DELETED};

    STATE state;

    SqlDataAdapter adapter;
    DataSet ds;
    object pk;
    Hashtable columns = new Hashtable();

    public SqlPersistentObject() {
      state = STATE.NEW;
    }

    public void save(SqlConnection conn) {
      if (state == STATE.NEW) {
        Insert(conn);
      } else if (state == STATE.LOAD) {
        Update(conn);
  } else if (state == STATE.DELETE) {
         Delete(conn);
      }
    }

    public object GetField(string columnName) {
      if (state != STATE.NEW) {
        DataRow row =
        ds.Tables[GetTableName()].Rows.Find((string)pk);
        return row[columnName];
      }else {
        return columns[columnName];
      }
    }

    public void markDelete() {
      if (state == STATE.LOAD) {
        state = STATE.DELETE;
      }
    }

    //Sets the value of the field of the object
    public void SetField(string columnName, object val) {
      if (state != STATE.NEW ) {
        DataRow row =
        ds.Tables[GetTableName()].Rows.Find((string)pk);
        row[columnName] = val;
      } else {
        columns.Remove(columnName);
        columns.Add(columnName, val);
      }
    }

    //Delete the object from the DB
    protected virtual void Delete(SqlConnection conn) {
      object pk = GetField(GetPrimaryKey());

      try {
        string query = "DELETE * FROM "+GetTableName() +"
          WHERE
          "+GetPrimaryKey()+" = '"+pk+"'";
        adapter.DeleteCommand = new SqlCommand(query, conn);
        DataRow row =
          ds.Tables[GetTableName()].Rows.Find((string)pk);
        row.Delete();
        adapter.Update(ds, GetTableName());

        adapter = null;
        ds = null;
        state = STATE.DELETED;

        } catch (Exception e) {
        Console.WriteLine(e.StackTrace);
      }
    }

    public static void Load(SqlConnection conn,
                          SqlPersistentObject obj, string pk) {

      try {
        string query = "SELECT * FROM "+obj.GetTableName() +"
                   WHERE "+obj.GetPrimaryKey()+" = '"+pk+"'";
        obj.adapter = new SqlDataAdapter (query, conn);
        obj.ds = new DataSet(obj.GetTableName());
        obj.adapter.MissingSchemaAction =
          MissingSchemaAction.AddWithKey;
        obj.adapter.Fill(obj.ds, obj.GetTableName());
        obj.state = STATE.LOAD;
        obj.pk = pk;
      } catch (Exception e) {
        Console.WriteLine(e.StackTrace);
      }
    }

    //Update the object
    protected virtual void Update(SqlConnection conn) {

      try {
        if (ds != null && adapter != null) {
          SqlCommandBuilder builder = new
                                   SqlCommandBuilder(adapter);
          adapter.Update(ds, GetTableName());
        }
      } catch (Exception e) {
        Console.WriteLine(e.StackTrace);
      }
    }

    protected void Insert(SqlConnection conn) {

      try {

        string query = "SELECT * FROM "+GetTableName();
        adapter = new SqlDataAdapter (query, conn);
        ds = new DataSet();

        adapter.MissingSchemaAction =
          MissingSchemaAction.AddWithKey;
        adapter.Fill(ds, GetTableName());
        DataRow row = ds.Tables[GetTableName()].NewRow();
        foreach (object key in columns.Keys) {
          row[key.ToString()] = columns[key];
        }

        ds.Tables[GetTableName()].Rows.Add(row);

        SqlCommandBuilder builder = new
          SqlCommandBuilder(adapter);
        adapter.Update(ds, GetTableName());

        state = STATE.LOAD;
      } catch (Exception e) {
    ds = null;
    adapter = null;
        Console.WriteLine(e.ToString());
      }
    }

    //Abstract methods
    public abstract string GetTableName();
    public abstract string GetPrimaryKey();
  }

}

The key assumption behind the PersistentObject class is that it maps to a single table in the RDBMS. When objects are mapped to an RDBMS, it is customary to assume that a single row of the table maps to a single instance of the object and that the primary key value of that row becomes the object ID of that persistent object. The object ID serves to identify the object (say, in a cache of objects) just as the primary key helps identify a row in a table. For simplicity we assume that the primary key or object ID of the object is a simple single-column entity.

The PersistentObject class leverages the information stored in the DataSet and the SqlDataAdapter objects and gives an object-oriented API for manipulating them.

We start by defining the states of the object:

public enum STATE {NEW, LOAD, DELETE, DELETED};

When the object is first constructed, its state is NEW. The object is in the LOAD state when it is loaded from the database. The object is in the DELETE state when it is marked for deletion, and it is in the DELETED state when it is deleted. The save method of the object drives the main flow of the object:

public void save(SqlConnection conn) {
if (state == STATE.NEW) {
  Insert(conn);
} else if (state == STATE.LOAD) {
  Update(conn);
  } else if (state == STATE.DELETE) {
    Delete(conn);
  }
}

This logic is fairly intuitive. Any DML executed on an object in the NEW state is interpreted as an insert; on an object in the LOAD state, it is interpreted as an update. The Insert and Update methods set the appropriate state of the object upon their successful completion. The Update method merely updates the changes made to the DataSet via the SetField method:

protected virtual void Update(SqlConnection conn) {

try {
  if (ds != null && adapter != null) {
  SqlCommandBuilder builder = new SqlCommandBuilder(adapter);
  adapter.Update(ds, GetTableName());
  }
  }catch (Exception e) {
    Console.WriteLine(e.StackTrace);
  }
}

The SetField method is interesting because if the object is in the NEW state, SetField merely adds the columnNames and values to the Hashtable. Note that in C#, for a Hashtable, having the key and value will not overwrite the old value (if any exists) for that key. To simulate this Java-like behavior we remove the key and then add it back:

columns.Remove(columnName);
columns.Add(columnName, val);

If the object is not in the NEW state, the value is set to that of an appropriate row/column combination of the DataSet. Remember that the row this object corresponds to is obtained by the object ID or primary key.

DataRow row = ds.Tables[GetTableName()].Rows.Find((string)pk);
row[columnName] = val;

When the object is loaded using the Load static method, it flags the object's state to LOAD. Once loaded, the object caches the DataSet, the SqlDataAdapter, and the primary key value. It is fairly expensive to retrieve these every time, and therefore it makes sense to cache them the first time the object is loaded.

obj.adapter = new SqlDataAdapter (query, conn);
obj.ds = new DataSet(obj.GetTableName());
obj.pk = pk;

For the insertion of the object, you will need a Hashtable of column names to column values. This is then used to create the DataSet and SqlDataAdapter in the Insert method. If the insert is successful, Hashtable can reuse the DataSet and SqlDataAdapter for future updates.

string query = "SELECT * FROM "+GetTableName();
adapter = new SqlDataAdapter (query, conn);
ds = new DataSet();
adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
adapter.Fill(ds, GetTableName());
DataRow row = ds.Tables[GetTableName()].NewRow();
foreach (object key in columns.Keys) {
  row[key.ToString()] = columns[key];
}
ds.Tables[GetTableName()].Rows.Add(row);
SqlCommandBuilder builder = new SqlCommandBuilder(adapter);
adapter.Update(ds, GetTableName());
state = STATE.LOAD;

Note how the Insert method sets the state of the object to LOAD.

Listing 19.11 shows a subclass that maps to the Customers table and shows how to use the SqlPersistentObject to load and save objects.

Listing 19.11 Subclass of SqlPersistentObject from Listing 19.10 (C#)
namespace DietPersistentFrameWork {

  using System;
  using System.Data;
  using System.Data.SqlClient;

  public class Customer : SqlPersistentObject {

    public override string GetTableName() { return "CUSTOMERS"; }

    public override string GetPrimaryKey(){ return
"CUSTOMERID"; }

    public static void Main(string[] args) {
      SqlConnection conn = GetConnection();
      try {
        //LOAD CUSTOMER
        Customer cust = new Customer();
        SqlPersistentObject.Load(conn, cust, "ANATR");
        Console.WriteLine(cust.GetField("CustomerId"));
        //UPDATE CUSTOMER
        SqlPersistentObject.Load(conn, cust, "ANATR");
        cust.SetField("ContactTitle", "CTO");
        cust.save(conn);
        //INSERT NEW CUSTOMER
        cust = new Customer();
        cust.SetField("CustomerID", "C3P0");
        cust.SetField("CompanyName", "ACME");
        cust.SetField("ContactTitle", "Super Owner");
        cust.save(GetConnection());

      } finally {
         conn.Close();
    }
  }

    public static SqlConnection GetConnection() {
      SqlConnection conn = new SqlConnection ();
      conn.ConnectionString = "Initial Catalog=Northwind;Data
                   Source=localhost;Integrated Security=SSPI;";
      conn.Open();
      return conn;
    }
  }
}

Because most of the complexity of persisting and loading the object is taken care of by the superclass, the subclass specification becomes trivial. In fact the Customer object has only two methods, which return the table name and the name of the primary key column:

public void SetField(string columnName, object val) {
  if (state != STATE.NEW ) {
    DataRow row = ds.Tables[GetTableName()].Rows.Find((
                                                    string)pk);
      row[columnName] = val;
  } else {
    columns.Remove(columnName);
    columns.Add(columnName, val);
  }
}

If the object is not in the NEW state, the value of the appropriate row/column combination of the DataSet is returned.


19.5 Summary

Both ADO.NET and JDBC provide a unified API for accessing any data store. Whereas the JDBC API is geared primarily toward an RDBMS, ADO.NET can be used to access data from text or XML streams.

The two key concepts of ADO.NET are the .NET data provider and the DataSet class. The .NET data provider is a set of classes for interfacing with a data source. A .NET data provider consists of all classes for connecting to the database, retrieving data, and running DML on the underlying RDBMS.

The DataSet class is an in-memory disconnected representation of the underlying RDBMS. The DataSet object can be used to programmatically run a subset of Data Definition Language and Data Modification Language. You can generate an entire database using the DataSet object.

Although JDBC was not discussed in this chapter, the following observations can be made regarding JDBC and ADO.NET.

  • JDBC and ADO.NET provide APIs for abstracting the concept of connecting to the database using a connection object. Both support connection pooling.

  • Whereas JDBC makes no distinction between the different RDBMSs, .NET provides a separate provider for the Microsoft SQL Server database so that it can optimize access to that database.

  • JDBC and ADO.NET provide a data structure for paging and scrolling through results of a select query. (ResultSet is the Java object, and DataReader is the ADO.NET object.)

  • JDBC and ADO.NET provide objects for compiling SQL statements that can be used to run select, update, insert, and delete queries. (In Java, the object is PreparedStatement; in ADO.NET it is Command.)

  • The RowSet object of JDBC 3.0 is roughly equivalent to the DataSet object, although the DataSet object goes further in defining in detail the metadata of the table structures and the table relations.

  • JDBC supports trivial database transactions by turning auto-commit off on the connection object. ADO.NET provides a first-class object for demarcating trivial database transactions. Both offer savepoints.

  • JDBC supports database metadata and result set metadata through separate classes. ADO.NET helps you explore the metadata using the DataSet object.

  • The .NET DataSet can be serialized to an XML stream.

  • Both JDBC and .NET (through .NET's DataSet) support batch updates.


Chapter 20. Processing XML

Like the popular HTML, Extensible Markup Language (XML) consists of tagged, human-readable text. Unlike HTML, the tags in an XML document follow one simple rule: For every opening tag <tag> there is a closing tag </tag> . An XML document in which every opening tag has a closing tag is said to be well formed.

As long as the XML document is well formed, you can fabricate the tags in any way you want. An XML document is typically parsed by an XML parser, which creates an in-memory logical data structure for navigating the document. There are different types of XML parsers. The most common do not usually care what the tags are as long as they are well formed. Sometimes a parser can validate an XML document against a set of rules that limit the document to only a certain subset of tags. Such parsers are called validating parsers.

The two most popular mechanisms for parsing XML documents are to create a Document Object Model (DOM) tree or to use the event-based Simple API for XML (SAX) model. An XML document can be validated against a DTD (the set of rules that define the type and structure of the XML tags) or an XML schema.

This chapter looks at C#'s API for DOM and SAX parsing of XML documents. We look at validating an XML document against a DTD. We also look at other utilities, such as XPath and Extensible Stylesheet Transformation (XSLT), that are built into the .NET API.


20.1 XML Support in Java

For a long time, XML was not built into the Java API. Support for XML was primarily through third-party libraries (such as Apache Xerces or JDOM). Fortunately, that has changed, and now you can get the Java XML Pack, a toolset for dealing with everything XML in Java. The XML Pack brings together several of the key industry standards for XML, such as SAX, DOM, XSLT, SOAP, Universal Description, Discovery & Integration (UDDI), Electronic Business using Extensible Markup Language (ebXML), and Web Services Description Language (WSDL). The two common programmatic XML APIs (SAX and DOM) are now built into the core Java API (as of J2SE 1.4.0).

The SAX parser is an event-driven parser in which the parser fires off events when it encounters XML elements. Users write content handlers, which they can register with the parser. A content handler is like an event listener and can take appropriate action upon encountering, say, a particular XML tag. The SAX parser is based on a push model, wherein the parser pushes events to content handlers.

The DOM parser parses the XML into an in-memory tree data structure (also known as a DOM tree). The Document Object Model is an API for valid HTML and well-formed XML documents. It defines the logical structure of documents and the way a document is accessed and manipulated. In the DOM specification, the term "document" is used in the broad sense; increasingly, XML is being used as a way to represent many kinds of information that may be stored in diverse systems. Much of this has traditionally been seen as data rather than as documents. Nevertheless, XML presents this data as documents, and the DOM can be used to manage this data.

With the Document Object Model, programmers can build documents, navigate their structure, and add, modify, or delete elements and content. Anything found in an HTML or XML document can be accessed, changed, deleted, or added using the DOM. The DOM is a W3C specification (http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/). The JDOM (http://www.jdom.org) API is one of the easier APIs for dealing with the XML DOM.


20.2 XML and .NET

Because of its extensible nature, XML can be used for a variety of purposes. Modern applications use XML as a messaging protocol, as a transport protocol, and even for backend data storage. The .NET platform uses XML as its key technology in ASP.NET, Web services, and so on. The .NET Framework XML classes are also tightly coupled with Managed Data Access (ADO.NET). Traditionally, there have always been different programming models for working with relational versus hierarchical data. The .NET platform, however, allows for transparent translation of relational data to XML and vice versa. This makes it possible to retrieve a relational ADO.NET dataset and walk it using the DOM, query or navigate it using XPath 1.0, and transform it using XSLT 1.0.

As mentioned earlier, the primary language-neutral APIs for parsing XML documents are the DOM and the SAX API. The DOM API stores the XML document in a tree data structure, thereby allowing complete navigation of all the elements (in no particular order). Users can modify the DOM tree. This API is very convenient, but it can consume a lot of memory when you're working with large documents.

The SAX API was developed to counter the defects of the DOM API. The SAX API uses an event-driven model in which parsing every character of the XML document is handled through event listeners. The parser parses the document in only one direction. The SAX API puts the onus of handling the state of the document parsing on the end user, and therefore it is inherently difficult and awkward to use.

The .NET XML classes support both DOM and SAX-like APIs. The SAX-like API in .NET is an improvement over the traditional SAX model, in the sense that it gives a lot more control to the end user during the parsing process. The so-called pull model used by .NET's version of SAX allows programmers to skip sections of the document, process only certain elements of the document, and interact a lot more with the document. You can say that .NET's SAX sits between DOM and pure SAX. It mixes the convenience of DOM with the power of the pure SAX API.

The .NET XML classes are spread over the namespaces given in Table 20.1.

We will look at the classes in the System.Xml and System.Xml.XPath namespaces.


20.3 Reading and Writing XML

At the core of the .NET Framework XML classes are two abstract classes: XmlReader and XmlWriter. These are found in the System.Xml namespace.

XmlReader provides a fast, forward-only, read-only cursor for processing an XML document stream. XmlWriter provides an interface for producing XML document streams. Both classes imply a streaming model that doesn't require an expensive in-memory cache. This makes them both attractive alternatives to the classic DOM approach. Both XmlReader and XmlWriter are abstract base classes and define the functionality that all derived classes must support.

Table 20.1. .NET XML Namespaces

Namespace

Description

System.Xml

Contains the core classes required for reading and writing XML documents.

System.Xml.Schema

Contains the XML classes that provide standards-based support for XML Schema Definition Language (XSD) schemas. The supported standards are as follows:

  • XML Schemas for Structures (http://www.w3.org/TR/xmlschema1/) supports schema mapping and validation.

  • XML Schemas for Data Types (http://www.w3.org/TR/xmlschema2/) supports data types for XML Schema definitions.

System.Xml.Serialization

Contains classes that are used to serialize and deserialize objects into XML format documents or streams.

System.Xml.XPath

Contains the XPath parser and evaluation engine. It supports the W3C XML Path Language (XPath) Version 1.0 Recommendation (www.w3.org/TR/xpath).

System.Xml.Xsl

Provides support for XSLT transforms. Also supports the W3C XSL Transformations (XSLT) Version 1.0 Recommendation (www.w3.org/TR/xslt).

There are three concrete implementations of XmlReader:

  1. XmlTextReader. This class provides forward-only, read-only access to a text-based stream of XML data. The reader is advanced using any of the read methods, and properties reflect the value of the current node (the node on which the reader is positioned). This class does not provide data validation. It's ideal for fast parsing of XML text that is well formed. It uses the DTD for checking whether the XML is well formed but does not validate using the DTD.

  2. XmlNodeReader. This class reads an XML DOM subtree. It does not support DTD or schema validation.

  3. XmlValidatingReader. This class represents a reader that provides DTD, XML-Data Reduced (XDR) schema, and XML Schema Definition Language (XSD) schema validation.

The XmlTextWriter class is a writer that provides a fast, noncached, forward-only way of generating streams or files containing XML data.

For the examples in this chapter we assume that our XML document is stored in a file called people.xml, whose contents are shown in Listing 20.1.

Listing 20.1 The Sample XML Document (C#)
<People>
  <Person id="1" ssn="555121212">
    <Name>
      <FirstName>Joe</FirstName>
      <LastName>Suits</LastName>
    </Name>
    <Address>
      <Street>1800 Success Way</Street>
      <City>Redmond</City>
      <State>WA</State>
      <ZipCode>98052</ZipCode>
    </Address>
    <Job>
      <Title>CEO</Title>
      <Description>Wears the nice suit</Description>
    </Job>
  </Person>

  <Person id="2" ssn="666131313">
    <Name>
      <FirstName>Linda</FirstName>
      <LastName>Sue</LastName>
    </Name>
    <Address>
      <Street>1302 American St.</Street>
      <City>Paso Robles</City>
      <State>CA</State>
      <ZipCode>93447</ZipCode>
    </Address>
    <Job>
      <Title>Attorney</Title>
      <Description>Stands up for justice</Description>
    </Job>
  </Person>

  <Person id="3" ssn="777141414">
    <Name>
      <FirstName>Jeremy</FirstName>
      <LastName>Boards</LastName>
    </Name>
    <Address>
      <Street>34 Palm Avenue</Street>
      <City>Waikiki</City>
      <State>HI</State>
      <ZipCode>98052</ZipCode>
    </Address>
    <Job>
      <Title>Pro Surfer</Title>
      <Description>Rides the big waves</Description>
    </Job>
  </Person>

  <Person id="4" ssn="888151515">
    <Name>
      <FirstName>Joan</FirstName>
      <LastName>Page</LastName>
    </Name>
    <Address>
      <Street>700 Webmaster Road</Street>
      <City>Redmond</City>
      <State>WA</State>
      <ZipCode>98073</ZipCode>
    </Address>
    <Job>
      <Title>Web Site Developer</Title>
      <Description>Writes the pretty pages</Description>
    </Job>
  </Person>
</People>

We will use the XML in Listing 20.1 for the rest of the samples.

20.3.1 Reading XML in .NET

Listing 20.2 shows the usage of XmlTextReader.

Listing 20.2 Browsing the XML File Using XmlTextReader (C#)
using System;
using System.Xml;

public class Test {
  public static void Main(string[] args) {
    XmlTextReader reader = new XmlTextReader("c:\\people.xml");
    reader.WhitespaceHandling = WhitespaceHandling.None;
    //Moves the reader to the root element.
    reader.MoveToContent();
    Console.WriteLine("Name,Value,Type ");
    while (reader.Read()) {
      Console.Write(reader.Name+",");
      Console.Write(reader.Value+",");
      Console.Write(reader.NodeType);
      Console.WriteLine();
    }
  reader.Close();
  }
}

Here is a partial output of Listing 20.2:

Name,Value,Type
People,,Element
Person,,Element
Name,,Element
FirstName,,Element
,Joe,Text
FirstName,,EndElement
LastName,,Element
,Suits,Text
LastName,,EndElement
Name,,EndElement
Address,,Element
Street,,Element
,1800 Success Way,Text
Street,,EndElement
City,,Element
,Redmond,Text
City,,EndElement

We first initialize an XmlTextReader by passing it the URL of the XML file:

XmlReader reader = new XmlTextReader("c:\\people.xml");

Next, we tell the XmlTextReader to ignore white space nodes when parsing the XML:

reader.WhitespaceHandling = WhitespaceHandling.None;

Then we position the reader to the root content node. In XML, nodes of the following types are considered noncontent nodes: ProcessingInstruction, DocumentType, Comment, Whitespace, and SignificantWhitespace. The following code skips any leading noncontent node(s) at the beginning of the file and positions the reader to the first content node, <People> :

//Moves the reader to the root element.
reader.MoveToContent();

When an XmlTextReader is first initialized there is no current node, so the first call to Read moves to the first node in the document. When an XmlReader reaches the end of the document, it doesn't walk off the end, leaving the document in an indeterminate state; instead, it simply returns false when there are no more nodes to process.

Calling reader.Read() in a while loop moves the cursor forward through all the nodes. At the end of each call to the loop, the cursor moves to the next node. Note that any code inside the while loop is applied only to the current visited node.

So far, we have seen how to browse the file. Listing 20.3 shows how to gather useful information from the file. Let's say we are interested in knowing all job titles in the people.xml file. Listing 20.3 shows how to achieve that.

Listing 20.3 Fetching Values of All Text Nodes Named "Title" (C#)
using System;
using System.Xml;

public class Test {
  public static void Main(string[] args) {
    XmlTextReader reader = new XmlTextReader("c:\\people.xml");
    //Moves the reader to the root element.
    reader.MoveToContent();
    while (reader.Read()) {
      if (reader.Name.Equals("Title") && reader.NodeType ==
          XmlNodeType.Element) {
        Console.WriteLine(reader.ReadElementString("Title"));
      }
    }
    reader.Close();
  }
}

The output of Listing 20.3 is as follows:

CEO
Attorney
Pro Surfer
Web Site Developer

The <Title> node is a text-only element, and so we use the method reader. The ReadElementString method is a helper method to obtain the text and value of that node. It is important to call this method for the right node. Note that we first check that the enclosing node name is "Title" and that it is of the type Element. After we confirm that the current node being visited is <Title> and not </Title>, we call ReadElementString.

The XmlReader class provides a forward-only cursor, and hence all the nodes are sequentially visited and checked to see whether they meet the criteria. Although this approach works, it is inefficient for any modest data mining work. The XPath API (discussed in Section 20.4.1) gives more control of the search process and should be used for XML searching.

Read() doesn't encounter attribute nodes because they aren't considered part of a document's hierarchical structure. Attributes are typically considered metadata attached to structural elements. When the current node is an element, its attributes can be accessed through calls to GetAttribute by name or index. Listing 20.4 shows how to access attributes of a node.

Listing 20.4 Displaying the Attributes of a Node (C#)
using System;
using System.Xml;

public class Test {
  public static void Main(string[] args) {
    XmlTextReader reader = new XmlTextReader("c:\\people.xml");
    //Moves the reader to the root element.
    reader.MoveToContent();
    while (reader.Read()) {
      if (reader.HasAttributes) {
        Console.WriteLine("Id = "+reader.GetAttribute("id")+"
                            SSN ="+reader.GetAttribute("ssn"));
      }
    }
  reader.Close();
  }
}

The output of Listing 20.4 is as follows:

Id = 1 SSN =555121212
Id = 2 SSN =666131313
Id = 3 SSN =777141414
Id = 4 SSN =888151515

Not all the nodes in the XML file have attributes, and therefore we use the HasAttributes property of the node to check whether the node has attributes defined.

An XML document contains data just as an RDBMS table contains rows, but RDBMSs provide SQL for accessing that data. To efficiently query and search for data in an XML document, you should use the XPath API (discussed later). Although XmlReader provides an API to visit all nodes, it is inefficient to use it by itself for any kind of modest data mining effort on an XML document.

Unlike the SAX event model—which fires off events upon encountering tags and requires you to have ContentHandler classes with specialized method signatures—the .NET API allows you to conveniently scan an entire XML document inside a single while loop. Although you can parse the entire document inside this loop, it is advisable to break the document into manageable chunks and hand off those chunks to classes or methods that can parse them. This technique is especially useful when the XML data maps to a code element (such as a class).

For example, suppose we're using people.xml to create Person objects. Listing 20.5 shows how the XML can be parsed to create meaningful data.

Listing 20.5 Parsing XML to Create Objects (C#)
using System;
using System.IO;
using System.Xml;
using System.Collections;

public class PersonReader {

  private XmlTextReader reader;
  public PersonReader (string url) {
    reader = new XmlTextReader (url);
    reader.MoveToContent();
  }
  public bool NextPerson() { return reader.Read(); }
  public Person GetPerson() { return new Person(reader); }

  public static void Main(string[] args) {

    PersonReader reader = new PersonReader("c:\\people.xml");
    while (reader.NextPerson()) {
      Person p = reader.GetPerson();
      if (p != null) {
        Console.WriteLine(p.GetZip());
      }
    }

  }
}

public class Person {

  Hashtable attributes;

  public Person (XmlTextReader reader) {

    attributes = new Hashtable();
    attributes.Add("id", null);
    attributes.Add("ssn", null);
    attributes.Add("FirstName", null);
    attributes.Add("LastName", null);
    attributes.Add("City", null);
    attributes.Add("State", null);
    attributes.Add("Street", null);
    attributes.Add("ZipCode", null);
    attributes.Add("Title", null);
    attributes.Add("Description", null);

    while (reader.Read()) {

      if (reader.NodeType == XmlNodeType.EndElement &&
        reader.Name.Equals("Person")) break;

      if (reader.HasAttributes) {
        attributes["id"] = reader.GetAttribute("id");
        attributes["ssn"] = reader.GetAttribute("ssn");
      }
      string name = reader.Name;
      if (attributes.ContainsKey(name)) {
        attributes[name] = reader.ReadElementString(name);
      }
    }
  }
  public object GetID() { return attributes["id"]; }
  public object GetFirstName() { return attributes
                                             ["FirstName"]; }
  public object GetLastName() { return attributes["LastName"];
}
  public object GetSSN() { return attributes["ssn"]; }
  public object GetCity() { return attributes["City"]; }
  public object GetState() { return attributes["State"]; }
  public object GetStreet() { return attributes["Street"]; }
  public object GetZip() { return attributes["ZipCode"]; }
  public object GetTitle() { return attributes["Title"]; }
  public object GetDescription() {
    return attributes["Description"];
  }
}

The output of Listing 20.5 is as follows:

98052
93447
98052
98073

We start by creating a PersonReader, which wraps XmlTextReader. Then NextPerson and GetPerson encapsulate the navigation over the XmlTextReader. The Person object contains a Hashtable of attributes. The constructor of the Person object merely iterates through the reader object, retrieves tag values it is interested in, and sets its state in the attributes Hashtable . After the state of the Person object is initialized, the accessor methods (getter methods) can access the state of the Person object.

So far, the XML parsed has been well formed but not necessarily valid. XmlTextReader supports validation against DTDs, XML-Data Reduced (XDR) schemas, and XSD schemas. Consumers can use the Validation property to control how the reader performs validation. In its default state, XmlTextReader will auto-detect DTDs and schemas to process entities and default attribute values. To turn on validation, you must provide a ValidationHandler. When set to Validation.Schema, the reader automatically detects whether an XDR or XSD schema is in use. Validation Handler is an event handler for the reader to use when it encounters validation errors. The callback method is associated with the reader through the ValidationEventHandler event property.

The DTD for people.xml is stored in a file called people.dtd:

<!ELEMENT People (Person+)>
<!ELEMENT Person (Name,Address*,Job*)>
<!ELEMENT Name (FirstName,LastName)>
<!ELEMENT FirstName (#PCDATA)>
<!ELEMENT LastName (#PCDATA)>
<!ELEMENT Address (Street,ZipCode,City,State)>
<!ELEMENT Street (#PCDATA)>
<!ELEMENT ZipCode (#PCDATA)>
<!ELEMENT City (#PCDATA)>
<!ELEMENT State (#PCDATA)>
<!ELEMENT Job (Title,Description)>
<!ELEMENT Title (#PCDATA)>
<!ELEMENT Description (#PCDATA)>

Note that we have intentionally omitted the definition of the attributes for the Person node.

Next, we add the following line to the top of the people.xml file:

<!DOCTYPE people SYSTEM "people.dtd">

Listing 20.6 shows how to use a validating XML reader.

Listing 20.6 Validating XML against a DTD (C#)
using System;
using System.Xml;
using System.Xml.Schema;

public class Test {

  public static void Main(string[] args) {

    XmlValidatingReader reader = new XmlValidatingReader (
                          new XmlTextReader("c:\\people.xml"));
    reader.ValidationType = ValidationType.DTD;
    reader.ValidationEventHandler += new
      ValidationEventHandler (CallBack);

    //Moves the reader to the root element.
    reader.MoveToContent();
    while (reader.Read()) {
    }
    reader.Close();

  }

  public static void CallBack (Object obj,
    ValidationEventArgs args) {
    Console.WriteLine(args.Message);
  }
}

The output of Listing 20.6 is as follows:

[View full width]

The root element name must match the DocType name. An error occurred at file:///c:/people.
  xml(2, 2).
The 'id' attribute is not declared. An error occurred at file:///c:/people.xml(3, 13).
The 'ssn' attribute is not declared. An error occurred at file:///c:/people.xml(3, 20).
Element 'Address' has invalid child element 'City'. Expected 'ZipCode'. An error occurred
  at file:///c:/people.xml(10, 14).
The 'id' attribute is not declared. An error occurred at file:///c:/people.xml(20, 13).
The 'ssn' attribute is not declared. An error occurred at file:///c:/people.xml(20, 20).
Element 'Address' has invalid child element 'City'. Expected 'ZipCode'. An error occurred
  at file:///c:/people.xml(27, 14).
The 'id' attribute is not declared. An error occurred at file:///c:/people.xml(37, 13).
The 'ssn' attribute is not declared. An error occurred at file:///c:/people.xml(37, 20).
Element 'Address' has invalid child element 'City'. Expected 'ZipCode'. An error occurred
  at file:///c:/people.xml(44, 14).
The 'id' attribute is not declared. An error occurred at file:///c:/people.xml(54, 13).
The 'ssn' attribute is not declared. An error occurred at file:///c:/people.xml(54, 20).
Element 'Address' has invalid child element 'City'. Expected 'ZipCode'. An error occurred
  at file:///c:/people.xml(61, 14).

Listing 20.6 starts by creating a validating reader:

XmlValidatingReader reader = new XmlValidatingReader (new
    XmlTextReader("c:\\people.xml"));

We then set the target against which the XML is to be validated. This can be an XSD schema or a DTD. Because we are validating against the DTD, we specify that as follows:

reader.ValidationType = ValidationType.DTD;

Next, we specify the callback handler that will be called during the validation process. Note that ValidationEventHandler is a delegate of the reader object.

reader.ValidationEventHandler += new ValidationEventHandler (CallBack);

We get errors during parsing of the XML, as shown in the output. We omitted the attribute definition in the DTD, and the parser makes it obvious when validating the XML. These errors are printed thanks to the following line in the CallBack method:

Console.WriteLine(args.Message);

20.3.2 Writing XML in .NET

So far, we have seen how to read XML from a data stream; now we will explore writing XML to a file or a memory stream. XmlTextWriter is a concrete class, derived from XmlWriter, for writing character streams. It supports many different output stream types (file URI, stream, and TextWriter) and is configurable. You can specify things such as whether to provide namespace support, indentation options, the quote character to be used for attribute values, and even the lexical representation to be used for typed values.

The Person class shown in Listing 20.5 now has a ToXml() method that prints the XML representation of the Person object (see Listing 20.7).

Listing 20.7 Printing the State of an Object in XML (C#)
using System;
using System.Text;
using System.IO;
using System.Xml;
using System.Collections;

public class Person {

  Hashtable attributes;

    public Person () {
      attributes = new Hashtable();
      attributes.Add("id", null);
      attributes.Add("ssn", null);
      attributes.Add("FirstName", null);
      attributes.Add("LastName", null);
      attributes.Add("City", null);
      attributes.Add("State", null);
      attributes.Add("Street", null);
      attributes.Add("ZipCode", null);
      attributes.Add("Title", null);
      attributes.Add("Description", null);
    }

    public object GetID() { return attributes["id"]; }
    public object GetFirstName() { return attributes["First
                                                   Name"]; }
    public object GetLastName() { return attributes["Last
                                                   Name"]; }
    public object GetSSN() { return attributes["ssn"]; }
    public object GetCity() { return attributes["City"]; }
    public object GetState() { return attributes["State"]; }
    public object GetStreet() { return attributes["Street"]; }
    public object GetZip() { return attributes["ZipCode"]; }
    public object GetTitle() { return attributes["Title"]; }
    public object GetDescription() {
      return attributes["Description"];
    }

    public void SetID(object o) { attributes["id"] = o; }
    public void SetFirstName(object o) { attributes["FirstName"]
                                                     = o; }
    public void SetLastName(object o) { attributes["LastName"]=
                                                    o; }
    public void SetSSN(object o) { attributes["ssn"] = o; }
    public void SetCity(object o) { attributes["City"] = o; }
    public void SetState(object o) { attributes["State"] = o; }
    public void SetStreet(object o) { attributes["Street"] = o; }
    public void SetZip(object o) { attributes["ZipCode"] = o; }
    public void SetTitle(object o) { attributes["Title"] = o; }
    public void SetDescription(object o) {
      attributes["Description"] = o;
    }

    public void ToXml() {

      XmlTextWriter w = new XmlTextWriter (Console.Out);
      w.Formatting = Formatting.Indented;

      w.WriteStartElement("Person");
      w.WriteStartAttribute("id", "");
      w.WriteString((string)GetID());
      w.WriteEndAttribute();
      w.WriteStartAttribute("ssn", "");
      w.WriteString((string)GetSSN());
      w.WriteEndAttribute();

      w.WriteStartElement("Name");
      w.WriteElementString("FirstName", (string)GetFirstName());
      w.WriteElementString("LastName", (string)GetFirstName());
      w.WriteEndElement();

      w.WriteStartElement("Address");
      w.WriteElementString("Street", (string)GetStreet());
      w.WriteElementString("City", (string)GetCity());
      w.WriteElementString("State", (string)GetState());
      w.WriteElementString("ZipCode", (string)GetZip());
      w.WriteEndElement();

      w.WriteStartElement("Job");
      w.WriteElementString("Title", (string)GetTitle());
      w.WriteElementString("Description", (string)Get
                                            Description());
      w.WriteEndElement();

      w.WriteEndElement();
    }

    public static void Main(string[] args) {
      Person p = new Person();
      p.SetID("3");
      p.ToXml();
    }
}

The output of Listing 20.7 is as follows:

<Person id="3" ssn="">
  <Name>
    <FirstName />
    <LastName />
  </Name>
  <Address>
    <Street />
    <City />
    <State />
    <ZipCode />
  </Address>
  <Job>
    <Title />
    <Description />
  </Job>
</Person>

In the ToXml method we start by creating an XmlTextWriter:

XmlTextWriter w = new XmlTextWriter (Console.Out);

The XmlTextWriter constructor is overloaded to write the XML to any stream (file, memory, string). In this case we chose to print it on the console.

Next, we set the formatting to Indented so that output is formatted properly:

w.Formatting = Formatting.Indented;

The rest of the ToXml method consists of calling the appropriate WriteXXXX() method.


20.4 Using the DOM API in .NET

The .NET DOM implementation (System.Xml.XmlDocument) supports all of DOM Level 1 and all of DOM Level 2 Core, but with a few minor naming changes. DOM loading is built on top of XmlReader, and DOM serialization is built on XmlWriter.

As mentioned earlier, central to the DOM API is the DOM tree data structure. The tree consists of nodes or elements. All nodes of a typical XML document (elements, processing instructions, attributes, text, comments, and so on) are represented in the DOM tree. Because the DOM tree is built in memory for the entire document, you are free to navigate anywhere in the document (unlike the XmlReader class, which has a forward-only cursor).

The core class that forms the root of this tree is the XmlDocument class. You create an XmlDocument object by loading any well-formed XML data stream. Listing 20.8 shows how to load the XmlDocument from the people.xml file and how to traverse the document. The best way to traverse a tree data structure is by recursion. Listing 20.8 shows how you can use recursion to traverse the XML DOM tree.

Listing 20.8 Traversing the DOM Tree (C#)
using System;
using System.Xml;

public class Test {

  public static void Main(string[] args) {

    XmlDocument doc = new XmlDocument();
    doc.Load("c:\\people.xml");
    PrintNodeDetail(doc.GetElementsByTagName("People")[0]);
  }

  private static void PrintNodeDetail (XmlNode node) {

    //Print the node type, node
      //name and node value (if any) of the node
    if (node.NodeType == XmlNodeType.Text) {
      Console.WriteLine("Type= [" ode.NodeType+"]
                                            Value=" ode.Value);

    } else {
      Console.WriteLine("Type= [" ode.NodeType+"]
                                              Name=" ode.Name);
    }

    //Print attributes of the node
    if (node.Attributes != null) {
      XmlAttributeCollection attrs = node.Attributes;
      foreach (XmlAttribute attr in attrs) {
        Console.WriteLine("\tAttribute Name ="+attr.Name+"
                                Attribute Value ="+attr.Value);
      }
    }
    //Print individual children of the node
    XmlNodeList children = node.ChildNodes;
    foreach (XmlNode child in children) {
      PrintNodeDetail (child);
    }
  }
}

Here is a partial output of Listing 20.8:

Type= [Element] Name=People
Type= [Element] Name=Person
Attribute Name =id Attribute Value =1
Attribute Name =ssn Attribute Value =555121212
Type= [Element] Name=Name
Type= [Element] Name=FirstName
Type= [Text] Value=Joe
Type= [Element] Name=LastName
Type= [Text] Value=Suits
Type= [Element] Name=Address
Type= [Element] Name=Street
Type= [Text] Value=1800 Success Way
Type= [Element] Name=City
Type= [Text] Value=Redmond
Type= [Element] Name=State
Type= [Text] Value=WA
Type= [Element] Name=ZipCode
Type= [Text] Value=98052
Type= [Element] Name=Job
Type= [Element] Name=Title
Type= [Text] Value=CEO
Type= [Element] Name=Description
Type= [Text] Value=Wears the nice suit

The XmlDocument is first instantiated, and a file URL is passed to it that contains well-formed XML. The document loads the XML from the file and automatically generates the DOM tree:

XmlDocument doc = new XmlDocument();
doc.Load("c:\\people.xml");

Next, we get a handle to the very first node of the document tree:

doc.GetElementsByTagName("People")[0]

If you have dabbled with DHTML or JavaScript you will recognize the method name GetElementsByTagName.

After the first node is obtained, we use the PrintNodeDetail method to recursively traverse through all the children of that node. PrintNodeDetail is generic enough to print details of any node type. Remember that the DOM tree consists of nodes of different types (elements, attributes, processing instructions, comments, text nodes, and so on). We first print generic information about the node (name, type); if it's a text node, we print the value of the node:

if (node.NodeType == XmlNodeType.Text) {
    Console.WriteLine("Type= ["+node.NodeType+"]
                               Value="+node.Value);
  } else {
    Console.WriteLine("Type= ["+node.NodeType+"] Name="+node.
Name);
  }

Next, we print any attributes associated with the node:

if (node.Attributes != null) {
  XmlAttributeCollection attrs = node.Attributes;
  foreach (XmlAttribute attr in attrs) {
    Console.WriteLine("\tAttribute Name ="+attr.Name+
                    " Attribute Value ="+attr.Value);
  }
}

The last step gets all the children of the current node and calls PrintNodeDetail on each of the children.

Note that the ChildNodes method gets only the direct children of the node. To get all children of a node, you must use recursive code:

XmlNodeList children = node.ChildNodes;
foreach (XmlNode child in children) {
  PrintNodeDetail (child);
}

If you have used the JDOM library (www.jdom.org) for parsing DOM trees in Java, the .NET DOM API should be fairly easy to follow.

Because of its in-memory storage of the entire XML document, the DOM API provides a convenient way to query for nodes matching certain criteria. Listing 20.9 shows ways to query for specific nodes.

Listing 20.9 Querying for Specific Nodes (C#)
using System;
using System.Xml;

public class Test {
  public static void Main(string[] args) {

    XmlDocument doc = new XmlDocument();
    doc.Load("c:\\people.xml");

    //Get all job titles in the people.xml
    XmlNodeList list = doc.GetElementsByTagName("Title");
    foreach (XmlNode node in list) {
      Console.WriteLine(node.FirstChild.Value);
    }

    //Get the last name of the first person
    XmlNode nn = doc.GetElementsByTagName("Person")[0];
    Traverse(nn);
  }

  private static void Traverse(XmlNode node) {
    foreach (XmlNode child in node.ChildNodes) {
      if ("LastName".Equals(child.Name) && child.NodeType ==
                                         XmlNodeType.Element) {
        Console.WriteLine(child.FirstChild.Value);
      }
      Traverse(child);
    }
  }
}

The output of Listing 20.9 is as follows:

CEO
Attorney
Pro Surfer
Web Site Developer
Suits

The XmlDocument object supports two methods: GetElementsByTagName and GetElementByID. These method names are very efficient at zooming into a specific location in the XML file. For example, to get a list of all job titles in the XML document, you search for all elements having the tag name "Title":

XmlNodeList list = doc.GetElementsByTagName("Title");
foreach (XmlNode node in list) {
  Console.WriteLine(fnode.FirstChild.Value);
}

Although the DOM tree provides for bidirectional navigation of the XML document, the search for specific details is still sequential, as indicated by the Traverse(..) method of Listing 20.9. In the Traverse method we must recursively iterate through each child of the first Person node and check whether the node is a LastName node. The DOM API is not the most efficient way for advanced data mining of XML documents. You should use XPath for that purpose.

The DOM API can also be used to write XML documents. Listing 20.10 shows the ToXml() method of the Person object written using the DOM API.

Listing 20.10 Writing XML Using DOM (C#)
using System;
using System.Text;
using System.IO;
using System.Xml;
using System.Collections;

public class Person {

  Hashtable attributes;

  public Person () {
    attributes = new Hashtable();
    attributes.Add("id", null);
    attributes.Add("ssn", null);
    attributes.Add("FirstName", null);
    attributes.Add("LastName", null);
    attributes.Add("City", null);
    attributes.Add("State", null);
    attributes.Add("Street", null);
    attributes.Add("ZipCode", null);
    attributes.Add("Title", null);
    attributes.Add("Description", null);
  }

  public object GetID() { return attributes["id"]; }
  public object GetFirstName() { return attributes["First
                                                    Name"]; }
  public object GetLastName() { return attributes["LastName"]; }
  public object GetSSN() { return attributes["ssn"]; }
  public object GetCity() { return attributes["City"]; }
  public object GetState() { return attributes["State"]; }
  public object GetStreet() { return attributes["Street"]; }
  public object GetZip() { return attributes["ZipCode"]; }
  public object GetTitle() { return attributes["Title"]; }
  public object GetDescription() { return
                      attributes["Description"];
  }

  public void SetID(object o) { attributes["id"] = o; }
  public void SetFirstName(object o) { attributes["FirstName"]
                                                         = o; }
  public void SetLastName(object o) { attributes["LastName"]=
                                                           o; }
  public void SetSSN(object o) { attributes["ssn"] = o; }
  public void SetCity(object o) { attributes["City"] = o; }
  public void SetState(object o) { attributes["State"] = o; }
  public void SetStreet(object o) { attributes["Street"] = o; }
  public void SetZip(object o) { attributes["ZipCode"] = o; }
  public void SetTitle(object o) { attributes["Title"] = o; }
  public void SetDescription(object o) {
                      attributes["Description"] = o;
  }

  public void ToXml() {
    XmlTextWriter tw = new XmlTextWriter(Console.Out);
    XmlNode childNode = null;
    tw.Formatting = Formatting.Indented;

    XmlDocument doc = new XmlDocument();
    XmlNode node = doc.CreateElement("Person");
    AppendAttribute(doc, node, "id");
    AppendAttribute(doc, node, "ssn");

    doc.AppendChild(node);

    childNode = AppendElement(doc, node, "Name",true);
    AppendElement(doc, childNode, "LastName", false);
    AppendElement(doc, childNode, "FirstName", false);

    childNode = AppendElement(doc, node, "Address",true);
    AppendElement(doc, childNode, "Street", false);
    AppendElement(doc, childNode, "City", false);
    AppendElement(doc, childNode, "State", false);
    AppendElement(doc, childNode, "ZipCode", false);

    childNode = AppendElement(doc, node, "Job",true);
    AppendElement(doc, childNode, "Title", false);
    AppendElement(doc, childNode, "Description", false);

    doc.WriteContentTo(tw);
  }

  private XmlNode AppendElement(XmlDocument doc, XmlNode parent,
                           string name, bool containerElement) {
    XmlNode child = doc.CreateNode(XmlNodeType.Element,name,"");
    if (!containerElement) {
      child.AppendChild(doc.CreateTextNode(
                    (string)attributes[name]));
    }
    parent.AppendChild(child);
    return child;
  }
  private void AppendAttribute(XmlDocument doc, XmlNode parent,
                                                 string name) {
    XmlAttribute child = doc.CreateAttribute(name);
    child.Value = (string)attributes[name];
    parent.Attributes.Append(child);
  }

  public static void Main(string[] args) {
    Person p = new Person();
    p.SetFirstName("Jack");
    p.SetID("jnicholson");
    p.SetSSN("123456789");
    p.SetLastName("Nicholson");
    p.SetStreet("101 Acting Blvd");
    p.SetCity("Beverly Hills");
    p.SetState("CA");
    p.SetZip("90210");
    p.SetTitle("Actor");
    p.SetDescription("Acted as Colonel Nathan
                         Jessop in A Few Good Men");
    p.ToXml();
  }
}

The output of Listing 20.10 is as follows:

<Person id="jnicholson" ssn="123456789">
  <Name>
    <LastName>Nicholson</LastName>
    <FirstName>Jack</FirstName>
  </Name>
  <Address>
    <Street>101 Acting Blvd</Street>
    <City>Beverly Hills</City>
    <State>CA</State>
    <ZipCode>90210</ZipCode>
  </Address>
  <Job>
    <Title>Actor</Title>
    <Description>Acted as Colonel
                  Nathan Jessop in A Few Good Men</Description>
  </Job>
</Person>

The ToXml() uses the AppendAttribute and AppendElement methods to abstract out the creation of elements and attributes. We start by creating the XmlTextWriter and signaling it to pretty-print the XML to Console.Out:

XmlTextWriter tw = new XmlTextWriter(Console.Out);
XmlNode childNode = null;
tw.Formatting = Formatting.Indented;

Next, we create the XmlDocument object to which all the elements will be appended. We create a Person node from the document object, which automatically appends the node to the document.

Next, we use the AppendAttribute call to set the attributes of the Person node:

XmlNode node = doc.CreateElement("Person");
AppendAttribute(doc, node, "id");

The AppendAttribute method creates an attribute, sets its value, and then appends the attribute node to the collection of attributes of the parent node.

Note that in the .NET API, an attribute is a type of node and hence must be appended to the parent node just as any other child node is:

XmlAttribute child = doc.CreateAttribute(name);
child.Value = (string)attributes[name];
parent.Attributes.Append(child);

Next, we use repetitive calls to the AppendElement method to append the element nodes to the document. The method creates an element node and sets its value from the object if it is not a container node. (A container node is an element node that is not a text node; that is, it contains other element nodes as its children.) The method returns the child node created, and thus successive calls to the AppendElement method will append new element nodes to the most recent child node:

XmlNode child = doc.CreateNode(XmlNodeType.Element,name,"");
if (!containerElement) {
  child.AppendChild(doc.CreateTextNode((string)attributes[
name]));
}
parent.AppendChild(child);
return child;

DOM and the .NET version of SAX provide a way to traverse the document by going through every tag. To search XML documents, there is a better mechanism that allows you to construct complex query strings and zoom into the desired parts of an XML document. The XPath query language (discussed next) is a popular standard for querying XML documents. The .NET Framework has built-in support for XPath query processing. The next section looks at XSLT support in .NET.

20.4.1 XPath

Searching XML documents for meaningful data can become challenging without some kind of macro language that allows you to specify what you are searching for in a terse manner. As shown in the previous examples, although the DOM API provides methods for iterating over nodes, it is not powerful enough to do advanced queries. It also requires loading of the entire document.

The XPath specification provides a standard syntax for constructing search expressions that query and extract data from XML documents. The role of XPath expressions in XML is similar to that of SQL for an RDBMS data store. A discussion of the syntax of the XPath language is beyond the scope of this chapter.

The .NET Framework bundles all XPath-related classes in the System.Xml.XPath namespace. The core class of this namespace is XPathNavigator, which has methods for running XPath expressions, extracting data, and navigating through the data. In the next few examples we use .NET classes to run useful queries on the people.xml file.

Listing 20.11 is the first XPath example. Here, we create and run a simple XPath expression and extract data from it.

Listing 20.11 Using XPath (C#)
using System;
using System.Xml;
using System.Xml.XPath;

public class Test {

  public static void Main(string[] args) {

    XPathDocument doc = new XPathDocument("c:\\people.xml");
    XPathNavigator navig = doc.CreateNavigator();
    //Get all FirstNames
    RunQuery(navig, "//FirstName/text()");
    //Get the Job title of the person with SSN = 666131313
    RunQuery(navig, "//Person[@ssn='666131313']/Job/Title");
    //Get the cities corresponding to the addresses with state
WA.
    RunQuery(navig, "//*[text()='WA']/parent::Address/City");
    //Evaluate the sum of all ids.
    EvaluateQuery(navig, "sum(//@id)");
    //Get the cities corresponding to the addresses with state
CA.
                            //Use a compiled expression
    XPathExpression cachedExpr =
        navig.Compile("//*[text()='CA']/parent::Address/City");
    RunQuery(navig, cachedExpr);

  }

  private static void RunQuery(XPathNavigator navig,
                                           string expression) {
    XPathNodeIterator itr = navig.Select(expression);
    while (itr.MoveNext()) {
      XPathNavigator currNode = itr.Current;
      Console.WriteLine(currNode.Value);
    }
  }
  private static void RunQuery(XPathNavigator navig,
                                  XPathExpression expression) {
    XPathNodeIterator itr = navig.Select(expression);
    while (itr.MoveNext()) {
      XPathNavigator currNode = itr.Current;
      Console.WriteLine(currNode.Value);
    }
  }
  private static void EvaluateQuery(XPathNavigator navig,
                                           string expression) {
    Console.WriteLine(navig.Evaluate(expression));
  }
}

The output of Listing 20.11 is as follows:

Joe
Linda
Jeremy
Joan
Attorney
Redmond
Redmond
10
Paso Robles

As mentioned earlier, the XPathNavigator class is the work horse of the XPath classes in C#. To get a handle to the navigator, you instantiate an XPathDocument object and then create the navigator object:

XPathDocument doc = new XPathDocument("c:\\people.xml");
XPathNavigator navig = doc.CreateNavigator();

The navigator object can be used to iterate through the NodeSet that is returned by the XPath expressions.

As you can see, you can get very specific with your search criteria using the XPath expression syntax. Queries either can return a node set that can be iterated over or can return an object value evaluated by the XPath language. To improve performance, you can also precompile the XPath expression and cache it for future evaluation:

XPathExpression cachedExpr =
navig.Compile("//*[text()='CA']/parent::Address/City");

Listing 20.12 shows how to iterate through all the Person nodes to see whether any of the nodes match a criterion.

Listing 20.12 Matching Nodes in XPath (C#)
using System;
using System.Xml;
using System.Xml.XPath;

public class Test {

  public static void Main(string[] args) {

    XPathDocument doc = new XPathDocument("c:\\people.xml");
    XPathNavigator nav = doc.CreateNavigator();
    XPathNodeIterator ni = nav.SelectDescendants("Person",
                                                    "", false);
    XPathExpression expr = nav.Compile("Person[@id='2']");
    while (ni.MoveNext()) {
      XPathNavigator nav2 = ni.Current.Clone();
      if (nav2.Matches(expr)){
        nav2.MoveToFirstChild();
        Console.WriteLine(nav2.Value);
      }
    }
  }
}

Here is the output of Listing 20.12:

LindaSue

It is sometimes necessary to transform a document from one form to another. For example, in a workflow system made up of different components that communicate with each other through XML, it is necessary to transform the output XML of one component into the input XML of another component. The XSLT language is a popular tool for transforming XML documents into other formats, not necessarily limited to XML. The next section looks at XSLT support in .NET.

20.4.2 XSLT

The .NET XML classes also provide support for XSLT transformation. The XslTransform class manages XSLT transformations in the new framework. XslTransform lives in the System.Xml.Xsl namespace, and it uses XmlNavigator during the transformation process.

As with all XSLT processors, XslTransform accepts as input an XML document, an XSLT document, and some optional parameters. It can produce any type of text-based output (XML, HTML, and so on).

To perform a transformation using XslTransform, you first create an XslTransform object and then call Load to load it with the desired XSLT document. Then you create an XmlNavigator object and initialize it with the source XML document that you want to transform. Finally, you call Transform to begin the process.

Listings 20.13 and 20.14 show a sample XSLT transformation. The sample XSL file is stored in people.xsl and is shown in Listing 20.13.

Listing 20.13 The People.xsl File
<?xml version="1.0"?>
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>

<xsl:template match="/">
<html>
      <xsl:apply-templates/>
</html>
</xsl:template>
<xsl:template match="People">
  <table>
    <xsl:apply-templates select="Person" />
  </table>
</xsl:template>
<xsl:template match="Person">
  <tr>
<xsl:apply-templates select="Name" />
  </tr>
</xsl:template>

<xsl:template match="Name">
<td>
<xsl:value-of select="LastName" />,<xsl:value-of
select="FirstName" />
</td>
</xsl:template>

</xsl:stylesheet>

Listing 20.14 takes the people.xml file as input and runs it through the people.xsl file, transforming it to HTML output.

Listing 20.14 Using XSLT Transformation (C#)
using System;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;

public class Test {

  public static void Main(string[] args) {

    //Create a new XslTransform object.
    XslTransform trans = new XslTransform();
    //Load the stylesheet.
    trans.Load("c:\\people.xslt");

    XPathDocument doc = new XPathDocument("c:\\people.xml");
    XmlWriter tw = new XmlTextWriter(Console.Out);
    trans.Transform(doc,null,tw);
  }

}

The output of Listing 20.14 is as follows:

<html><table><tr><td>Suits,Joe</td></tr><tr><td>Sue,Linda</td>
</tr><tr><td>Boards,Jeremy</td></tr><tr><td>Page,Joan</td></tr>
</table></html>

XSLT transformation of XML documents is a powerful mechanism and is often used to separate content from the view. By merely changing the XSL file, you can render a given XML document in several different formats.


20.5 Summary

The following summarizes the .NET Framework XML classes:

  • The .NET Framework has built-in support for XML processing, something Java was lacking until Java version 1.4.

  • The .NET classes support both the DOM and the SAX APIs.

  • Unlike the Java SAX parsers, which process every tag of an XML document, the .NET SAX parser uses the flexible pull model to extract data and parses only those tags in which you are interested. You can skip sections of the documents while parsing.

  • The Java SAX parser uses the event programming paradigm; therefore, the state of the parsing process is maintained by a set of classes. The .NET SAX parser allows for simple while loop constructs for conditional parsing of the XML document.

  • You can read and write XML to any stream (file, memory) by using the subclasses of System.Xml.XmlReader and System.Xml.XmlWriter. The key subclasses of XmlReader are XmlTextReader, XmlValidatingReader, and XmlNodeReader. The key subclass of XmlWriter is XmlTextWriter.

  • The .NET DOM implementation supports all of DOM Level 1 and all of DOM Level 2 Core. The DOM tree is read and written through classes built on top of XmlNodeReader and XmlNodeWriter. The .NET DOM API is similar to the third-party JDOM API of Java (http://www.jdom.org).

  • The .NET Framework has built-in classes for evaluating and running XPath expressions and doing XSLT transformations.


Chapter 21. GUI Programming in C#

The .NET Framework contains classes for creating Windows-based applications that take full advantage of the rich user interface features available in the Microsoft Windows operating system. Using the Visual Studio .NET IDE, you can create GUI applications using the popular drag-and-drop mechanism. We begin discussion of this complex topic with a brief overview of what the two languages provide for developing GUI applications.


21.1 Choosing a GUI Library: Java or .NET?

As a programmer of GUI applications, you should ask yourself these key questions in evaluating a GUI library:

  • Does the library allow for developing modular GUIs?

  • Does it provide a toolkit that separates GUI application behavior from GUI application design and configuration?

  • Does it exploit platform-specific GUI controls? Should it?

  • How flexible is it when it comes to customizing default behavior?

Modular GUIs let you extend your applications simply by plugging the relevant GUI component in to the basic GUI framework. A good example of a modular GUI is the NetBeans Java IDE, an open source IDE for building Java applications. NetBeans is written in Java Swing. Users of this IDE can write custom components that they can plug in to the main IDE framework to customize its behavior. Not all GUI applications have to be modular.

To develop a GUI application, you create the screens and display controls and then code the interaction between them. A GUI toolkit should let you separate the assembly of GUI components from coding their interaction. The Visual Studio .NET IDE comes with a Visual Basic–like Form Designer that allows you to design your screens using a WYSIWIG editor. You can then use C# to code the interaction between the controls. You can also write code to modify the properties of the visual components. Toolkits such as Visual Studio .NET can jump-start application development by relieving the programmer of the need to code tedious design-time control settings.

Sometimes the platform on which your application runs provides a host of commonly used components that your application can use. In the Windows world, users are accustomed to a certain look and feel and behavior in screens and dialog boxes. It is useful if your GUI toolkit lets you leverage these prebuilt components with minimal configuration.

GUI toolkits should also allow you to modify the default behavior and properties. Java's Swing and Abstract Window Toolkit (AWT) libraries shine in this regard because they allow you to customize the look and feel and behavior of your application in a cross-platform manner.

Both Java Swing and AWT and the .NET GUI toolkit provide all the features we've mentioned. However, .NET support is stronger. For example, the .NET GUI toolkit allows for drag-and-drop assembly of GUI screens, something Java Swing programmers have longed for ever since the Swing-supporting JDK was released. Currently, drag-and-drop form design in Swing is provided through third-party IDEs and is not shipped with the JDK. Also, many Java Swing programmers have complained about Swing's platform-neutral approach, which does not let them exploit some Windows-specific features. For example, mouse-wheel support was added only recently in Swing.


21.2 Using the IDE to Develop a Small Application

The Visual Studio .NET IDE provides a built-in toolbox for rapidly developing GUI applications. The following sections explore the steps needed to set up a Windows application and assemble some GUI components using the IDE.

21.2.1 Step 1: Creating a New Project

Assuming you have Visual Studio .NET up and running, create a new project as shown in Figure 21.1.

Figure 21.1. Creating a New Project

Then click on the Windows Application template, as shown in Figure 21.2.

Figure 21.2. Choosing a VS.NET Windows Form Application

The IDE will take you to the screen shown in Figure 21.3. This is the Form Designer screen, where you can drag and drop controls from the toolbox. (If you don't see the Toolbox component on your screen, click on View and then click on Toolbox.)

Figure 21.3. Windows Form Designer with Toolbox

In addition to the Toolbox, Figure 21.3 shows the Solution Explorer and Properties windows. (If you don't see them on your screen, click on View and then click on Solution Explorer and Properties.)

The Properties window is like a property bag where you can change properties of any control that is on the Form Designer. Click on the form, and you will see all the properties for the form. Click on the Text property and change it to Sample GUI Application. Solution Explorer shows you a list of all the files in the project.

In the Toolbox you will find a number of GUI controls. The Data category consists of controls for developing data-driven GUIs, which are directly synchronized with the backend database. The Components category consists of common controls used by the Windows operating system. The Windows Forms category consists of controls that are used for your GUI application. We will look at the Windows Forms and Components categories.

21.2.2 Step 2: Designing the Form

Double-click on the Button control and drag and drop it in the center of the Form1 box. Repeat this action with the TextBox control.

Next, change the Name property of the TextBox control to txtFullName. To do this, right-click on the control. This will pop up a Properties box, where you can change the properties. Click on txtFullName and then click OK. Now change the Name property of the Button control to btnOK. The final designed form is shown in Figure 21.4.

Figure 21.4. The Designed Form

21.2.3 Step 3: Modifying the Form

Now we will add some code. Double-click on the button or the textbox. This will take you to Form1.cs, a file that was created for you by the IDE. Listing 21.1 shows Form1.cs.

Listing 21.1 Form1.cs As Generated by the IDE (C#)
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace GUI
{
  /// <summary>
  /// Summary description for Form1.
  /// </summary>
  public class Form1 : System.Windows.Forms.Form
  {
    private System.Windows.Forms.TextBox textBox1;
    private System.Windows.Forms.Button button1;
    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.Container components = null;

    public Form1()
    {
      //
      // Required for Windows Form Designer support
      //
      InitializeComponent();
      //

      // TODO: Add any constructor code after
        //InitializeComponent call
      //
    }

    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    protected override void Dispose( bool disposing )
    {
      if( disposing )
      {
        if (components != null)
        {
          components.Dispose();
        }
      }
      base.Dispose( disposing );
    }

    #region Windows Form Designer generated code
    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
      this.textBox1 = new System.Windows.Forms.TextBox();
      this.button1 = new System.Windows.Forms.Button();
      this.SuspendLayout();
      //
      // textBox1
      //
      this.textBox1.Location = new
        System.Drawing.Point(88, 40);
      this.textBox1.Name = "textBox1";
      this.textBox1.Size = new System.Drawing.Size(96, 20);
      this.textBox1.TabIndex = 0;
      this.textBox1.Text = "textBox1";
      //
      // button1
      //
      this.button1.Location = new
        System.Drawing.Point(96, 88);
      this.button1.Name = "button1";
      this.button1.Size = new System.Drawing.Size(72, 24);
      this.button1.TabIndex = 1;
      this.button1.Text = "OK";
      //
      // Form1
      //
      this.AutoScaleBaseSize = new
        System.Drawing.Size(5, 13);
      this.ClientSize = new
        System.Drawing.Size(272, 213);
      this.Controls.AddRange(new
        System.Windows.Forms.Control[] {
      this.button1,
      this.textBox1});
      this.Name = "Form1";
      this.Text = "Sample GUI Program";
      this.Load += new
      System.EventHandler(this.Form1_Load);
      this.ResumeLayout(false);

    }
    #endregion

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
      Application.Run(new Form1());
    }
    private void Form1_Load(object sender, System.EventArgs e)
    {

    }
  }
}

The form that you designed using the Form Designer is abstracted as a Form class in C#. The controls of the form constitute the fields of the Form class. The methods of the Form class correspond to the events that can be associated with the form and its controls.

A particular section in the Form1.cs file might be new to Java programmers: the code between the #region directives. This directive tells the IDE to collapse and hide the sections of the code, as shown in Figure 21.5. It is better not to monkey with the code within this section.

Figure 21.5. Snapshot of the Collapse and Hide Sections of the Code

Note that the names of the textbox and the class are not intuitive. When you try to change the name of the field textBox1 to txtFullName and compile, you will get errors. The reason is that you changed the name of the field in the .cs file and not through the Form Designer. The IDE cleverly hides a tiny piece of boilerplate code (which has references to the field name textBox1). To fix this, you must change the name of the field using the Form Designer.

Now let's add some code to the btnOK_click() method. Double-click on the button control, and the IDE automatically takes you to the control's onclick event. The modified code is shown in Listing 21.2.

Listing 21.2 The Modified Form1.cs (C#)
private void btnOK_Click(object sender, System.EventArgs e)
{
  if (this.txtFullName.Text.Length == 0)
  {
    MessageBox.Show(this,
        "Enter some text in the text box above");
    this.txtFullName.Focus();
  }
  else
  {
    MessageBox.Show(this, this.txtFullName.Text);
  }
}

Note the following:

  • The name of the class was changed from Form1 to MyApp.

  • The text associated with the textbox field is initialized to "" so that when the form is initially displayed, the textbox is empty.

  • The btnOK_Click() method now contains code that shows a pop-up message box containing the text entered in the textbox.

Now build the project, and run the EXE that was generated. Figure 21.6 is a snapshot of the application in action.

Figure 21.6. The Application in Action

Using the IDE GUI toolkit is fairly intuitive. Here is the overall procedure for quickly creating controls and coding them:

  1. Double-click or drag any of the controls onto the form.

  2. Right-click on the control and change any properties you want. The Properties box also shows the events that can be associated with that control. You specify the name of the method that gets called when the event is fired on that control.

  3. Add code to the relevant event methods associated with the control.

Unlike the AWT and Swing libraries—which have layout managers that take care of resizing the contained controls when the container is resized—the Windows library does not have layout managers. The position of the controls is specified in absolute terms with respect to the container. This is similar to the absolute position layout of Java. As a result, when you resize a form window, the controls inside that form window do not resize along with the form. In Section 21.5 you will learn about docking and anchoring controls with respect to the container, thereby allowing the controls to resize along with the form.

Now that you've seen how easy it is to create a simple application using the IDE GUI toolkit, you are encouraged to play with the Form Designer as much as possible and discover the different pathways for editing code and designing screens.


21.3 Windows Forms, Component Controls, and Their Associated .NET Classes

As shown in Table 21.1, four namespaces contain all the GUI-related classes.

For our purposes, this chapter explores only those classes in the System.Windows.Forms namespace. Classes in this namespace can be categorized as controls, components, and common dialog boxes. Controls are GUI widgets that display information to the user. They handle user input through the keyboard and pointing devices. They also handle message routing and security. Controls have associated properties and events and are represented by classes that inherit from the System.Windows.Form.Control class.

Table 21.2 shows a subset of the controls in the Visual Studio .NET default toolbox, along with their associated .NET classes. The Java Swing equivalents of these classes are given for easy reference.

The component classes are helper classes that are used to model GUI components typical of a Windows application. Component classes typically inherit from the System.ComponentModel.Component class. Table 21.3 shows the components and their associated .NET classes.

Table 21.1. .NET GUI Namespaces

Namespace

Description

System.Windows.Forms

Classes in this namespace can be categorized as controls, components, and dialog boxes. Controls are GUI widgets (such as textboxes and check-boxes); MDI or SDI forms; or custom user controls. Components are classes used in conjunction with controls to build rich Windows applications (such as ToolTips, Windows help, and the menu framework). Common dialog boxes model the commonly encountered Windows pop-up dialog boxes that are displayed when users do tasks such as open or save a file, select a font, or print.

System.Windows.Forms.ComponentModel, System.Windows.Forms.Design, System.Windows.Forms.PropertyGridInternal

These namespaces contain classes that can be used to extend design-time support for Windows Forms.

System.Drawing

Classes in this namespace provide access to GDI+ basic graphics functionality.

Common dialog boxes represent those components that are common to the Windows operating system and that can be leveraged in a C# application. The classes of these components inherit from the System.Windows.Forms.CommonDialog class. Table 21.4 shows the dialog boxes and their .NET classes.

Associated with every control, component, and dialog box class are properties and events. Properties can be set at design time or dynamically. You specify events by indicating the delegate that will be used upon the execution of the event. For example, a form control has an associated Load event. This event is fired whenever the form is loaded, much like the onload event of the HTML body tag. Using the .NET IDE or dynamically, you can specify the delegate that is associated with this Load event. You specify the event delegate as follows:

this.Load += new System.EventHandler(this.
  PersonDetailForm_Load);

Table 21.2. .NET GUI Controls That Inherit from System.Forms.Form.Control with Their Java Swing Equivalents

Control

.NET Class

Java Swing

Form

System.Windows.Forms.Form

JFrame

Label

System.Windows.Forms.Label

JLabel

LinkLabel

System.Windows.Forms.LinkLabel

JButton

Button

System.Windows.Forms.Button

JButton

TextBox

System.Windows.Forms.TextBox

JTextArea

CheckBox

System.Windows.Forms.CheckBox

JCheckBox

ComboBox

System.Windows.Forms.ComboBox

JComboBox

RadioButton

System.Windows.Forms.RadioButton

JRadioButton

GroupBox

System.Windows.Forms.GroupBox

PictureBox

System.Windows.Forms.PictureBox

ImageIcon

Panel

System.Windows.Forms.Panel

JPanel

DataGrid

System.Windows.Forms.DataGrid

ListBox

System.Windows.Forms.ListBox

JList

CheckedListBox

System.Windows.Forms.CheckedListBox

ComboBox

System.Windows.Forms.ComboBox

ListView

System.Windows.Forms.ListView

JTable

TreeView

System.Windows.Forms.TreeView

JTree

TabControl

System.Windows.Forms.TabControl

JTabbedPane

DateTimePicker

System.Windows.Forms.DateTimePicker

MonthCalendar

System.Windows.Forms.MonthCalendar

HscrollBar

System.Windows.Forms.HscrollBar

JScrollBar

VscrollBar

System.Windows.Forms.VscrollBar

JScrollBar

Splitter

System.Windows.Forms.Splitter

DomainUpDown

System.Windows.Forms.DomainUpDown

JSpinner

NumericUpDown

System.Windows.Forms.NumericUpDown

TrackBar

System.Windows.Forms.TrackBar

JSlider

ProgressBar

System.Windows.Forms.ProgressBar

RichTextBox

System.Windows.Forms.RichTextBox

JTextPane

StatusBar

System.Windows.Forms.StatusBar

JProgressBar

PrintPreviewControl

System.Windows.Forms.PrintPreviewControl

Here, Load is the name of the event, and PersonDetailForm_Load is the method called when the Load event occurs.

This event-driven approach is similar to Java's AWT event listener model. The Java Swing API, however, adopts a model view controller (MVC) paradigm, in which there is a clear separation of responsibilities.

Table 21.3. .NET GUI Components That Inherit from System.ComponentModel.Component and Their Java Swing Equivalents

Control

.NET Class

Java Swing

MainMenu

System.Windows.Forms.MainMenu

JMenuBar

Timer

System.Windows.Forms.Timer

Timer

MenuItem

System.Windows.Forms.MenuItem

JMenuItem

ImageList

System.Windows.Forms.ImageList

HelpProvider

System.Windows.Forms.HelpProvider

ToolTip

System.Windows.Forms.ToolTip

JToolTip

ContextMenu

System.Windows.Forms.ContextMenu

JPopupMenu

NotifyIcon

System.Windows.Forms.NotifyIcon

ErrorProvider

System.Windows.Forms.ErrorProvider

Table 21.4. .NET GUI Dialog Components That Inherit from System.Windows.Forms.CommonDialog and Their Java Swing Equivalents

Control

.NET Class

Java Swing

OpenFileDialog

System.Windows.Forms.OpenFileDialog

JFileChooser

SaveFileDialog

System.Windows.Forms.SaveFileDialog

JFileChooser

FontDialog

System.Windows.Forms.FontDialog

ColorDialog

System.Windows.Forms.ColorDialog

JColorChooser

PrintDialog

System.Windows.Forms.PrintDialog

PrintPreviewDialog

System.Windows.Forms.PrintPreviewDialog

PageSetupDialog

System.Windows.Forms.PageSetupDialog

This modularity lets you mix and match a single model with multiple views. The .NET GUI paradigm is equivalent to that of Visual Basic's form, properties, and event model.


21.4 Putting It All Together

In this section we will demonstrate a simple GUI front end. We will use the .NET IDE to assemble the GUI controls, sparing ourselves the tedium of specifying their design-time display properties. We will then add code to the relevant pieces to make these controls interactive. Figure 21.7 shows a snapshot of the application.

Figure 21.7. The People Browser

21.4.1 What the Application Does

The application allows the user to open and load an XML file (of a specific format) that contains data about Person objects. It then displays the parsed data in GUI controls for easy browsing.

21.4.2 Source Files of the Application

To see a list of the source files making up your application, click on View and then click on Solutions Explorer in the .NET IDE. For simplicity we have adopted the Java style of specifying only one class per source file and naming the source file the same as the class file. The classes that make up the application are listed in Table 21.5.

Table 21.5. Classes in the Sample GUI Application

Source File

Description

Person.cs

Models the Person class. The Person object consists of a few properties and a list of addresses and contact info objects. A Person object is stored as a <Person> tag in the XML file.

Address.cs

Models the Address class. The Address object consists of a few properties. An address object is stored as an <Address> tag in the XML file.

ContactInfo.cs

Models a person's phone number or e-mail address. A Person object maintains the list of phone numbers and e-mail addresses. Although in the GUI we differentiate between phone and e-mail, the data structure used to store the two is the ContactInfo object.

MainForm.cs

This is the main window of the application. The main form divides the window into the tree of persons (in the left side of the window) and the right side panel, which consists of the PersonDetailForm.

PersonDetailForm.cs

This form shows the details of a Person object. The details are further split into two horizontal panels. The upper panel shows the last-name, first-name, username, password, and sex of the person, and the bottom panel shows a tab control of the addresses, e-mails, and phone information of the person.

PersonNode.cs

This is a wrapper class for the Person class and represents a node of the Person tree shown on the main window. By having this class, we avoid adding the Person object directly to the tree and thereby prevent having the Person class (a non-GUI class) inherit from TreeNode (a GUI class).

Table 21.6. Controls in the Sample GUI Application

Control ID

Control

A

System.Windows.Forms.Form

B

System.Windows.Forms.Panel

C

System.Windows.Forms.TreeView

D

System.Windows.Forms.ListView

E

System.Windows.Forms.TextBox

F

System.Windows.Forms.RadioButton

G

System.Windows.Forms.TabControl

H

System.Windows.Forms.MainMenu

I

System.Windows.Forms.OpenFileDialog

J

System.Windows.Forms.GroupBox

The entire application's GUI components are defined in the MainForm and PersonDetailForm classes. The rest of the classes manage the interaction between the two form classes. For a clean and maintainable design, it is imperative to separate the GUI classes from the non-GUI classes and to minimize the coupling between the two types of classes. By having first-class objects such as Person, Address, and ContactInfo, you avoid regurgitating that information in every form you design.

21.4.3 The Application Controls

Table 21.6 lists the controls used in the application. The properties and the event delegates of the controls are specified at design time.

MainForm.cs

The contents of MainForm.cs are shown in Listing 21.3.

Listing 21.3 MainForm.cs (C#)
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Diagnostics;
using System.Data;

namespace Sample {

  public class MainForm : System.Windows.Forms.Form {
    private System.Windows.Forms.Panel DetailPanel;
    private System.Windows.Forms.TreeView tree;
    private System.Windows.Forms.MainMenu mainMenu;
    private System.Windows.Forms.MenuItem menuItem1;
    private System.Windows.Forms.MenuItem menuItem6;
    private System.Windows.Forms.MenuItem openItem;
    private System.Windows.Forms.MenuItem exitItem;
    private System.Windows.Forms.OpenFileDialog openFileDialog;
    private System.Windows.Forms.MenuItem menuItem2;
    private System.Windows.Forms.MenuItem aboutItem;
    private System.ComponentModel.Container components = null;

    public MainForm() {
      InitializeComponent();
    }

    //Entry point
    [STAThread]
    static void Main() {
      Application.Run(new MainForm());
    }

    //Fills up the tree with Person nodes.
    private void FillTree(ArrayList people) {
      tree.BeginUpdate();
      tree.Nodes.Clear();
      foreach (Person p in people) {
        tree.Nodes.Add(new PersonNode(p));
        tree.EndUpdate();
    }

    //Fired when a node is selected on the tree
    private void TreeSelection(object sender,
        System.Windows.Forms.TreeViewEventArgs e) {
      TreeViewAction action = e.Action;
      PersonNode pNode = e.Node as PersonNode;
      //If a person node is clicked
        //show person details on right side
      DetailPanel.Controls.Clear();
      PersonDetailForm f = new PersonDetailForm(pNode.Person);
      DetailPanel.Controls.Add(f.GetMasterPanel());
    }
    //Fired when the user quits the application.
    private void exitItem_Click(object sender,
           System.EventArgs e) {
      Process.GetCurrentProcess().Kill();
    }
    //Fired when user opens a file dialog box for file selection.
    private void file_open(object sender, System.EventArgs e) {
      DialogResult result = openFileDialog.ShowDialog();
      if (result == DialogResult.OK) {
        String file = openFileDialog.FileName;
        ArrayList people = Person.GetPeople(file);
        FillTree(people);
      }
    }

    //Fired when the About menu item is clicked.
    private void menuItem3_Click(object sender,
           System.EventArgs e) {
      MessageBox.Show(this, "A minimal person browser",
           "People Browser", MessageBoxButtons.OK,
             MessageBoxIcon.Information);
    }
  }
}

The MainForm class models the main window of the application. Figure 21.7 indicates that the main window is split logically into the tree on the left side (defined as the field tree) and a detail panel on the right side (defined as DetailPanel). The tree consists of Person nodes that, when clicked, show details of the Person in the detail panel.

Listing 21.3 does not show the IDE auto-generated code. The entry point of the application is specified by the following lines:

[STAThread]
static void Main() {
  Application.Run(new MainForm());
}

After the application starts, the main thread instantiates a MainForm. The constructor of the MainForm class initializes the form controls. The application is then driven by user actions. If a user clicks on the menu to open a file, the following code is executed:

private void file_open(object sender, System.EventArgs e) {
  DialogResult result = openFileDialog.ShowDialog();
  if (result == DialogResult.OK) {
    String file = openFileDialog.FileName;
    ArrayList people = Person.GetPeople(file);
    FillTree(people);
  }
}

This pops up the standard Windows dialog box for opening a file. When the user selects a file, the static method Person.GetPeople() parses the XML to create a list of Person objects. As the name suggests, the FillTree() method fills the tree with PersonNode objects.

Note that we add the PersonNode objects instead of Person objects to the tree. Had we added Person objects directly to the tree, they would have to inherit from the TreeNode class. Because you can inherit from only one class in C#, by using a PersonNode object we keep the view separate from the model Person and allow the possibility of the Person object inheriting from some other class in the future.

After the tree is filled, you an click on any of the nodes. The following code is invoked when a tree node is selected:

private void TreeSelection(object sender,
       System.Windows.Forms.TreeViewEventArgs e) {
  PersonNode pNode = e.Node as PersonNode;
  DetailPanel.Controls.Clear();
  PersonDetailForm f = new PersonDetailForm(pNode.Person);
  DetailPanel.Controls.Add(f.GetMasterPanel());
}

The CLR packages the signal sent by a user clicking on a tree node into the System.Windows.Forms.TreeViewEventArgs object. The Node attribute of this object indicates the node that was clicked on. When a node is clicked on, we first clear the controls of the right side DetailPanel, and then we instantiate a PersonDetailForm with the Person object clicked on through the node. Because you cannot add a form to a panel, we must add the master panel of the PersonDetailForm to the controls of the DetailPanel. This takes care of updating the right side of the window with details of the person chosen on the tree.

PersonDetailForm.cs

The contents of PersonDetailForm.cs are shown in Listing 21.4.

Listing 21.4 PersonDetailForm.cs
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;

namespace Sample {
  public class PersonDetailForm : System.Windows.Forms.Form {

    private System.ComponentModel.Container components = null;
    private System.Windows.Forms.GroupBox MasterPanel;
    private System.Windows.Forms.TabControl Details;
    private System.Windows.Forms.TabPage addresses;
    private System.Windows.Forms.TabPage phones;
    private System.Windows.Forms.Panel TopPanel;
    private System.Windows.Forms.TextBox firstName;
    private System.Windows.Forms.TextBox lastName;
    private System.Windows.Forms.TextBox userName;
    private System.Windows.Forms.TextBox password;
    private System.Windows.Forms.Label label1;
    private System.Windows.Forms.Label label2;
    private System.Windows.Forms.Label label3;
    private System.Windows.Forms.Label label4;
    private System.Windows.Forms.GroupBox sex;
    private System.Windows.Forms.RadioButton radioButton1;
    private System.Windows.Forms.RadioButton radioButton2;
    private System.Windows.Forms.TabPage emails;
    private System.Windows.Forms.ListView phoneview;
    private System.Windows.Forms.ListView emailview;
    private System.Windows.Forms.ListView addressview;

    public PersonDetailForm(Person p) {
         InitializeComponent();
      firstName.Text = p.FirstName;
      lastName.Text = p.LastName;
      userName.Text = p.UserName;
      password.Text = p.Password;
      LoadAddressesInView(p);
      LoadContactInfoInView(p, phoneview, p.GetPhones());
      LoadContactInfoInView(p, emailview, p.GetEmails());
    }

    public Control GetMasterPanel() {
        return MasterPanel;
    }

    //Load the phone numbers & e-mails into the view components
    private void LoadContactInfoInView(Person person, ListView
                                                          view,
           ArrayList infos) {
      view.View = View.Details;
      view.Columns.Add("Contact", -2, HorizontalAlignment.
Left);
      view.Columns.Add("Type", -2, HorizontalAlignment.Left);
      view.Columns.Add("Description", -2, HorizontalAlignment.
Left);
      ListViewItem[] items = new ListViewItem[infos.Count];
      for(int i = 0; i < infos.Count; ++i) {
        ContactInfo a = (ContactInfo) infos[i];
        ListViewItem item = new ListViewItem(a.Contact, i);
        item.SubItems.Add(a.Type);
        item.SubItems.Add(a.Description);
          items[i] = item;
      }
      view.Items.AddRange(items);
    }

    //Load the addresses into the view components
    private void LoadAddressesInView(Person person) {
      ArrayList addresses = person.GetAddresses();
      addressview.View = View.Details;
      addressview.Columns.Add("Street", -2,
          HorizontalAlignment.Left);
      addressview.Columns.Add("City", -2,
          HorizontalAlignment.Left);
      addressview.Columns.Add("State", -2,
          HorizontalAlignment.Left);
      addressview.Columns.Add("Zip", -2,
          HorizontalAlignment.Left);
      addressview.Columns.Add("Country", -2,
          HorizontalAlignment.Left);
      ListViewItem[] items = new ListViewItem[addresses.
Count];
      for(int i = 0; i < addresses.Count; ++i) {
        Address a = (Address) addresses[i];
        ListViewItem item = new
            ListViewItem(a.Street.ToString(), i);
        item.SubItems.Add(a.City);
        item.SubItems.Add(a.State);
        item.SubItems.Add(a.Zip);
        item.SubItems.Add(a.Country);
        items[i] = item;
      }
      addressview.Items.AddRange(items);
    }
  }
}

The PersonDetailForm class models the right side of the main window, which shows the details of the chosen person. The form consists of one panel (the MasterPanel field) inside that holds all the components. The main panel contains the top panel (the TopPanel field), which in turn contains the person details such as the lastname, firstname, sex, username, and password. The bottom half of the MasterPanel is made up of a tab control (the Details field). The tab control consists of three tab pages: Address(es) (the addresses field), Phone(s) (the phones field), and Email(s) (the emails field). Each of the tab pages contains list views (the addressview field, phoneview field, and listview field).

Listing 21.4 does not show the IDE auto-generated code. The entry point of this form is the form constructor. The constructor takes a Person object whose attributes are used to initialize the form GUI controls:

public PersonDetailForm(Person p) {
  InitializeComponent();
  firstName.Text = p.FirstName;
  lastName.Text = p.LastName;
  userName.Text = p.UserName;
  password.Text = p.Password;
  LoadAddressesInView (p);
  LoadContactInfoInView(p, phoneview, p.GetPhones());
  LoadContactInfoInView (p, emailview, p.GetEmails());
}

The simple properties of a Person object are used to populate the textboxes and radio buttons of the TopPanel. The list views at the bottom are filled in the LoadAddressesInView and LoadContactInfoInView methods. The list views are dynamically defined and filled. The default behavior of the tab controls gives us access to the list views without the need to write any code. The code listings for the non-GUI classes (Person, ContactInfo, Address, and PersonNode) are shown in Listings 21.5, 21.6, 21.7, and 21.8, respectively.

The contents of Person.cs are shown in Listing 21.5.

Listing 21.5 Person.cs
using System;
using System.Xml;
using System.Collections;

public class Person {

  private int id;
  private string lastName;
  private string firstName;
  private string userName;
  private string password;
  private string sex;

  public int ID{
    get { return id; }
    set { id = value; }
  }
  public string LastName {
    get { return lastName; }
    set { lastName = value; }
  }
  public string FirstName {
    get { return firstName; }
    set { firstName = value; }
  }
  public string UserName {
    get { return userName; }
    set { userName = value; }
  }
  public string Password {
    get { return password; }
    set { password = value; }
  }
  public string Sex {
    get { return sex; }
    set { sex = value; }
  }

  private ArrayList addresses = new ArrayList();
  public void Add(Address address) {
    addresses.Add(address);
  }
  public ArrayList GetAddresses() {
    return addresses;
  }
  public void Remove(Address address) {
    addresses.Remove(address);
  }
  ArrayList phones = new ArrayList();
  public void AddPhone(ContactInfo phone) {
    phones.Add(phone);
  }
  public ArrayList GetPhones() {
    return phones;
  }
  public void RemovePhone(ContactInfo phone) {
    phones.Remove(phone);
  }
  ArrayList emails = new ArrayList();
  public void AddEmail(ContactInfo email) {
    emails.Add(email);
  }
  public ArrayList GetEmails() {
    return emails;
  }
  public void RemoveEmail(ContactInfo email) {
    emails.Remove(email);
  }

  public override bool Equals(object o){
    Person p = o as Person;
    if (p == null) return false;
    return (p.ID == this.ID);
  }
  public override string ToString(){
    return userName;
  }

  public static ArrayList GetPeople(string filename) {
    ArrayList list = new ArrayList();
    try {
      XmlDocument doc = new XmlDocument();
      doc.Load(filename);
      XmlNodeList persons = doc.GetElementsByTagName
("Person");
      foreach(XmlNode node in persons) {
        Person p = new Person();
        p.FirstName = node.Attributes["firstName"].Value;
        p.LastName = node.Attributes["lastName"].Value;
        p.UserName = node.Attributes["userName"].Value;
        p.Password = node.Attributes["password"].Value;
        p.ID = Int32.Parse(node.Attributes["id"].Value);
        XmlNodeList addresses =
         node.SelectNodes("//Person[@id='"+p.ID+"']/Address");
        foreach (XmlNode address in addresses) {
          Address a = new Address();
          a.Street = address.Attributes["street"].Value;
          a.City = address.Attributes["city"].Value;
          a.State = address.Attributes["state"].Value;
          a.Zip = address.Attributes["zip"].Value;
          a.Country = address.Attributes["country"].Value;
          p.Add(a);
        }
        XmlNodeList phones =
          node.SelectNodes("//Person[@id='"+p.ID+"']/Phone");
        foreach (XmlNode phone in phones) {
          ContactInfo c = new ContactInfo();
          c.Contact = phone.Attributes["number"].Value;
          c.Type= phone.Attributes["type"].Value;
          c.Description= phone.Attributes["description"].
Value;
          p.AddPhone(c);
        }
        XmlNodeList emails =
          node.SelectNodes("//Person[@id='"+p.ID+"']/Email");
        foreach (XmlNode email in emails) {
          ContactInfo c = new ContactInfo();
          c.Contact = email.Attributes["number"].Value;
          c.Type= email.Attributes["type"].Value;
          c.Description= email.Attributes["description"].
Value;
          p.AddEmail(c);
        }
        list.Add(p);
      }
    } catch (Exception e) {
        Console.WriteLine(e.StackTrace);
      }
    return list;
  }
}

The contents of ContactInfo.cs are shown in Listing 21.6.

Listing 21.6 ContactInfo.cs
public class ContactInfo {
  string contact;
  string description;
  string type;

  public ContactInfo(){}

  public ContactInfo (string contact,
      string type, string description) {
    this.contact = contact;
    this.type = type;
    this.description = description;
  }

  public string Contact {
    set {
      contact = value;
    }
    get {
      return contact;
    }
  }
  public string Type {
    set {
      type = value;
    }
    get {
      return type;
    }
  }

  public string Description {
    set {
      description = value;
      }
      get {
        return description;
      }
  }
}

The contents of Address.cs are shown in Listing 21.7.

Listing 21.7 Address.cs
public class Address {

  private string street;
  private string zip;
  private string city;
  private string state;
  private string country = "USA";

  public string Street {
    get { return street; }
    set { street = value; }
  }
  public string City {
    get { return city; }
    set { city = value; }
  }
  public string State {
    get { return state; }
    set { state = value; }
  }
  public string Zip {
    get { return zip; }
    set { zip = value; }
  }
  public string Country {
    get { return country; }
    set { country = value; }
  }

  public override bool Equals(object o) {
    Address that = o as Address;
    if (that == null) return false;
    return (
      string.Equals(that.Street, this.Street) &&
      string.Equals(that.Zip, this.Zip) &&
      string.Equals(that.Country, this.Country) &&
      string.Equals(that.State, this.State) &&
      string.Equals(that.City, this.City)
    );
  }

  public override string ToString() {
    return "Address with zip "+this.Zip;
  }
}

The contents of PersonNode.cs are shown in Listing 21.8.

Listing 21.8 PersonNode.cs
using System.Windows.Forms;
public class PersonNode : TreeNode {

  Person p;
  public PersonNode(Person p) : base (p.ToString()) {
    this.p = p;

  }

  public Person Person {
    get {return p;}
    set {p = value;}
  }

  public override string ToString() {
    return p.ToString();
  }
  public override bool Equals(object o) {
    PersonNode pn = o as PersonNode;
    if (pn == null) return false;
    return (pn.Person.Equals(p));
  }
}


21.5 Anchoring and Docking

One of the key differences between the .NET GUI classes and Java AWT and Swing classes is the manner in which they lay out the controls inside a container object. In .NET the default positioning of the controls within the container is absolute. This means that you must specify hard numbers when deciding where exactly to put the control on the container. It also means that the control does not resize when the container is resized. In Java AWT and Swing, the positioning of the controls is relative to the container and is specified using the available layout managers. As a result, the controls resize with the container.

Without code for resizing the controls in .NET, the controls will overlap when the container is resized. The .NET Windows library, however, provides two concepts—anchoring and docking—that allow for the control to be resized automatically with the container.

21.5.1 Anchoring

When a control is anchored to the edge of the container, the distance between the edge of the control and the edge of the container remains constant when the container is resized. A control can be anchored to any combination of edges, and that causes the control to be resized as needed to maintain the distance between edges. Listing 21.9 makes this clear.

Listing 21.9 Anchoring a Control
using System;
using System.IO;
using System.Drawing;
using System.Windows.Forms;

public class TestForm : Form {

  Button noResizeButton;
  Button yesResizeButton;
  StreamWriter logger = null;

  public TestForm () {
    noResizeButton = new Button();
    noResizeButton.Text = "Not anchored";
    noResizeButton.Width = 100;
    noResizeButton.Location = new Point(30, 30);

    yesResizeButton= new Button();
    yesResizeButton.Text = "Anchored";
    yesResizeButton.Anchor = AnchorStyles.Left |
                             AnchorStyles.Right |
                             AnchorStyles.Bottom |
                             AnchorStyles.Top;
    yesResizeButton.Width = 100;
    yesResizeButton.Location = new Point(30, 80);
    yesResizeButton.Layout +=
        new LayoutEventHandler(YesLayoutEvent);
    Controls.Add(noResizeButton);
    Controls.Add(yesResizeButton);
    InitLogger();
  }

  private void YesLayoutEvent(object source,
           LayoutEventArgs args) {
    logger.WriteLine("Form.Left - yesResizeButton.Left
        ="+(this.Left-yesResizeButton.Left));
    logger.WriteLine("Form.Right - yesResizeButton.Right
        ="+(this.Right-yesResizeButton.Right));
    logger.WriteLine("Form.Top - yesResizeButton.Top
        ="+(this.Top -yesResizeButton.Top ));
    logger.WriteLine("Form.Bottom - yesResizeButton.Bottom
        ="+(this.Bottom -yesResizeButton.Bottom));
    logger.WriteLine("Form.Left - noResizeButton.Left
        ="+(this.Left-noResizeButton.Left));
    logger.WriteLine("Form.Right - noResizeButton.Right
        ="+(this.Right-noResizeButton.Right));
    logger.WriteLine("Form.Top - noResizeButton.Top
        ="+(this.Top -noResizeButton.Top ));
    logger.WriteLine("Form.Bottom - noResizeButton.Bottom
        ="+(this.Bottom -noResizeButton.Bottom));
  }

  static void Main() {
    Application.Run(new TestForm());
  }

  public void InitLogger() {
    FileStream fs = new
      FileStream("c:\\anchor.txt",FileMode.OpenOrCreate,
      FileAccess.ReadWrite, FileShare.None);
    logger = new StreamWriter(fs);
  }

}

Listing 21.9 is a simple form that contains two button controls. One button is anchored, and the other is not. The two buttons are initialized in the constructor of the form:

noResizeButton = new Button();
noResizeButton.Text = "Not anchored";
noResizeButton.Width = 100;
noResizeButton.Location = new Point(30, 30);

yesResizeButton= new Button();
yesResizeButton.Text = "Anchored";
yesResizeButton.Anchor = AnchorStyles.Left |
                         AnchorStyles.Right |
                         AnchorStyles.Bottom |
                         AnchorStyles.Top;
yesResizeButton.Width = 100;
yesResizeButton.Location = new Point(30, 80);
yesResizeButton.Layout +=
       new LayoutEventHandler(YesLayoutEvent);

Controls.Add(noResizeButton);
Controls.Add(yesResizeButton);
InitLogger();

The yesResizeButton button is anchored, and this is indicated by specifying the edges to which it is anchored. In Listing 21.9, this button is anchored to all the edges of the form. This means that the distance between the edges of this button and the form's edges should remain constant as the form is resized.

To show that this is really happening, we record the distance between the edges of the controls and the form in a text file called anchor.txt. The InitLogger method initializes the file to which this log will be written:

public void InitLogger() {
  FileStream fs = new
    FileStream("c:\\anchor.txt",FileMode.OpenOrCreate,
    FileAccess.ReadWrite, FileShare.None);
  logger = new StreamWriter(fs);
}

When the form is resized, the CLR broadcasts events that can be processed by an event handler. The event handler for Listing 21.9 is specified this way:

yesResizeButton.Layout +=
    new LayoutEventHandler(YesLayoutEvent);

In the YesLayoutEvent method, we record the distances between the edges of the two buttons and the form. When you run the application and resize the form, the application records every resize event. A sample of the generated text file is as follows:

  Form.Left - yesResizeButton.Left =58
 Form.Right - yesResizeButton.Right =258
   Form.Top - yesResizeButton.Top =8
Form.Bottom - yesResizeButton.Bottom =285

  Form.Left - noResizeButton.Left =58
 Form.Right - noResizeButton.Right =261
   Form.Top - noResizeButton.Top =58
Form.Bottom - noResizeButton.Bottom =335

  Form.Left - yesResizeButton.Left =58
 Form.Right - yesResizeButton.Right =258
   Form.Top - yesResizeButton.Top =8
Form.Bottom - yesResizeButton.Bottom =285

  Form.Left - noResizeButton.Left =58
 Form.Right - noResizeButton.Right =263
   Form.Top - noResizeButton.Top =58
Form.Bottom - noResizeButton.Bottom =334

Note that the distance for the anchored control remains the same for all resize events.

21.5.2 Docking

When a control is docked to an edge of its container, it remains in contact with the edge when the container is resized; the control is resized so that it is the same size as the docked edge. All controls have a Dock property, which accepts a value from the DockStyle enumeration. The DockStyle values are shown in Table 21.7.


21.6 Drawing with .NET GUI Classes

The drawing classes in .NET are spread throughout a number of namespaces, as listed in Table 21.8.

Table 21.7. DockStyle Enumeration

Location

Description

Top

The top edge of the control is docked to the top of the container.

Bottom

The bottom edge of the control is docked to the bottom of the container.

Left

The left edge of the control is docked to the left of the container.

Right

The right edge of the control is docked to the right of the container.

Fill

All control edges are docked to all container edges.

None

No control edges are docked.

Table 21.8. Namespaces That Contain Drawing Classes

Namespace

Description

System.Drawing

This namespace provides access to GDI+ basic graphics functionality. The Graphics class provides methods for drawing to the display device. The other classes include Rectangle, Point, Brush, Pen, and many more.

System.Drawing.Drawing2D

This namespace provides advanced two-dimensional and vector graphics functionality.

System.Drawing.Imaging

This namespace provides imaging-related services.

System.Drawing.Printing

This namespace provides print-related services.

System.Drawing.Text

The classes in this namespace allow users to create and use collections of fonts.

System.Drawing.Design

This namespace provides the basic frameworks for developing extensions to the design-time UI.

The features of the .NET GDI+ API are similar to those of the Java 2D API included in the Java platform.

21.6.1 System.Drawing

The core class of the System.Drawing namespace is the System.Drawing.Graphics class, which is similar to the java.awt.Graphics and java .awt.Graphics2D classes. All the drawing operations are done on the Graphics object. To obtain a handle to the Graphics object, you can override the OnPaint event handler method of the control.

The Graphics object is obtained as the property of the PaintEventArgs object:

using System.Windows.Forms;
using System.Drawing;

public class DrawingTest : Form {

  protected void OnPaint(PaintEventArgs e) {
    Graphics g = e.Graphics;
  }
}

When a component needs to be redrawn manually, the Paint event is raised automatically by a call to the Control.RaisePaintEvent method. You can also obtain instances of the Graphics object by calling the Control.CreateGraphics method. When you have a reference to the Graphics object, you can use any of its several draw methods to render shapes on the screen. Table 21.9 lists the methods of the Graphics object.

Along with the Graphics object, you can use a number of other drawing classes and structs. These are listed in Table 21.10.

Using these classes and the Graphics object, you can commence drawing two- and three-dimensional objects on the screen.

The following code snippets indicate the basic syntax used to achieve some routine drawing tasks. These snippets can be placed in methods of your custom System.Windows.Form class. We will use the CreateGraphics() method of the Form control.

Drawing a Rectangle

Graphics g = CreateGraphics();
Rectangle rect = new Rectangle(50, 30, 100, 100);
Pen pen = Pens.Black;
g.DrawRectangle(pen, rect);

Drawing an Ellipse

Graphics g = CreateGraphics();
Rectangle rect = new Rectangle(50, 30, 100, 100);
Pen pen = Pens.Black;
g.DrawEllipse(pen, rect);

Table 21.9. The System.Drawing.Graphics Methods

Method

Description

DrawArc

Draws an arc from the specified ellipse

DrawBezier

Draws a cubic bezier curve

DrawBeziers

Draws a series of cubic bezier curves

DrawClosedCurve

Draws a closed curve based on an array of points

DrawCurve

Draws a curve based on an array of points

DrawEllipse

Draws an ellipse

DrawImage

Draws an image

DrawLine

Draws a line

DrawPath

Draws the lines and curves defined by a graphics path

DrawPie

Draws the outline of a pie section

DrawPolygon

Draws the outline of a polygon

DrawRectangle

Draws a rectangle

DrawString

Draws a string

FillEllipse

Fills the interior of an ellipse

FillPath

Fills the interior of a path

FillPie

Fills the interior of a pie section

FillPolygon

Fills the interior of a polygon

FillRectangle

Fills the interior of a rectangle

FillRectangles

Fills the interior of a series of rectangles

FillRegion

Fills a region

Drawing an Arc

Graphics g = pe.Graphics ;
Pen pn = new Pen( Color.Green);
Rectangle rect = new Rectangle(50, 30, 100, 100);
g.DrawArc( pn, rect, 12, 84 );

Drawing a Line

Graphics g = CreateGraphics();
Point pt1 = new Point(10,10);
Point pt2 = new Point(100, 100);
g.DrawLine( pn, pt1, pt2 );

Table 21.10. Commonly Used System.Drawing Classes and Structs

.NET Class/Struct

Description

Brush

Used to fill enclosed surfaces with patterns, colors, or bitmaps

Brushes

A collection of commonly defined brush objects

Pen

Used to draw lines and polygons, including rectangles, arcs, and pies

Pens

A collection of commonly defined Pen objects

Font

Describes the font used to render text

Point (structure)

Represents an ordered pair of integer x- and y-coordinates that defines a point in a two-dimensional plane

Rectangle (structure)

Stores the location and size of a rectangular region

Color (structure)

Represents an ARGB color

Size (structure)

Represents the size of a rectangular region with an ordered pair of width and height

FillPath Example

Graphics g = CreateGraphics();
g.FillRectangle(new SolidBrush(Color.White),
       ClientRectangle);
GraphicsPath path = new GraphicsPath(new Point[] {
  new Point(105, 225), new Point(190, 300),
  new Point(50, 350), new Point(20, 180), },
  new byte[] {
    (byte)PathPointType.Start,
    (byte)PathPointType.Bezier,
    (byte)PathPointType.Line,
    (byte)PathPointType.Line,
});
PathGradientBrush pgb = new PathGradientBrush(path);
pgb.SurroundColors = new Color[] {
    Color.Green,Color.Yellow,Color.Red,
    Color.Blue, Color.Orange, Color.White,
    };
g.FillPath(pgb, path);

Drawing Text and Strings

Graphics g = CreateGraphics();
Font fnt = new Font("Helvetica", 16);
g.DrawString("Hello World", fnt,
    new SolidBrush(Color.Red), 14,10);
}

Drawing Images

Graphics g = CreateGraphics();
Bitmap img = new Bitmap("myImage.jpg");
g.DrawImage(img, 0, 0);

21.6.2 Double Buffering

GDI+ causes the Graphics class to update the screen after every drawing operation, and that can cause the screen to flicker. You can eliminate this flicker by using double buffering. In this technique, you execute drawing operations to a Graphics object in memory and render the drawing to the screen in a single operation.

You enable double buffering in GDI+ by using the SetStyle method of the System.Windows.Form.Control class:

SetStyle(ControlStyles.DoubleBuffer, true);

Flickering can also occur if the contents of a Graphics class are cleared automatically before a component is asked to repaint itself. The following statement can completely eliminate flicker:

SetStyle(ControlStyles.UserPaintControlStyles.AllPaintingIn
Wm|Paint|ControlStyles.DoubleBuffer, true);

You can implement double buffering programmatically by executing drawing operations on an in-memory bitmap and then drawing that bitmap in one operation to the Graphics object:

Bitmap img = new Bitmap(Width, Height);
Graphics g = Graphics.fromImage(img);
Event.Graphics.DrawImage(img, 0, 0);
img.Dispose();

21.6.3 Sample Drawing Program

Figure 21.8 shows a snapshot of what the sample program looks like in action. The program is a trivial shape-drawing application in which a user clicks on the specific shape shown on the toolbar and starts drawing.

Figure 21.8. A Simple GDI+ Drawing Application

One feature of this application is that it allows for the rubber-band effect, which basically stretches the shape in the direction of the mouse movement. When the mouse is released, the shape takes its final form. Although the shape is not actually stretched, the visual effect is achieved by drawing the shape multiple times so that subsequent drawing of the shape undoes the previous drawing.

Listing 21.10 shows the listing of this example. To keep things focused, the entire application is written in one class.

Listing 21.10 Sample Drawing Application (C#)
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

public class DrawingTest : System.Windows.Forms.Form
{
  private enum Shape {Undefined, Rectangle, Line,
       Circle, Square, Ellipse, Doodle};

  private System.Windows.Forms.ToolBar toolBar;
  private System.Windows.Forms.ToolBarButton rectangle;
  private System.Windows.Forms.ToolBarButton line;
  private System.Windows.Forms.ToolBarButton clear;
  private System.Windows.Forms.ToolBarButton doodle;
  private System.Windows.Forms.ToolBarButton circle;
  private System.ComponentModel.Container components = null;

  private Bitmap bitmap;
  private Shape currentShape;
  private Point previousPoint, currentPoint;
  private int currentWidth, currentHeight;
  private bool dragging = false;
  private System.Windows.Forms.ToolBarButton ellipse;
  private System.Windows.Forms.ToolBarButton square;
  private Graphics gB;

  public DrawingTest() {
    InitializeComponent();
    SetStyle(ControlStyles.UserPaint|
         ControlStyles.AllPaintingInWmPaint|
         ControlStyles.DoubleBuffer, true);
  }

  protected override void Dispose( bool disposing ){
    if( disposing ) {
      if (components != null) {
        components.Dispose();
      }
    }
    base.Dispose( disposing );
  }

  [STAThread]
  static void Main() {
    Application.Run(new DrawingTest());
  }

  private void UnPushAllButtons() {
    foreach (ToolBarButton button in toolBar.Buttons) {
      button.Pushed = false;
    }
  }

  private void toolBar_ButtonClick(object sender,
      System.Windows.Forms.ToolBarButtonClickEventArgs e) {
    UnPushAllButtons();
    ToolBarButton button = e.Button;
    button.Pushed = true;
    switch (button.Text) {
    case "Rectangle":
      currentShape = Shape.Rectangle;
      break;
    case "Line":
      currentShape = Shape.Line;
      break;
    case "Doodle":
      currentShape = Shape.Doodle;
      break;
    case "Circle":
      currentShape = Shape.Circle;
      break;
    case "Ellipse":
      currentShape = Shape.Ellipse;
      break;
    case "Square":
      currentShape = Shape.Square;
      break;
    case "Clear":
      gB.Clear(this.BackColor);
      this.RaisePaintEvent(this, null);
      this.currentShape = Shape.Undefined;
      break;

    }
  }

  private void DrawingTest_MouseDown (object sender,
         System.Windows.Forms.MouseEventArgs e) {
    Point currentPoint = new Point(e.X, e.Y);
    previousPoint = currentPoint;
    dragging = true;
  }

  private void DrawingTest_MouseMove(object sender,
         System.Windows.Forms.MouseEventArgs e) {
    currentPoint = new Point(e.X, e.Y);
    CalculateWidthHeight();
    if (dragging) {
      DrawBackground();
      Pen pen = new Pen(Color.Blue);
      if (currentShape != Shape.Doodle) {
        Graphics g = CreateGraphics();
        DrawShape(pen, g);
        g.Dispose();
      } else {
        DrawShape(pen, gB);
        previousPoint = currentPoint;
      }
      pen.Dispose();
    }
  }

  private void DrawingTest_MouseUp(object sender,
         System.Windows.Forms.MouseEventArgs e) {
    dragging = false;
    CalculateWidthHeight();
    Pen pen = new Pen(Color.Blue);
    DrawShape(pen, gB);
    pen.Dispose();
  }

  private void DrawingTest_Load(object sender,
         System.EventArgs e) {
    Size size =
      SystemInformation.PrimaryMonitorMaximizedWindowSize;
    bitmap = new Bitmap(size.Width, size.Height);
    gB = Graphics.FromImage(bitmap);
    gB.Clear(this.BackColor);
  }

  private void DrawingTest_Paint(object sender,
    System.Windows.Forms.PaintEventArgs e) {
    DrawBackground();
  }

  private void CalculateWidthHeight() {
    currentWidth = currentPoint.X - previousPoint.X;
    currentHeight = currentPoint.Y - previousPoint.Y;
  }

  private void DrawBackground() {
    Graphics gx = CreateGraphics();
    gx.DrawImage(bitmap, 0, 0);
    gx.Dispose();
  }

  private void DrawShape(Pen pen, Graphics g) {
    if (currentShape == Shape.Line) {
      g.DrawLine(pen, previousPoint, currentPoint);
    }
    if (currentShape == Shape.Doodle) {
      g.DrawLine(pen, previousPoint, currentPoint);
    }
    if (currentShape == Shape.Rectangle) {
      g.DrawRectangle(pen, previousPoint.X, previousPoint.Y,
          currentWidth, currentHeight);
    }
    if (currentShape == Shape.Square) {
      g.DrawRectangle(pen, previousPoint.X, previousPoint.Y,
          currentWidth, currentWidth);
    }
    if (currentShape == Shape.Circle) {
      g.DrawEllipse(pen, previousPoint.X, previousPoint.Y,
          currentWidth, currentWidth);
    }
    if (currentShape == Shape.Ellipse) {
      g.DrawEllipse(pen, previousPoint.X, previousPoint.Y,
          currentWidth, currentHeight);
    }
  }
}

The entry point of the application is the Main method, which instantiates an object of the DrawingTest class. The constructor takes the steps necessary to avoid flickering:

SetStyle(ControlStyles.UserPaint|
ControlStyles.AllPaintingInWmPaint|
ControlStyles.DoubleBuffer, true);

The toolbar on the top consists of the shapes that can be drawn. The user draws a shape by clicking on any one of the shape buttons. Once pressed, that button stays pressed until another toolbar button is pressed. In this way, the same shape can be drawn multiple times. The Clear toolbar button clears the drawing area.

The drawing action is initiated in the toolBar_ButtonClick method, where the currentShape of the application is set. The user then uses the mouse to drag the shape. Note that as the user moves the mouse, the shape stretches in the direction of the mouse until the user releases the mouse.

The DrawingTest_MouseDown, DrawingTest_MouseUp, and DrawingTest_MouseMove methods together capture the entire drawing process. In the DrawingTest_MouseDown method we set the initial position of the click and set the dragging flag to true:

Point currentPoint = new Point(e.X, e.Y);
previousPoint = currentPoint;
dragging = true;

To achieve the effect of stretching, you would have to draw multiple shapes of the same type as the mouse moves in such a manner that the next shape drawn undoes the previous one. In the DrawingTest_MouseMove method we first obtain the current location of the mouse pointer:

currentPoint = new Point(e.X, e.Y);

Then we calculate the distance between the current location and the previously clicked point of the DrawingTest_MouseDown method:

CalculateWidthHeight();

This basically sets the width as the difference between the X- and Y-coordinates of the two points (the moving currentPoint of the DrawingTest_MouseMove method, and the fixed previousPoint of the DrawingTest_MouseDown method). If we are in dragging mode, we undo the previous drawings using the DrawBackground() method.

We then set the Pen object and draw the appropriate shape in the DrawShape(pen, g) method. The DrawShape method is where all the drawing takes place.

private void DrawShape(Pen pen, Graphics g) {
  if (currentShape == Shape.Line) {
    g.DrawLine(pen, previousPoint, currentPoint);
  }
  if (currentShape == Shape.Doodle) {
    g.DrawLine(pen, previousPoint, currentPoint);
  }
  if (currentShape == Shape.Rectangle) {
    g.DrawRectangle(pen, previousPoint.X, previousPoint.Y,
           currentWidth, currentHeight);
  }
  if (currentShape == Shape.Square) {
    g.DrawRectangle(pen, previousPoint.X, previousPoint.Y,
           currentWidth, currentWidth);
  }
  if (currentShape == Shape.Circle) {
    g.DrawEllipse(pen, previousPoint.X,
           previousPoint.Y, currentWidth, currentWidth);
  }
  if (currentShape == Shape.Ellipse) {
    g.DrawEllipse(pen, previousPoint.X,
          previousPoint.Y, currentWidth, currentHeight);
  }
}

The DrawingTest_MouseUp and DrawingTest_MouseMove methods call the DrawShape method using two different Graphics objects. The Draw ingTest_MouseUp method uses the Graphics object held in memory to render the final shape to the screen. The DrawingTest_MouseMove method uses the Graphics object obtained by the CreateGraphics method. This allows for rapid rendering of the same shape as the mouse moves.

Note that we use programmatic double buffering to create a memory-based Graphics object in the DrawingTest_Load method and then render everything using this object in the DrawingTest_Paint method.


21.7 Summary

GUI programming typically encompasses writing applications with GUI front ends, such as two- and three-dimensional drawing applications, imaging, and printing utilities.

The classes of the System.Windows.Form namespace are used for writing applications with a GUI front end consisting of .NET GUI controls. Here are the main points you should remember:

  • The .NET GUI component library closely resembles the Visual Basic world of forms and events. GUI controls are assembled on a form and are connected using events triggered by user actions or by the system. Whereas Java Swing uses the classic model view controller paradigm to separate GUI components from non-GUI components, .NET forms do not provide any logical separation between GUI and non-GUI classes. This might tempt a beginner C# GUI programmer to mix the two types of components, thereby limiting the extensibility and modularity of the GUI application.

  • The Visual Studio .NET environment comes with a Form Designer that makes it trivial to cook up control-rich screens. This is useful for configuring tedious design-time GUI settings. The JDK lacks a built-in GUI designer, and that makes it tedious to rapidly put Swing apps together.

  • The default layout mechanism for the .NET Forms library is absolute positioning of the controls on the form. This means that the controls inside a container do not resize nicely along with the container. To overcome this, .NET introduces the concept of anchoring and docking. In Java AWT and Swing, the positioning of controls inside the container is relative to the container and is done using a suite of layout managers. Therefore, the controls resize automatically with the container without any extra steps.

The .NET GDI+ library allows for complex two-dimensional drawing operations.

  • Much like java.awt.Graphics and java.awt.Graphics2D, the System.Drawing.Graphics object is used for doing all the drawing operations.

  • GDI+ has built-in support for imaging and printing, much like Java's imaging and printing packages.


Chapter 22. Reflection

This chapter introduces the classes in C#'s System.Reflection namespace. We assume that you are familiar with the general concept of reflection and are somewhat familiar with Java's reflection.

Reflection can be thought of as a "back door" way to get information about objects. In OOP, we can call a method on an object simply by knowing that the method exists on the object. Often, though, when we are dealing with objects in a generic fashion, we do not know about their methods in advance, and sometimes we don't even know the object's type. To call a method on any object without having prior knowledge of the object or its methods, we must query all the methods of the object, find the desired method, and invoke it by passing it parameters at runtime.

To accommodate this, a Java or C# object has associated metadata, or information about its data, such as its fields, its methods, and the constructors and interfaces the object implements. Reflection is the ability to query, search, and use this metadata. Because you must go through the additional step of discovering the method, field, or constructor to call, reflection is always slower than directly calling a specific object method, setting the field, or instantiating the object.

Reflection has a number of important uses:

  • Viewing and querying metadata. Many IDEs use reflection on your custom objects in order to categorize your custom methods and fields in a graphical, treelike format.

  • Dynamic invocation. This allows you to get and set fields of an object at runtime and to invoke methods on that object in a generic fashion. A lot of object-relational tools define mappings from object fields and methods to database table columns. These tools use dynamic invocation to call the appropriate methods to set and get fields on an object after its state is loaded from the database. Java programmers will note that this technique is also used in custom JSP tags.

  • Creating custom types. Java programmers may be familiar with the other two uses of reflection but may be surprised that C# allows you to programmatically access and modify the IL and programmatically create a new type or class. This would be equivalent to two Java APIs: one that allows programmatic access to byte code, and a second one to create classes and interfaces. The core JDK 1.3.1 does not have an API for accessing byte code.


22.1 The Core Reflection Classes

The core classes for reflection in C# are stored in the System.Reflection namespace. The close equivalents to those classes in Java can be found in the java.lang.reflect and java.lang packages.

System.Type is the core C# class from which all reflective discovery is done. This class is roughly equivalent to the java.lang.Class class. However, there is a difference. In Java, everything is a java.lang.Object, so java.lang.Class is the metadata of all objects. In C#, System.Type can represent any of the following types: classes, value types, arrays, pointers, interfaces, and enumerations. The C# typeof operator is somewhat equivalent to the .class operator in Java. To get metadata about the System.String class, you would use the following:

Console.WriteLine (typeof (System.String).isInterface);

This is somewhat similar to the following Java call:

System.out.println ( String.class.isInterface());

It is easy to see the members of the System.Type class if you are using Visual Studio .NET. The auto-complete feature displays a pop-up box filled with the members. Members of a type include its fields, properties, methods, constructors (which are a special kind of method), custom attributes, interfaces, and any nested types.

Listing 22.1 browses the members of the System.Type class.

Listing 22.1 Members of the System.Type Class (C#)
using System;
using System.Reflection;

class TypeTest {

  public static void Main(string[] args) {
    Type t = typeof (System.Type);
    if (!t.IsAbstract) {
      Console.WriteLine("Type has the following constructors");
      PrintConstructors(t);
    }
    PrintMethods(t);
    PrintFields(t);
    PrintProperties(t);
    PrintInterfaces(t);
    PrintAttributes(t);
  }

  private static void PrintConstructors(Type t) {
    Console.WriteLine("This should never get printed ");
  }

  private static void PrintFields(Type t) {
    Console.WriteLine(" $$$$$$ Printing
          fields of the System.Type class ");
    Console.WriteLine(" ");
    FieldInfo[] cons = t.GetFields();
    Console.WriteLine ("There are "+cons.Length+" fields ");
    Console.WriteLine(" ");
    foreach (FieldInfo con in cons) {
      Console.WriteLine ("Field name = "+con.Name);
      Console.WriteLine ("Field Type = "+con.GetType());
      Console.WriteLine ("Is Serialized =
                 "+!con.IsNotSerialized);
      Console.WriteLine ("Is Private = "+con.IsPrivate);
      Console.WriteLine ("Is Static = "+con.IsStatic);
      Console.WriteLine (" ");
    }
  }

  private static void PrintProperties(Type t) {
    Console.WriteLine(" $$$$$$ Printing properties of the
                                      System.Type class ");
    Console.WriteLine(" ");
    PropertyInfo[] cons = t.GetProperties();
    Console.WriteLine ("There are "+cons.Length+" properties ");
    Console.WriteLine(" ");
    foreach (PropertyInfo con in cons) {
      Console.WriteLine ("Property name = "+con.Name);
      Console.WriteLine ("Property Type = "+con.GetType());
      Console.WriteLine ("CanRead = "+con.CanRead);
      Console.WriteLine ("CanWrite = "+con.CanWrite);
      Console.WriteLine (" ");
    }
  }

  private static void PrintInterfaces(Type t) {
    Console.WriteLine(" $$$$$$ Printing interfaces of the
                                      System.Type class ");
    Console.WriteLine(" ");
    Type[] cons = t.GetInterfaces();
    Console.WriteLine ("There are "+cons.Length+" interfaces ");
    Console.WriteLine(" ");
    foreach (Type con in cons) {
      Console.WriteLine ("Interface name = "+con.FullName);
    }
  }

  private static void PrintAttributes(Type t) {
    Console.WriteLine(" $$$$$$ Printing attributes of the
                                      System.Type class ");
    Console.WriteLine(" ");
    object[] cons = t.GetCustomAttributes(true);
    Console.WriteLine ("There are "+cons.Length+" attributes ");
    Console.WriteLine(" ");
    foreach (object con in cons) {
      Console.WriteLine ("Attribute type = "+con.GetType());
    }
  }

  private static void PrintMethods(Type t) {
    Console.WriteLine(" $$$$$$ Printing methods of the
                                      System.Type class ");
    Console.WriteLine(" ");
    MethodInfo[] cons = t.GetMethods();
    Console.WriteLine ("There are "+cons.Length+" methods ");
    Console.WriteLine(" ");
    foreach (MethodInfo con in cons) {
      Console.WriteLine ("Method name = "+con.Name);
      Console.WriteLine ("Is Private = "+con.IsPrivate);
      Console.WriteLine ("Is Static = "+con.IsStatic);
      ParameterInfo[] pinfos = con.GetParameters();
      if (pinfos.Length > 0) Console.WriteLine ("---- Method
                                      parameters-----");
      foreach (ParameterInfo pinfo in pinfos) {
        Console.WriteLine ("Parameter position
                                      ="+pinfo.Position);
        Console.WriteLine ("Parameter name ="+pinfo.Name);
        Console.WriteLine ("Parameter type
                                      ="+pinfo.GetType());
        Console.WriteLine ("In Parameter     ="+pinfo.IsIn);
        Console.WriteLine ("Out Parameter    ="+pinfo.IsOut);
        Console.WriteLine (" ");
      }
      Console.WriteLine (" ============================ ");
      Console.WriteLine (" ");
    }
  }

}

Note that System.Type is an abstract class and therefore has no constructors. Note also that printMethods takes a Type parameter. We use the typeof operator to get the Type object associated with the System.Class class.

Because the output of Listing 22.1 is verbose, we do not list it here; instead, we show the gist of the output in Table 22.1. The table lists the number of methods, properties, fields, interfaces, and attributes implemented by the System.Type class.

In Listing 22.1, note that ConstructorInfo, PropertyInfo, FieldInfo, MethodInfo, and ParameterInfo all extend System.Reflection.MemberInfo.

Table 22.1. Metadata Information of the System.Type Class

Item

Count

Number of methods

124

Number of properties

47

Number of fields

6

Number of interfaces

2

Number of attributes

1


22.2 Browsing and Querying Members

As you have seen, with 124 methods to browse it can quickly become tedious trying to find the right method with the right signature. Unlike Java, C# provides an API that gives you methods for selecting and searching for a set of members based on certain criteria. The criteria you specify are typically member modifiers that precede the member definition; examples include static, public, and private methods, fields, and constructors.

Listing 22.2 shows how to browse and query fields and methods of any C# type. The FindMembers method of the MemberInfo class can be used to narrow the search. The parameters of this method are MemberTypes, BindingFlags, MemberFilter, and Object. Let's explore the meanings of these parameters in further detail:

  • MemberTypes is an object that indicates the type of the member to search for. These include All, Constructor, Custom, Event, Field, Method, Nestedtype, Property, and TypeInfo. We will also use the MemberTypes.Method to find a method. BindingFlags is an enumeration that controls the way searches are conducted by reflection. There are a great many BindingFlag values, including IgnoreCase, Instance, Public, Static, and so forth. The BindingFlags default member indicates no binding flag, which is what we want because we do not want to restrict the binding.

  • MemberFilter is a delegate that is used to filter the list of members in the MemberInfo array of objects. The filter we'll use is Type.FilterName, a field of the Type class used for filtering on a name.

  • Object is a string value that will be used by the filter. In this case we pass "Get*" to match only those methods that begin with the letters Get.

Listing 22.2 Browsing and Querying Fields and Methods of a Type (C#)
using System;
using System.Reflection;

class FieldTest {

  public static void Main(string[] args) {

    Type t = Type.GetType(args[0]);
    QueryFields(t);
    QueryMethods(t);
  }

  private static void QueryFields(Type t) {
    //Get Fields
    FieldInfo[] f = t.GetFields();
    Display("Get Fields ", f);
    //default fields
    f = t.GetFields(BindingFlags.Default);
    Display("Default ", f);
    //Find all public instance fields
    f = t.GetFields(BindingFlags.Public | BindingFlags.
                        Instance);
    Display("Public and Instance ", f);
    //Find all nonpublic instance fields
    f = t.GetFields(BindingFlags.Instance |
                        BindingFlags.NonPublic);
    Display("Non Public and Instance ", f);
    //Find all public static fields
    f = t.GetFields(BindingFlags.Static |BindingFlags.Public);
    Display("Static and Public ", f);
    //Find all nonpublic static fields
    f = t.GetFields(BindingFlags.Static | BindingFlags.
                        NonPublic);
    Display("Static and NonPublic ", f);
    //Find all public declared-only fields
    f = t.GetFields(BindingFlags.DeclaredOnly |
                               BindingFlags.NonPublic);
    Display("Public and DeclaredOnly ", f);
    //Find all fields
    MemberInfo[] m =
      t.FindMembers(MemberTypes.Field,
        BindingFlags.Default,Type.FilterName, "Get*");
    Display ("Found Field members", m);
  }

  private static void QueryMethods(Type t) {
    //Get Methods
    MethodInfo[] f = t.GetMethods();
    Display("Get Methods ", f);
    //default methods
    f = t.GetMethods(BindingFlags.Default);
    Display("Default ", f);
    //Find all public instance methods
    f = t.GetMethods(BindingFlags.Public | BindingFlags.
                        Instance);
    Display("Public and Instance ", f);
    //Find all nonpublic instance methods
    f = t.GetMethods(BindingFlags.Instance |
                        BindingFlags.NonPublic);
    Display("Non Public and Instance ", f);
    //Find all public static methods
    f = t.GetMethods(BindingFlags.Static | BindingFlags.
                        Public);
    Display("Static and Public ", f);
    //Find all nonpublic static methods
    f = t.GetMethods(BindingFlags.Static | BindingFlags.Non
                        Public);
    Display("Static and NonPublic ", f);
    //Find all public declared only methods
    f = t.GetMethods(BindingFlags.DeclaredOnly |
                               BindingFlags.NonPublic);
    Display("Public and DeclaredOnly ", f);
    //Find all methods
    MemberInfo[] m =
          t.FindMembers(MemberTypes.Method,
            BindingFlags.Default,Type.FilterName, "Get*");
    Display ("Found Method members", m);
  }

  private static void Display(String msg, MemberInfo[] fields) {
    Console.WriteLine ("--------");
    Console.WriteLine (msg);
    Console.WriteLine ("--------");
    foreach (MemberInfo f in fields) {
      Console.WriteLine(f.Name);
    }
  }

}

To run Listing 22.2 we pass a command line parameter:

C:\>Reflection System.Type

Here is an excerpt of the output of Listing 22.2:

--------
Get Fields
--------
FilterAttribute
FilterName
FilterNameIgnoreCase
Missing
Delimiter
EmptyTypes
...
--------
Static and NonPublic
--------
ResolveTypeRelativeTo
ResolveTypeRelativeTo
ResolveTypeRelativeToBaseTypes
--------

To print only the name, we treat the FieldInfo[] and MethodInfo[] collections using the generic MemberInfo[]. We can find out the type of the member using the MemberType property of the MemberInfo class.

Listing 22.2 deals with fetching a collection of members (fields or methods) either by browsing or calling the FindMembers method. Sometimes, though, it is necessary to zoom into a particular method. Listing 22.3 shows how we can use GetMethod to find a particular method. The same calls can be used to get to a specific field, property, or constructor of a type. Using reflection to get a particular member of a type requires knowledge of the existence of that member; otherwise, an exception will occur.

Listing 22.3 Getting Specific Methods of a Type (C#)
using System;
using System.Reflection;

class SpecificMethodTest {

  public static void Main(string[] args) {

    Type t = typeof (System.String);
    DoMethod(t);
  }

  private static void DoMethod(Type t) {
    //Getting a method using only the name.
    //This will give an exception
    //Because ToUpper is overloaded. The call below is
    //confusing as to which
    //overloaded method is to be returned.
    MethodInfo m = null;
    try {
      m = t.GetMethod("ToUpper");
    } catch (Exception e) {
      Console.WriteLine(e.Message);
    }
    //Getting a method using only the name. The name
    //is case-sensitive, so the exact name is needed.
    //The method called cannot be overloaded.
    m = t.GetMethod("Remove");
    Console.WriteLine("Default Remove method "  );
    //Getting an overloaded method using the name and the
    //the type of parameters it takes.
    Type[] ta = new Type [] {typeof (int),typeof (char)};
    m = t.GetMethod("PadLeft", ta);
    Console.WriteLine("PadLeft method with params "  );
    //Call a method that takes no parameters
    Type[] noparams = new Type [0];
    m = t.GetMethod("ToUpper", noparams);
    Console.WriteLine("ToUpper method with no params "  );
    //Call a method that takes no parameters
    m = t.GetMethod("Remove", noparams);
    Console.WriteLine("Remove method with no params
                                                  "+(m==null));
  }

}

The output of Listing 22.3 is as follows:

Ambiguous match found.
Default Remove method System.String Remove(Int32, Int32)
PadLeft method with params System.String PadLeft(Int32, Char)
ToUpper method with no params System.String ToUpper()
Remove method with no params True

The following excerpt of Listing 22.3 is particularly interesting. We know that there is only one Remove method in the System.String class, and it takes two parameters, and we are attempting to get a Remove method with no parameters.

//Call a method that takes no parameters
m = t.GetMethod("Remove", noparams);
Console.WriteLine("Remove method with no params "+(m==null));

Such a method does not exist in the System.String API, and, instead of throwing an exception, the runtime returns a "null" MethodInfo object. This can be a little confusing to Java programmers, who expect an exception to be thrown if a method is not found.

Note that in the reflection API, the search and query methods—such as GetMethod(s), GetConstructor(s), GetMember(s), GetField(s), and GetProperties—do not throw a NoSuchMemberException or a derivative; instead, they return a null object if the member is not found or defined for that type. This approach is somewhat different from that of the Java world, where a checked exception (java.lang.NoSuchMethodException or java.lang.NoSuchFieldException) gets thrown when a member (field, method, or constructor) cannot be found for a particular class. We will leave it to the language gurus to debate which is philosophically more sound: returning null or throwing an exception. Java programmers who are used to using exceptions for control flow should be aware of this subtle difference in C#.


22.3 Invoking Methods and Setting Fields and Properties

So far we have seen how to query, search, and retrieve specific type members or a collection of type members. The true power of reflection lies in invoking those methods and constructors and setting those properties and fields. Listing 22.4 shows invoke operations on the System.String class.

Listing 22.4 Invoke Operations Using Reflection (C#)
using System;
using System.Reflection;

class InvocationTest {

  public static void Main(string[] args) {

    Type t = typeof (System.String);
    CallConstructor(t);
    CallMethod(t);
    GetProperty(t);
  }

  private static void CallConstructor(Type t) {
    //Tried to get the default no-args public constructor.
    // String s = new String(char[] c);
    ConstructorInfo constructor =null;
    constructor = t.GetConstructor(new Type[] {typeof
                                                    (Char[])});
    Char[] carr = new Char[] { 's', 'u', 'g', 'a', 'r' };
    object[] args = new object[1] {carr} ;
    try {
      Console.WriteLine(constructor.Invoke(args));
    } catch (Exception e) {
      Console.WriteLine(e.StackTrace);
    }
  }

  private static void CallMethod(Type t) {
    MethodInfo method =null;
    String target = "sugarless";
    method = t.GetMethod("Remove");
    object[] args = new object[] {5,4};
    Console.WriteLine(method.Invoke(target, args));
  }

  private static void GetProperty(Type t) {
    PropertyInfo property = null;
    property = t.GetProperty("Length");
    String args = "floccinaucinihipilification";
    Console.WriteLine(property.GetValue(args, null));
  }
}

The output of Listing 22.4 is as follows:

sugar
sugar
27

To truly appreciate the power of reflection, we will write a utility that lets us query in-memory collections. The System.Collections classes (see Chapter 14) are type-agnostic, so they can be used to hold objects of any type. Using reflection, we can ask for objects from a collection meeting specific criteria. For simplicity we assume that it is a homogeneous collection (one that contains only one type of an object). Listing 22.5 shows a sample class whose objects we will store in the collection.

Listing 22.5 The Person Class (C#)
using System.Text;

public class Person {

  private string firstname;
  private string lastname;
  private int age;
  private float salary;
  private bool male;

  public Person (string fname, string lname,
             int age, float salary, bool male) {
    this.firstname = fname;
    this.lastname = lname;
    this.age = age;
    this.salary = salary;
    this.male = male;
  }

  public override string ToString() {
    StringBuilder builder = new StringBuilder();

      builder.Append(firstname+","+lastname+",
                       "+age+","+salary+","  ale);
    return builder.ToString();
  }
}

The basic idea behind the QueryList collection is to be able to query objects from it that meet specific criteria. For example, if we were to store only Person objects in the list, then we could ask for Persons meeting specific criteria (for example, all persons with firstname Jack, all persons with salary > $60000, all males under 40 years of age). Listing 22.6 shows the bulk of the QueryList class.

Listing 22.6 The QueryList Class (C#)
using System;
using System.Collections;
using System.Reflection;
using System.Text;
public class QueryList : ArrayList {

public enum Operation : int {
    OBJECT_EQUALS = 1,
    PRIMITIVE_EQUALS,
    LESS_THAN_OR_EQUAL,
    GREATER_THAN_OR_EQUAL,
    LESS_THAN,
    GREATER_THAN,
    NOT_EQUALS
  }

  public QueryList() : base() {
  }
  public QueryList(ICollection coll): base(coll) {
  }
  public QueryList(int size): base(size) {
  }

  public QueryList GetList(string field, int val,
                         Operation operation) {
    return _GetList(field, val, operation);
  }
  public QueryList GetList(string field, bool val,
                         Operation operation) {
    return _GetList(field, val, operation);
  }
  public QueryList GetList(string field, float val,
                                Operation operation) {
    return _GetList(field, val, operation);
  }
  public QueryList GetList(string field, string val,
                                Operation operation) {
    return _GetList(field, val, operation);
  }
  public QueryList GetList(string field, int val) {
    return _GetList(field, val, Operation.PRIMITIVE_EQUALS);
  }
  public QueryList GetList(string field, bool val) {
    return _GetList(field, val, Operation.OBJECT_EQUALS);
  }
  public QueryList GetList(string field, float val ) {
    return _GetList(field, val, Operation.PRIMITIVE_EQUALS);
  }
  public QueryList GetList(string field, string val) {
    return _GetList(field, val, Operation.OBJECT_EQUALS);
  }
  private QueryList _GetList(string field, object val,
                                       Operation oper) {
    QueryList list = new QueryList();
    IEnumerator myEnumerator = GetEnumerator();
    while (myEnumerator.MoveNext()) {
      object o = myEnumerator.Current;
      Type t = o.GetType();
      FieldInfo info = t.GetField(field, BindingFlags.Public |
          BindingFlags.NonPublic | BindingFlags.Instance );
      object targetval = info.GetValue(o);
      switch ((int)oper) {
        case (int)Operation.OBJECT_EQUALS: if
          (val.Equals(targetval)) list.Add(o); break;
        case (int)Operation.PRIMITIVE_EQUALS : if
          (val == targetval) list.Add(o); break;
        case (int)Operation.GREATER_THAN : if
          (!_IsBool(targetval.GetType()) &&
          _GetDouble(targetval)
          > _GetDouble(val)) list.Add(o); break;
        case (int)Operation.LESS_THAN : if
          (!_IsBool(targetval.GetType()) &&
          _GetDouble(targetval) < _GetDouble(val))list.Add(o);
                                     break;
        case (int)Operation.LESS_THAN_OR_EQUAL : if
          (!_IsBool(targetval.GetType()) &&
          _GetDouble(targetval) <=
          _GetDouble(val)) list.Add(o); break;
        case (int)Operation.GREATER_THAN_OR_EQUAL : if
          (!_IsBool(targetval.GetType()) &&
          _GetDouble(targetval) >=
          _GetDouble(val)) list.Add(o); break;
      }
    }
    return list;
  }

  private bool _IsBool(Type t) {
    return t.Equals(typeof (bool));
  }

  private double _GetDouble(object o) {
    return Double.Parse(o.ToString());
  }

public override string ToString() {
  StringBuilder sbf = new StringBuilder();
  IEnumerator myEnumerator = GetEnumerator();
  while (myEnumerator.MoveNext()) {
    sbf.Append(myEnumerator.Current.ToString()).Append("\n");
  }
  return sbf.ToString();
}
}

The class QueryList extends ArrayList. The API for this class contains overloaded methods for searching the collection. The general signature of the calls is as follows:

GetList ( String field, String value)
GetList ( String field, int value)
GetList ( String field, float value)
GetList ( String field, bool value)

Basically, we want to search for those objects in the collection whose field name "field" has the value specified in the value argument. This call can be used to search for simple criteria. The following code gets all persons with the first name of Jack:

GetList ("firstname", "jack")

There is a three-argument call that looks like this:

GetList ( String field, int value, Operation oper)
GetList ( String field, float value, Operation oper)

where Operation is a public enumeration indicating the various operations supported.

So a criterion such as all persons with salary > 60000 would be expressed as follows:

GetList ("salary", 60000, QueryList.Operation.GREATER_THAN);

Note that the GetList method returns a QueryList. This means that we can chain method calls and thereby combine criteria. So to get all males under age 40, we would first find all males and then, within that set, find those under the age 40:

GetList ("male", true).GetList ("age", 40,
QueryList.Operation.LESS_THAN);

This may not be the most efficient way to handle a query. However, the purpose of this exercise is to show how reflection can make it possible to write generic utilities.

Listing 22.7 shows the driver class that uses the QueryList object.

Listing 22.7 Driver Class for Executing the QueryList Utility (C#)
using System;
class QueryListDriver {
  public static void Main(string[] args) {

    QueryList list = new QueryList();
    list.Add( new Person("jack", "daniel",26, 60000, true));
    list.Add( new Person("bud", "weiser",33, 75000, true));
    list.Add( new Person("jack", "monroe",40, 150000, false));
    list.Add( new Person("jack", "kennedy",38, 100000, false));
    list.Add( new Person("ozzy", "ozbourne",50, 200000, true));
    list.Add( new Person("pat", "boone", 70, 40000, true));
    list.Add( new Person("bill", "gates", 45, 900000000,
                                                        true));
    list.Add( new Person("lady", "diana", 36, 80000, false));

    Console.WriteLine("All people with firstname \"jack\"");
    Console.WriteLine(list.GetList("firstname","jack"));

    Console.WriteLine("All people with salary > 60000 ");
    Console.WriteLine(list.GetList("salary",60000,
             QueryList.Operation.GREATER_THAN));
    Console.WriteLine("All males less than 40 yrs of age ");
    Console.WriteLine(list.GetList("male",true).GetList("age",
                        40, QueryList.Operation.LESS_THAN));
  }
}

The output of Listing 22.7 is as follows:

All people with firstname "jack"
jack,daniel,26,60000,True
jack,monroe,40,150000,False
jack,kennedy,38,100000,False

All people with salary > 60000
bud,weiser,33,75000,True
jack,monroe,40,150000,False
jack,kennedy,38,100000,False
ozzy,ozbourne,50,200000,True
bill,gates,45,9E+08,True
lady,diana,36,80000,False

All males less than 40 yrs of age
jack,daniel,26,60000,True
bud,weiser,33,75000,True

You can use the QueryList class when you are populating an ArrayList with objects from a database and do not want to go back to the database to get a different view of the same list or a subset of the list. For example, if the Person class were mapped to a database row, fetching all the Person objects at once and then using the QueryList class would eliminate the need to run different select queries based on the criteria being used.


22.4 Generating Dynamic IL Using Reflection Emit

In Java it is possible to generate source code dynamically and compile it to byte code. In fact, the JSP technology is based on just that principle, in which the JSP is compiled at runtime to a servlet class, which is then run by the JVM.

In C#, too, you can generate the code and compile it at runtime. However, C# goes a step further and provides an API for actually dealing with the generated IL, a capability known as reflection emit. This would be equivalent to a Java API that manipulates byte code. The classes in the C# System.Reflection.Emit namespace can be used to programmatically generate IL instructions.

To better understand what we mean by this, let's look at a simple example. Listing 22.8 is a simple class that basically generates the number 3 on the system console when run. Here we actually wrote the SimpleClass class in our IDE, compiled it, and ran it.

Listing 22.8 A Simple Class (C#)
using System;

public interface ISimple {
  int GetInt();
}

public class SimpleClass : ISimple {
  public int GetInt() {
    return 3;
  }
  public static void Main(string[] args) {
    ISimple sc = new SimpleClass();
    Console.WriteLine(sc.GetInt());
  }
}

It is possible to achieve the same result as that of Listing 22.8 by defining SimpleClass programmatically instead of writing it. Listing 22.9 shows the code for doing that.

Listing 22.9 Programmatic Generation of SimpleClass Using Reflection Emit (C#)
using System;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using System.Reflection.Emit;

public class Driver {
  public interface ISimple {
    int GetInt();
  }

  public static void Main(string[] args) {

    AssemblyName assemblyName = new AssemblyName( );
    assemblyName.Name = "GeneralAssembly";
    AssemblyBuilder newAssembly =
      Thread.GetDomain().DefineDynamicAssembly(
      assemblyName, AssemblyBuilderAccess.Run);
    ModuleBuilder newModule =
      newAssembly.DefineDynamicModule("Simple");

    TypeBuilder myType =
      newModule.DefineType(
      "SimpleClass", TypeAttributes.Public);

    myType.AddInterfaceImplementation(
      typeof(ISimple));

    Type[] paramTypes = new Type[0];
    Type returnType = typeof(int);
    MethodBuilder simpleMethod =
      myType.DefineMethod(
      "GetInt",
      MethodAttributes.Public |
      MethodAttributes.Virtual,
      returnType,
      paramTypes);
    ILGenerator generator =
      simpleMethod.GetILGenerator( );

    generator.Emit(OpCodes.Ldc_I4, 3);
    generator.Emit(OpCodes.Ret);

    MethodInfo getIntInfo =
      typeof(ISimple).GetMethod("GetInt");

    myType.DefineMethodOverride(simpleMethod, getIntInfo);
    myType.CreateType( );
    ISimple sc =
    (ISimple)newAssembly.CreateInstance("SimpleClass");
    Console.WriteLine(sc.GetInt());
  }
}

The first step in programmatically defining the class is to define the assembly and the module that will house the class:

AssemblyName assemblyName = new AssemblyName( );
assemblyName.Name = "GeneralAssembly";

An AssemblyName is an object that fully describes an assembly's unique identity. An assembly's identity consists of a simple name, a version number, a cryptographic key pair, and a supported culture.

With this object in hand, we can create a new AssemblyBuilder object. To do so, we call DefineDynamicAssembly on the current domain, which we get by calling the static GetDomain( ) method of the Thread object. The parameters to the GetDomain() method are the AssemblyName object we just created and an AssemblyBuilderAccess enumeration value (either Run, RunandSave, or Save). We use Run in this case to indicate that the assembly can be run but cannot be saved:

AssemblyBuilder newAssembly =
Thread.GetDomain().DefineDynamicAssembly(
assemblyName, AssemblyBuilderAccess.Run);

With this newly created AssemblyBuilder object, we are ready to create a ModuleBuilder object. The job of the ModuleBuilder, not surprisingly, is to build a module dynamically:

ModuleBuilder newModule =
newAssembly.DefineDynamicModule("Simple");

Next, we define the class and the interface it implements:

TypeBuilder myType = newModule.DefineType("SimpleClass",
         TypeAttributes.Public);
myType.AddInterfaceImplementation(typeof(ISimple));

Details about the one instance method on the class are then written as follows:

Type[] paramTypes = new Type[0];
Type returnType = typeof(int);
MethodBuilder simpleMethod =
  yType.DefineMethod("GetInt",MethodAttributes.Public |
    MethodAttributes.Virtual,returnType, paramTypes);

We then use the MethodBuilder object we created to get an ILGenerator object:

ILGenerator generator = simpleMethod.GetILGenerator( );

With the ILGenerator object in hand, we are ready to emit the OpCodes. These are the very OpCodes that the C# compiler would have created. First, we emit the value 3 to the stack and then return it. Now we're ready to create a MethodInfo object that will describe the method:

MethodInfo getIntInfo = typeof(ISimple).GetMethod("GetInt");

Now we must specify the implementation that will implement the method. We call DefineMethodOverride on the TypeBuilder object we created earlier, passing the MethodBuilder we created along with the MethodInfo object we just created:

myType.DefineMethodOverride(simpleMethod, getIntInfo);

After the assembly is created, we instantiate an instance of the SimpleClass class and call the method GetInt on it. The output will be the same as that of Listing 22.8.

This example illustrates how you can dynamically define classes and directly interact with the IL of the class using the reflection emit API.


22.5 Summary

C#'s reflection lets you view and query metadata, dynamically invoke methods, set fields, and call constructors on any C# type. You can also define custom types and use reflection emit to dynamically manipulate the generated IL. Java's reflection API, in contrast, does not allow you to manipulate the generated byte code.

One noteworthy difference between the Java and C# reflection APIs is that in C#, the reflection methods that search for a particular member of a type, based on its signature, return null instead of throwing an exception. This is ideologically different from the Java world, where the nonexistence of something is usually indicated by throwing a checked exception instead of returning null. Java programmers who are used to using checked exceptions for control flow should be aware of this subtle difference in C#.


Chapter 23. Assemblies, Application Configuration, and Process Management

So far in this book, you have learned how to write C# programs. As an enterprise programmer, you should know that writing application code is only one of the responsibilities of a programmer. The other responsibilities include assembling, deploying, configuring, and monitoring applications. This chapter introduces packaging and managing applications in C#. We look at assemblies, versioning, and application configuration and management—that is, process management.

In the Java world, applications are packaged not as EXE files but rather as class files that are loaded by the runtime .java executable. Many Java applications bundle class files in a .java archive file (or a .jar file). The .jar file is the preferred unit of deployment for applications and libraries in Java. Java products are usually shipped as nothing more than a group of .jar files. Several variations of the .java archive file (.jar) file are now used as units of deployment for the new breed of J2EE applications. J2EE Web applications are deployed as a Web application archive (.war) file. Similarly, EJB components are bundled in yet another variation of .jar files called enterprise application archive (.ear) files.

Java also provides an API for exploring these archive files and deployed units. The Java environment, however, does not come with a robust set of application monitoring and management tools. These tools tend to be closely tied to the operating system on which applications run, and Java's OS-neutral nature prohibits it from leveraging any OS-specific features in providing tight integration with any one platform. The JDK therefore ships with a minimal set of executables that can help you debug the performance and memory consumption of an application. Also, the API that helps in monitoring memory allocation and garbage collection is not part of the core Java API. Nor does Java have built-in support for versioning of archive files.

The .NET Framework is a different story. It provides tools and APIs to ease the task of assembling, deploying, and versioning an application. In addition, it has APIs to interact with the Windows platform directly and to control the application's state through OS process management.


23.1 The .NET Assembly

An assembly is a reusable, self-describing, versionable deployment unit for types and resources. Because it is self-describing, it allows the .NET runtime to fully understand the application and enforce dependency and versioning rules. Unlike Java—in which both a stand-alone application and a component library are deployed as a .jar file—assemblies come in four types or formats:

  1. exe. A console executable. The application must contain one entry point defined in the Main method.

  2. Library. A library (DLL) that can be used by other assemblies.

  3. Module. A nonexecutable collection of compiled code for use in other assemblies.

  4. winexe. A graphical Windows executable. The assembly must contain one entry point.

An assembly consists of the following components:

  • A manifest that contains the assembly metadata

  • One or more modules

  • Resource files, such as icons for Windows applications

Let's explore each of these in detail.

23.1.1 The Assembly Manifest

The assembly manifest consists of metadata describing the assembly and the types inside the assembly. The manifest is automatically created when the compiler creates an assembly.

A manifest consists of the following components:

  • Information about the name, version, culture, and strong name, as well as general descriptive information (company name, product name, description, and copyright). The culture information indicates the language supported by the assembly. The version information consists of the major, minor, build, and revision numbers that the runtime uses to enforce versioning policy. The strong name is a globally unique name tag given to an assembly so that it can be identified in a shared context. The general descriptive information regarding copyright, product, and company name can easily be changed by the end user (shown later).

  • A list of compiled code files that make up the assembly.

  • Information about the types exported by the assembly and references to any other assemblies that are required to support the types of this assembly.

Microsoft Intermediate Language (MSIL) code in a portable executable (PE) file will not be executed if it does not have an associated assembly manifest.

23.1.2 Modules

Often, a complex application can consist of components written in different languages. Modules are designed to hold disparate components so that they can be developed in parallel in totally different languages and later integrated into one assembly. Modules contain one or more .NET types. Modules can contain types from more than one namespace, and modules can be compiled from one or more source files.

23.1.3 Resources

Resources let you separate text and images (static content for those working with Web applications) from the application logic, thereby simplifying the maintenance of the application.


23.2 Creating an Assembly

In its simplest form, an assembly is a single file with an .exe, .dll, or .module file extension. This file contains a single module and a manifest.

Let's create an assembly of type exe. You will need two source files: HelloWorld.cs and HelloWorldDriver.cs. The contents of the source files are shown in Listings 23.1 and 23.2.

Listing 23.1 HelloWorld.cs (C#)
public class HelloWorld {
  public override string ToString() {
    return "Hello World";
  }
}
Listing 23.2 HelloWorldDriver.cs (C#)
using System;
public class HelloWorldDriver {
  public static void Main(string[] args) {
    HelloWorld hw = new HelloWorld();
    Console.WriteLine(hw.ToString());
  }
}

Next, we use the following command to create an EXE assembly:

csc /target:exe /out:MyApp.exe *.cs

We specify the type of assembly using the /target option. The /out option allows us to name the EXE file.

Note that for the EXE file to be created, the compiled classes should have an entry point defined in a Main() method (Listing 23.2). After the EXE assembly is created, you can view its properties by right-clicking on the file name. Some of the properties displayed are the general information section of the manifest discussed earlier. Note that this information can be customized so that when you right-click on the EXE file you can view product, version, and copyright information that is customized to suit your needs.

To customize this information, we edit a special file called Assembly Info.cs and compile it with the rest of our source files. Here is a sample AssemblyInfo.cs:

using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyTitle("")]
[assembly: AssemblyDescription("This is test")]
[assembly: AssemblyCompany("Acme Inc")]
[assembly: AssemblyProduct("Acme Product")]
[assembly: AssemblyCopyright("Buyer beware")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("1.0.0.1")]

Next, we run the command again:

csc /target:exe /out:MyApp.exe *.cs

Upon running MyApp.exe we get the following output:

Hello World

The MyApp.exe file that is created now contains the extra information that was specified in the AssemblyInfo.cs file. You can view this information by clicking on the Version tab of the MyApp.exe properties box.

As mentioned earlier, assemblies do not have to be of type .exe. You can create a .dll or a .module module by using the appropriate /target command line option:

csc /target:library /out:MyApp.dll *.cs
csc /target:module /out:MyApp.module *.cs

In both cases, the metadata information specified in AssemblyInfo.cs is retained in the end product (MyApp.dll or MyApp.module).

Assemblies can consist of one or more modules. This is typical in complex applications in which the modules may be developed by different teams, perhaps in different languages. To illustrate this, we will need two more source files. Listings 23.3 and 23.4 show the GoodByeWorld and GoodByeWorldDriver classes.

Listing 23.3 GoodByeWorld.cs (C#)
public class GoodByeWorld {
  public override string ToString() {
    return "Good Bye World";
  }
}
Listing 23.4 GoodByeWorldDriver.cs (C#)
using System;
public class GoodByeWorldDriver {
  public static void Main(string[] args) {
    GoodByeWorld hw = new GoodByeWorld ();
    Console.WriteLine(hw.ToString());
  }
}

Next, we compile the Hello*.cs files into a Hello.module and compile the GoodBye*.cs files into a GoodBye.module:

csc /target:module /out:GoodBye.module Good*.cs
csc /target:module /out:Hello.module Hello*.cs

Then we combine the two modules into a Greeting.exe assembly:

al /target:exe /out:Greeting.exe /main:HelloWorldDriver.Main
Hello.module GoodBye.module

The assembly linker EXE file (al.exe) is used to link the two modules into a Greeting.exe. This concept of linking binary code to create an executable is similar to linking .obj files to create .exe files in traditional C/C++ environments.

Note that both the modules have an entry point defined, and so when we construct the Greeting.exe assembly we specify which Main() method we want to use as the entry point for that EXE. Running the Greeting.exe program now results in the following output:

Hello World

Now we run the command again, this time changing the value of the /main option:

al /target:exe /out:Greeting.exe /main:GoodByeWorldDriver.Main
Hello.module GoodBye.module

Running the Greeting.exe program now results in the following output:

Good Bye World

Assemblies can therefore be single file assemblies or multiple file assemblies (consisting of modules linked to each other).

The Greeting.exe assembly is now made up of two modules: GoodBye.module and Hello.module. Each module has an entry point defined, and the Greeting.exe assembly can adopt the entry point of any one of the modules. Depending on the entry point specified at the time the Greeting.exe assembly is created, the output of Greeting.exe can be either Good Bye World or Hello World.


23.3 Programmatic Access to Assemblies

Fortunately, .NET assemblies are not just black boxes. A .NET assembly is represented by the Assembly class of the System.Reflection namespace. You can use this class to gain programmatic access to the components of an assembly. Listing 23.5 shows how to load the assemblies created earlier and explore them.

Listing 23.5 Loading and Browsing an Assembly in C#
using System;
using System.Reflection;

public class AssemblyExplore {

  public static void Main(string[] args) {

    Assembly asm = Assembly.LoadFrom("C:\\BOOK\\
           code\\Chapter 23\\Greeting.exe");
    //Prints the properties of the assembly
    PrintProperties(asm);
    //Browse the modules of the assembly
    BrowseModules(asm);
  }

  private static void PrintProperties(Assembly asm) {
    Console.WriteLine("CodeBase "+asm.CodeBase);
    Console.WriteLine("Location "+asm.Location);
    Console.WriteLine("Global Assembly Cache
           "+asm.GlobalAssemblyCache);
    Console.WriteLine("EntryPoint "+asm.EntryPoint.ToString());
    Console.WriteLine("FullName "+asm.FullName);
  }

  private static void BrowseModules(Assembly asm) {
    foreach (Module m in asm.GetModules()) {
      BrowseModule (m);
    }
  }

  private static void BrowseModule(Module m) {
    Console.WriteLine("Module "  .Name);
    foreach (Type t in m.GetTypes()) {
      Console.WriteLine("\tType "+t.FullName);
      foreach (MethodInfo method in t.GetMethods()) {
        Console.WriteLine("\t\tMethod signature
          "+method.ToString());
      }
    }
  }
}

The output of Listing 23.5 is as follows:

CodeBase file:///C:/BOOK/code/Chapter 23/Greeting.exe
Location c:\book\code\chapter 23\greeting.exe
Global Assembly Cache False
EntryPoint Void __EntryPoint(System.String[])
FullName Greeting, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null
Module greeting.exe
Module hello.module
Type HelloWorld
Method signature Int32 GetHashCode()
Method signature Boolean Equals(System.Object)
Method signature System.String ToString()
Method signature System.Type GetType()
Type HelloWorldDriver
Method signature Int32 GetHashCode()
Method signature Boolean Equals(System.Object)
Method signature System.String ToString()
Method signature Void Main(System.String[])
Method signature System.Type GetType()
Module goodbye.module
Type GoodByeWorld
Method signature Int32 GetHashCode()
Method signature Boolean Equals(System.Object)
Method signature System.String ToString()
Method signature System.Type GetType()
Type GoodByeWorldDriver
Method signature Int32 GetHashCode()
Method signature Boolean Equals(System.Object)
Method signature System.String ToString()
Method signature Void Main(System.String[])
Method signature System.Type GetType()

The output of Listing 23.5 should be self-explanatory based on what we did earlier while constructing the assembly. There are two modules that contain types. The hello.module file contains the HelloWorld and the HelloWorldDriver classes. The goodbye.module file contains the GoodByeWorld and GoodByeWorldDriver classes. The method signatures of the methods of these two classes are listed in the output generated by Listing 23.5.

Notice that HelloWorld, HelloWorldDriver, GoodByeWorld, and GoodByeWorldDriver are simple classes; so where did all these methods come from? Remember that all classes extend System.Object. What you see in the output are the System.Object methods inherited by these classes.

The Assembly class provides methods to retrieve information about the assembly. There are no methods provided to modify the assembly. Chapter 22 discusses the reflection emit mechanism, which can be used to create dynamic assemblies (see Listing 22.9).

So far, the assemblies we've created have been private assemblies—that is, specific to the application in question. Many times, applications depend on a shared assembly to function. The notion of sharing an assembly becomes more apparent when the assembly is a type library (a DLL file). Think of multiple EXE files using the same DLL (sound familiar?). A .NET shared assembly is more than just a static DLL that is referenced by the applications.

Consider a Java Web application deployed in a J2EE-compliant Web container. The container has library files that are infrastructure .jar files used by all applications deployed in that container. It makes little sense to deploy this common set of .jar files every time a new Web application is deployed. What is needed is a mechanism by which the container automatically knows to use the shared .jar file when an application makes a reference to it. Java handles this mechanism using a class-loader hierarchy. Because the application references the shared .jar file at runtime, it only makes sense that for the application to compile, it would need to reference that .jar file at compile time. The .jar file required at compile time need not be in the same location as the one required at runtime.

In .NET, a shared assembly is in many ways similar to that J2EE infrastructure .jar file. But .NET does not have the concept of class loaders or class paths, so how does the CLR know where to look when an application references a DLL it cannot find in its assembly? The Global Assembly Cache (GAC) is a directory (usually C:\Windows\assembly or C:\WINNT\assembly) where the CLR can locate such shared assemblies. Although it is not necessary to put the shared assembly in the GAC, it makes for easier deployment if shared assemblies are kept there. The .NET Framework provides a tool called the GAC tool (gacutil.exe) to install shared assemblies in the GAC.

Sharing an assembly means that the assembly should have a name that is globally unique. Such a name is called the strong name of the assembly. It consists of the name, version information, culture information, and a public key for cryptography. The AssemblyInfo.cs file can be used to modify the strong name components of an assembly. To create a public key, the .NET Framework provides a strong name tool called sn.exe, which creates a key file that can be referenced in the AssemblyInfo.cs. The following command creates a Hello.snk key file containing the cryptographic public key:

sn–k Hello.snk

Next, we create the AssemblyInfo.cs file with all the strong name components specified, as shown in boldface in Listing 23.6.

Listing 23.6 AssemblyInfo with Strong Name Components (C#)
using System.Reflection;
using System.Runtime.CompilerServices;

[assembly: AssemblyTitle("MyApp")]
[assembly: AssemblyDescription("A simple app")]
[assembly: AssemblyCompany("MyCompany.com")]
[assembly: AssemblyProduct("MyApp")]
[assembly: AssemblyCopyright("This is the property of
MyCompany.com")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyKeyFile("Hello.snk")]

If you run the following command, the Hello.dll is created with a strong name:

csc /target:library /out:Hello.dll HelloWorld.cs AssemblyInfo.cs

Now store it in the GAC using this command:

gacutil /i Hello.dll

When the DLL is installed in the GAC, an application is free to use it at runtime. But first, the application must use the DLL at compile time to compile itself. To do this, we copy the DLL from the GAC into the directory where you would compile your application. Let's compile HelloWorldDriver using this new DLL:

csc /target:exe /out:MyApp.exe HelloWorldDriver.cs
/reference:Hello.dll

Now remove Hello.dll from the local directory. When we run MyApp it should print the following as output:

Hello World

We have just created an EXE (MyApp.exe) that references a shared assembly (Hello.dll).


23.4 Versioning

In Section 23.3 you learned how to create an application that references a GAC assembly.

Sharing libraries among applications brings home the age-old problem regarding which version of the common library is being used by which application. Can applications that use two different versions of the same DLL coexist? How do you upgrade an application to use the new version of the DLL? Fortunately, .NET answers all these questions.

To illustrate versioning, we will create a new Hello.dll consisting of an AssemblyInfo.cs (Listing 23.7) with a different strong name and a different HelloWorld.cs (Listing 23.8).

Listing 23.7 Modified AssemblyInfo with Strong Name Components (C#)
using System.Reflection;
using System.Runtime.CompilerServices;

[assembly: AssemblyTitle("MyApp")]
[assembly: AssemblyDescription("A simple app")]
[assembly: AssemblyCompany("MyCompany.com")]
[assembly: AssemblyProduct("MyApp")]
[assembly: AssemblyCopyright("This is the property of
MyCompany.com")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("2.0.0.0")]
[assembly: AssemblyKeyFile("Hello.snk")]

Note that in Listing 23.7 we need only change the version number in order to change the strong name of an assembly. Next, we create a slightly different HelloWorld class.

Listing 23.8 A Slightly Different HelloWorld Class (C#)
public class HelloWorld {
  public override string ToString() {
  return "A slightly different Hello World";
  }
}

Next, we quickly create Hello.dll, register it in the GAC, and compile the HelloWorldDriver class using this new DLL.

csc /target:library /out:Hello.dll HelloWorld.cs AssemblyInfo.cs
gacutil /i Hello.dll
csc /target:exe /out:MyApp.exe HelloWorldDriver.cs
/reference:Hello.dll

Now type MyApp, and you should see the following output:

A slightly different Hello World

To list the contents of the GAC, you type gacutil /l. This will output all the shared assemblies in your environment. You will see the two versions of Hello.dll in the output:

Hello, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=cc6fce1c7117f564om=null

Hello, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=cc6fce1c7117f564om=null

Note that the version information is different for the two assemblies.

Now that we have two assemblies in the GAC, how do we force an application to use the desired version of the assembly? The .NET Framework supports assembly policy files, which are XML files that indicate how the assembly is to be upgraded. A sample policy file is shown in Listing 23.9.

Listing 23.9 A Sample .NET Policy File (C#)
<configuration>
  <runtime>
    <assemblyBinding>
      <dependentAssembly>
        <assemblyIdentity name="Hello"
          publicKeyToken="cc6fce1c7117f564"
                              culture=""/>
          <bindingRedirect oldVersion="1.0.0.0"
                        newVersion="2.0.0.0"/>
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

The new policy-based Hello.dll is then created using the following command:

al /link:policy.xml /keyfile:Hello.snk /out:policy.1.0.Hello.dll

Note that the name format of the new Hello.dll includes the word policy with a number indicating clearly that the assembly enforces a policy. The next step is to register this policy-based DLL in the GAC:

gacutil /i policy.1.0.Hello.dll


23.5 Application Configuration

Many applications rely on external static settings that they read before they do something useful. Such settings are never hardcoded in the application itself, because doing so forces a recompilation of the application whenever a simple static setting changes.

A traditional approach is to store these static settings in a static configuration file. Java programmers are probably thinking about .properties files. A Java property file is a file containing name/value pairs that can be used at runtime by the application. JDK 1.4 introduced a more structured way of storing these settings in a class called java.util.Preferences.

The .NET Framework comes with its own arsenal of classes that allow configuration of the application. Because everything is XML-based in the .NET Framework, there is no special syntax to be learned. The framework provides a rich API for reading these configuration files.

A .NET configuration file ends with the .config file extension. For the application MyApp.exe the configuration file would be MyApp.exe.config. For multiple-file assemblies, the configuration file is named after the file that contains the manifest. The CLR ensures that the configuration files are loaded automatically, and therefore no special effort is needed by the programmer. The configuration files must contain the root XML node <configuration>.

In its simplest form, custom application settings are defined in the <appSettings> section of the configuration file. The settings are defined as key-value pairs.

<configuration>
  <appSettings>
  <add key="Key1" value="Value1"/>
  <add key="Key2" value="Value2"/>
  <add key="Key3" value="Value3"/>
</appSettings>

You can also specify sections in the configuration file. Sections are used to define complex settings. There are three types of sections depending on the handler that is specified for each section: a single-tag section, a name/value section, and a dictionary section.

23.5.1 Single-Tag Section

A single-tag section allows you to define the key-value pairs as an attribute of a single XML tag:

<configuration>
  <configSections>
  <section name="Single"
    type="System.Configuration.SingleTagSectionHandler"/>
  </configSections>
  <Single Key1 = "Value1"
          Key2 = "Value2"
          Key3 = "Value3"></Single>
</configuration>

23.5.2 Name/Value Section

A name/value section allows you to define the key-value pairs in the same way as the simple settings are defined:

<configuration>
  <configSections>
  <section name="NameValue"
    type="System.Configuration.NameValueSectionHandler" />
  </configSections>
  <NameValue>
    <add key="Key1" value="Value1"/>
    <add key="Key2" value="Value2"/>
    <add key="Key3" value="Value3"/>
  </NameValue>
</configuration>

23.5.3 Dictionary Section

A dictionary section is similar to a name/value section except that a dictionary section uses a different section handler:

<configuration>
  <configSections>
    <section name=" Dictionary"
      type="System.Configuration.DictionarySectionHandler" />
  </ configSections >
  <Dictionary>
    <add key="Key1" value="Value1"/>
    <add key="Key2" value="Value2"/>
    <add key="Key3" value="Value3"/>
  </Dictionary>
</configuration>

You can even signal the configuration file parser to ignore sections by making the section handler an IgnoreSectionHandler.

<configuration>
  <configSections>
<section name="Advanced"
type="System.Configuration.IgnoreSectionHandler" />
  </configSections>
<Advanced>
<add key="Key1" value="Value1"/>
<add key="Key2" value="Value2"/>
<add key="Key3" value="Value3"/>
</Advanced>
</configuration>

23.5.4 A Sample Config File

Listings 23.10 and 23.11 show how to handle the simple application settings along with the section settings we've discussed. Listing 23.10 shows the complete MyApp.config file.

Listing 23.10 A Sample .NET Config File (C#)
<configuration>
  <configSections>
    <section name="Single"
    type="System.Configuration.SingleTagSectionHandler" />

    <section name="NameValue"
    type="System.Configuration.NameValueSectionHandler" />

    <section name="Dictionary"
    type="System.Configuration.DictionarySectionHandler" />
  </configSections>

<appSettings>
  <add key="SimpleKey1" value="SimpleValue1"/>
  <add key="SimpleKey2" value="SimpleValue2"/>
  <add key="SimpleKey3" value="SimpleValue3"/>
</appSettings>
  <Single SingleKey1 = "SingleValue1"
    SingleKey2 = "SingleValue2"
    SingleKey3 = "SingleValue3" />

  <NameValue>
  <add key="NameValueKey1" value="NameValueValue1"/>
  <add key="NameValueKey2" value="NameValueValue2"/>
  <add key="NameValueKey3" value="NameValueValue3"/>
  </NameValue>

  <Dictionary>
    <add key="DictionaryKey1" value="DictionaryValue1"/>
    <add key="DictionaryKey2" value="DictionaryValue2"/>
    <add key="DictionaryKey3" value="DictionaryValue3"/>
  </Dictionary>

</configuration>

Note that there can be only one <configSections> element of the <configuration> root, and it must be the first child of the root node. Listing 23.11 shows how the different settings of the config file are read.

Listing 23.11 A Simple C# Class to Read the Application Settings
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Configuration;

public class MyApp {

  public static void Main(string[] args) {
    //Read simple settings
    ReadSimpleSettings();
    //Read single-tag settings
    ReadSingleTagSettings();
    //Read single-tag settings
    ReadNameValueSettings();
    //Read single-tag settings
    ReadDictionarySettings();
  }

  private static void ReadSimpleSettings() {
    Console.WriteLine(
    ConfigurationSettings.AppSettings["SimpleKey1"]);
  }

  private static void ReadSingleTagSettings() {
    IDictionary dict = (IDictionary)
    ConfigurationSettings.GetConfig("Single");
    Console.WriteLine(dict["SingleKey2"]);
  }

  private static void ReadNameValueSettings() {
    NameValueCollection dict = (NameValueCollection)
    ConfigurationSettings.GetConfig("NameValue");
    Console.WriteLine(dict["NameValueKey3"]);
  }

  private static void ReadDictionarySettings() {
    Hashtable dict = (Hashtable)
    ConfigurationSettings.GetConfig("Dictionary");
    Console.WriteLine(dict["DictionaryKey1"]);
  }
}

The output of Listing 23.11 is as follows:

SimpleValue1
SingleValue2
NameValueValue3
DictionaryValue1

The code in Listing 23.11 is self-explanatory. The ConfigurationSettings class of the System.Configuration namespace contains all the methods needed to parse a config file. Note that the object returned by the GetConfig method is cast to the appropriate collection depending on the section being read.

Note also that the collections returned by the ConfigurationSettings class are read-only. You cannot use this class to modify the config file. You should do this manually.


23.6 Process Management

Traditionally, the unit of isolation for applications has been at the process level. A dedicated operating system user process is allocated to every instance of the running application, and stopping the application means killing the OS process. The .NET Framework introduces the concept of application domains, which are new, lighter units of isolation. The CLR allows a process to consist of many application domains, each independent of the others. Application domains thus create a boundary for applications to coexist within the same OS process. Hence, an interapplication method call is made within the same OS process, and that is lighter than making an interprocess method call. Applications can be stopped independently of each other, and a fault in one application does not affect the other applications in the same OS process.

The .NET application domain concept is reminiscent of Web-application isolation provided by Java-based application servers. The J2EE specification mandates that Web applications be independent of each other even though they are created in the same JVM instance. Classes loaded in one Web application are not available to another Web application. This isolation is achieved by having different class loaders for each Web application.

Associated with each application is an application domain. When you start, say, a .NET shell executable from the command line, a piece of software creates the default application domain and your application-specific domain. This piece of software is the CLR host. The CLR host is responsible for loading the CLR into the OS process, creating a logical partition within the OS process so that your application can reside in that partition. Running a .NET shell executable automatically invokes the CLR host associated with shell executables. In .NET an application domain is modeled using the System.AppDomain class.

23.6.1 Querying the Current Application Domain

Listing 23.12 shows how a shell executable can query its current application domain and get at its properties.

Listing 23.12 Current Application Domain Properties (C#)
using System;
using System.Threading;
public class Test {

  public static void Main(string[] args) {

    AppDomain domain = Thread.GetDomain();
    Console.WriteLine("Friendly name "+domain.FriendlyName);
    Console.WriteLine("Base directory "+domain.BaseDirectory);

    AppDomainSetup setup = domain.SetupInformation;
    Console.WriteLine("ApplicationBase "+setup.
                        ApplicationBase);
    Console.WriteLine("ApplicationName "+setup.
                        ApplicationName);
    Console.WriteLine("Config file "+setup.ConfigurationFile);
  }
}

The output of Listing 23.12 is as follows:

Friendly name Deployment.exe
Base directory C:\BOOK\code\Chapter 23\Deployment\bin\Debug\
ApplicationBase C:\BOOK\code\Chapter 23\Deployment\bin\Debug\
ApplicationName
Config file C:\BOOK\code\Chapter
23\Deployment\bin\Debug\Deployment.exe.config

You can obtain the application domain of the application by calling the GetDomain method of the current thread. The AppDomain class defines several properties of interest.

23.6.2 Executing an Application in a Remote Application Domain

To execute an application in another application domain, you must first create that application domain. Listing 23.13 shows how.

Listing 23.13 Executing an Assembly inside a Custom Application Domain (C#)
using System;

public class Test {

  public static void Main(string[] args) {

    AppDomainSetup info = new AppDomainSetup();
    info.ApplicationBase = System.Environment.CurrentDirectory;
    AppDomain dom = AppDomain.CreateDomain(
           "RemoteDomain", null,info);
    dom.ExecuteAssembly("c:\\book\\code\\Chapter 23\\MyApp.exe");
    AppDomain.Unload(dom);
  }
}

The output of Listing 23.13 is as follows:

A slightly different Hello World

Listing 23.13 creates a custom application domain called RemoteDomain and executes and runs a previously developed assembly (MyApp.exe). Note that in this case the application that is run is trivial, but in practice the application can be complex, and it is important to unload the assembly. There is no API for unloading an assembly directly, but you can do so by unloading the application domain in which it runs:

AppDomain.Unload(dom);

23.6.3 Invoking a Method in a Remote Application Domain

As mentioned earlier, in the absence of application domains, invoking methods of other applications typically means making an interprocess call, which is slower than making an intraprocess call. To demonstrate how to call a method of a class loaded in another application domain, we first create a simple class and create a library type assembly (DLL) out of it. Listing 23.14 shows the RemoteHello class that we will use to create the DLL.

Listing 23.14 RemoteHello.cs (C#)
using System;

public class RemoteHello : MarshalByRefObject {

  string greeting;
  public RemoteHello(string greeting) {
    this.greeting = greeting;
  }

  public void Greet() {
    Console.WriteLine("Hello "+greeting);
  }
}

MarshalByRefObject is an abstract class that enables access to objects across application domain boundaries.

Next, we compile RemoteHello.cs into RemoteHello.dll:

csc /target:library /out:RemoteHello.dll RemoteHello.cs

Now that we have the DLL, Listing 23.15 shows how to call the Greet() method by loading the DLL in a custom application domain.

Listing 23.15 RemoteHelloDriver.cs (C#)
using System;
using System.Reflection;
using System.Runtime.Remoting;

public class Test {

  public static void Main(string[] args) {

    //Set up information regarding the application domain
    AppDomainSetup info = new AppDomainSetup();
    info.ApplicationBase = System.Environment.CurrentDirectory;

    // Create an application domain with null evidence
    AppDomain dom = AppDomain.CreateDomain(
           "RemoteDomain", null,info);
    BindingFlags flags = (BindingFlags.Public |
      BindingFlags.Instance | BindingFlags.CreateInstance);
    ObjectHandle objh = dom.CreateInstance("RemoteHello",
      "RemoteHello", false, flags, null, new String[]{"Hello
      World!"}, null, null, null);
    Object obj = objh.Unwrap();
    // Cast to the actual type

    RemoteHello h = (RemoteHello)obj;
    // Invoke the method

    h.Greet();

    // Clean up by unloading the application domain
    AppDomain.Unload(dom);
  }
}

Because Listing 23.15 references the RemoteHello class, we must compile it using the following command line:

csc /r:RemoteHello.dll RemoteHelloDriver.cs

If we now run the RemoteHelloDrive.exe we will see the following output:

Hello Hello World!

Being able to execute other applications or even call methods on types defined in other applications without slowing execution is a huge positive of the .NET Framework. It allows enterprise developers to develop and deploy common enterprise components in one assembly and reuse those components in other applications.

We mentioned earlier that multiple applications can be run in a single OS process using the application domain as the unit of isolation and that you can stop one application without affecting the other. To illustrate this, we create Application1.cs (Listing 23.16) and Application2.cs (Listing 23.17) and then create the assemblies Application1.exe and Application2.exe, respectively, from the two listings.

Listing 23.16 Application1.cs (C#)
using System;
using System.Threading;

public class Test {

  static void Main(string[] args) {
    Thread t = new Thread(new ThreadStart(new Test().Run));
    t.Start();
  }

  public void Run() {
    while (true) {
      Thread.Sleep(400);
      Console.WriteLine("Running App 1");
    }
  }
}

Run the following command to create the Application1.exe assembly:

csc Application1.cs

Next, we create Application2.cs (Listing 23.17).

Listing 23.17 Application2.cs (C#)
using System;
using System.Threading;

public class Test{

  static void Main(string[] args) {
    Thread t = new Thread(new ThreadStart(new Test().Run));
    t.Start();
  }

  public void Run() {
    while (true) {
      Thread.Sleep(500);
      Console.WriteLine("Running App 2");
    }
  }
}

Run the following command to create the Application2.exe assembly:

csc Application2.cs

Now we create the source file that we will use to create two application domains and run Application1.exe and Application2.exe in these two domains. We will call this class Monitor.cs (Listing 23.18).

Listing 23.18 Monitor.cs (C#)
using System;
using System.Threading;
using System.Reflection;

public class Test {

  AppDomain dom1, dom2;

  public Test() {

    AppDomainSetup info = new AppDomainSetup();
    info.ApplicationBase = System.Environment.CurrentDirectory;

    dom1 = AppDomain.CreateDomain("RemoteDomain1", null, info);
    dom2 = AppDomain.CreateDomain("RemoteDomain2", null, info);
    dom1.ExecuteAssembly("c:\\book\\code\\Chapter23
          \\Deployment\\Application1.exe");
    dom2.ExecuteAssembly("c:\\book\\code\\Chapter23
          \\Deployment\\Application2.exe");

    public static void Main(string[] args) {
    Thread t = new Thread(new ThreadStart(new Test().Run));
    t.Start();
  }

    public void Run() {
      Thread.Sleep(1000);
      Console.WriteLine("Unloading dom1");
      AppDomain.Unload(dom1);
      Thread.Sleep(1000);
      Console.WriteLine("Unloading dom2");
      AppDomain.Unload(dom2);
        }
}

When we run Monitor.exe we get the following output:

Running App 1
Running App 2
Running App 1
Unloading dom1
Running App 2
Running App 2
Running App 2
Unloading dom2

The contents of Listing 23.18 are self-explanatory. The class instantiates a thread, and in its constructor it creates two application domains and executes Application1.exe and Application2.exe in those domains. Next, the instantiated thread sleeps for 1,000 ms and then unloads the application domain running Application1.exe. Note that after this statement, Application2.exe is the only assembly running and printing. The main thread then sleeps some more and then unloads the second application domain, thereby stopping all the applications.

System.Diagnostics

The System.Diagnostics namespace contains many useful classes that help your application code deal with the operating system. Although there are several fun Windows-specific operations that you can do using the classes in this namespace, one of the more important things, perhaps more familiar to Java programmers, is monitoring the operating system process. In Java, this is modeled using the java.lang.Process class. This class provides only read-only methods. You cannot create a process in Java using the methods of this class. In .NET the Process class of this namespace can be used to query existing process details as well as create new processes.

23.6.4 Querying Processes

Listing 23.19 shows how to query an existing process. The application repeatedly prints the details of the current process in which it runs. Because the application creates a thread, it keeps the process running, and hence you will get an unending stream of output until you kill the process.

Listing 23.19 Getting the Current .NET Process Details (C#)
using System;
using System.Threading;
using System.Diagnostics;

public class OSProcessApp {

  Process p;

  public OSProcessApp() { p = Process.GetCurrentProcess(); }

    static void Main(string[] args) {
      Thread t = new Thread(new ThreadStart(new
      OSProcessApp().Run));
      t.Start();
  }

  public void Run() {
    while (true) {
      Thread.Sleep(400);
      Console.WriteLine(p.BasePriority);
      Console.WriteLine(p.Id);
      Console.WriteLine(p.TotalProcessorTime);
      Console.WriteLine(p.UserProcessorTime);
      Console.WriteLine(p.VirtualMemorySize);
      Console.WriteLine(p.MachineName);
    }
  }
}

The output of Listing 23.19 will depend on your machine, but here is a sample excerpt:

Priority 8
Id 1380
Total processor time 00:00:00.5007200
User processor time 00:00:00.2303312
Virtual memory size 90882048
Machine name .
Priority 8
Id 1380
Total processor time 00:00:00.5007200
User processor time 00:00:00.2303312
Virtual memory size 90882048

23.6.5 Creating and Killing Processes

Listing 23.20 shows how to create a new process and then kill it. We create and kill the process required to run Application1.exe, created earlier in this chapter.

Listing 23.20 Creating and Killing Processes (C#)
using System;
using System.Threading;
using System.Diagnostics;

public class Test {

  static void Main(string[] args) {

    try {
      Process p = Process.Start("C:\\BOOK\\code\\Chapter
      23\\Deployment\\Application1.exe");
      Thread.Sleep(5000);
      p.Kill();
    } catch (Exception e) {
      Console.WriteLine(e.StackTrace);
      }

  }
}

When you run Listing 23.20 from the command line, a second console window pops up and prints the following:

Running App 1
Running App 1
Running App 1
Running App 1
Running App 1
Running App 1

After about 5 seconds the second pop-up console window automatically closes itself.

Listing 23.20 uses the static method of the Process class to create a process. You can use the constructor instead and specify the ProcessStartInfo parameters corresponding to this process.

23.6.6 Redirecting Process Output

Sometimes it is necessary to redirect the output generated by a particular process. Java programmers attempting to get the process ID (PID) of a UNIX process are perhaps familiar with this approach of running a UNIX command and then parsing the output from the command. Listing 23.21 shows how to achieve that in C#.

Listing 23.21 Redirecting Output of a Process (C#)
using System;
using System.Diagnostics;

public class Test {

  public static void Main() {

    Process p = new Process();
    p.StartInfo.FileName = "cmd.exe";
    p.StartInfo.Arguments = "/c dir *.exe";
    p.StartInfo.UseShellExecute = false;
    p.StartInfo.RedirectStandardOutput = true;
    p.Start();
    string output = p.StandardOutput.ReadToEnd();
    Console.WriteLine("Output of command "+output);
  }

}

The output of Listing 23.21 is as follows:

Output of command Volume in drive C is Local Disk
Volume Serial Number is 50DD-3A83
Directory of C:\BOOK\code\Chapter 23\Deployment\bin\Debug
01/05/2003 10:13p  5,632 Deployment.exe
1 File(s)  5,632 bytes
0 Dir(s) 14,532,866,048 bytes free

23.6.7 Detecting Process Completion

Earlier we created a process and then killed it without caring whether the process was finished executing. In practice, however, you want to know whether the process has completed. The .NET Framework provides an event handler hook that lets you know when the process has exited. Listing 23.22 takes the example in Listing 23.21 and adds event handling.

Listing 23.22 Detecting Process Completion (C#)
using System;
using System.Diagnostics;

public class Test {

  public static void Main() {

    Process p = new Process();
    p.StartInfo.FileName = "cmd.exe";
    p.StartInfo.Arguments = "/c dir *.exe";
    p.StartInfo.UseShellExecute = false;
    p.StartInfo.RedirectStandardOutput = true;

    p.EnableRaisingEvents = true;
    p.Exited += new EventHandler(ProcessDone);

    p.Start();

    string output = p.StandardOutput.ReadToEnd();
    Console.WriteLine("Output of command "+output);

    p.WaitForExit();
  }

  private static void ProcessDone(object sender, EventArgs e) {
    Console.WriteLine("Process Exited");
  }
}

The output of Listing 23.22 is as follows:

Volume Serial Number is 50DD-3A83
Directory of C:\BOOK\code\Chapter 23\Deployment\bin\Debug
01/05/2003 10:13p  5,632 Deployment.exe
1 File(s) 5,632 bytes
0 Dir(s) 14,532,866,048 bytes free

23.6.8 Exiting a Process

Note that in Listing 23.22 we provide two ways of detecting process completion. One is by registering the ProcessDone event handler, and the other is the WaitForExit method, which returns when the process is finished.

The Process class provides several interesting methods and is richer in its API compared with its Java counterpart. For example, if you have been Web surfing a lot lately and want to quickly kill all your IE browser instances with one click, you can simply create an EXE assembly containing the code displayed in Listing 23.23.

Listing 23.23 Getting Rid of All IE Instances (C#)
using System;

using System.Diagnostics;
public class Test {
  public static void Main() {
    Process[] processes = Process.GetProcessesByName
("IEXPLORE");
    foreach (Process p in processes) {
      p.Kill();
    }
  }
}

C# also provides an API for querying services and operating system (Windows) log files. Classes for reporting at the level of the operating system are in the System.Diagnostics namespace.


23.7 Summary

Both C# and Java provide tools and APIs to package and manage applications.

  • Applications in Java are class files or their packaged archive (.jar) files. Depending on the application type, Java provides different flavors of the .jar file (.war and .ear files). These archive files are merely a collection of disk files. The unit of deployment in .NET is an assembly. An assembly can be of the type EXE, module, or library (DLL). An assembly can comprise a single file or multiple modules (perhaps written in different languages). Assemblies are represented by the System.Reflection.Assembly class. You can use this class to explore assemblies, or you can use reflection to dynamically create assemblies. Assemblies have globally unique strong names. Unlike .jar, .war, or .ear files, assemblies can be versioned. An assembly can have an associated version policy file that can dictate the upgrade path of the assembly. It is possible for two similar assemblies of different versions to coexist.

  • Application configuration is built into .NET. Unlike Java property files, the .NET configuration files are XML-based. Configuration files are automatically recognized by the CLR. Configuration files can contain sections of name/value pairs that can be accessed by the System.Collections classes.

  • In .NET, the unit of application isolation is an application domain. In Java, depending on the application, the unit of application isolation is the JVM instance; or, in the case of modern J2EE-compliant application servers, it can be the Web application–specific class loader, which allows for similar classes to coexist between two Web applications. In .NET, a single operating system process can contain several application domains, each mapped to an application. These applications behave independently of each other. You can stop applications without affecting sibling applications running in the same process. A fault in one application does not affect other applications. Application domains have built-in support for loading and executing remote assemblies. You can call methods on types defined in remote applications without incurring any performance penalty because the method call is intraprocess instead of interprocess.

  • The .NET System.Diagnostics namespace provides several classes for interacting with the operating system. One such class is the Process class, which is similar to the java.lang.Process class. However, unlike the Java counterpart, a .NET Process class lets you do a lot. You can query, create, and kill processes.


Appendix Java and C# API Comparison

Classes in Java and .NET C# provide comparable functionality in several areas of enterprise computing. These languages have built-in support for database access, graphics, XML processing, networking, reflection, and many other enterprise tasks. The languages also provide a basic set of utility classes and interfaces that form a foundation for users to build on. This appendix provides a quick reference to Java and C# class library functionality.

We list classes primarily in JDK 1.4 and their C# counterparts from different areas of enterprise computing. The commonly used packages and namespaces are listed, along with their classes. Note that we do not show classes that do not have readily identifiable equivalents in .NET, nor do we show classes that do not have equivalent concepts in .NET.


A.1 The java.lang Package

This is the core Java package, and it consists of classes that form the building blocks of the rest of the language API. Table A.1 shows the classes in this package and their .NET counterparts.

Table A.1. The java.lang Package Classes and Their .NET Counterparts

Java

.NET

Boolean

System.Boolean

Byte

System.Byte

Character

System.Char

Class

System.Type

Double

System.Double

Float

System.Single

Integer

System.Int32

Long

System.Int64

Math

System.Math

Object

System.Object

Process

System.Diagnostics.Process

Runtime

System.Diagnostics.Process

Short

System.Int16

StrictMath

System.Math

String

System.String

StringBuffer

System.Text.StringBuilder

Thread

System.Threading.Thread

ThreadLocal

System.LocalDataStoreSlot

Throwable

System.Exception

Tables A.2 and A.3 show the classes in the java.lang.reflect and java.lang.ref packages, respectively.

Table A.2. The java.lang.reflect Package Classes and Their .NET Counterparts

Java

.NET

AccessibleObject

System.Reflection.MemberInfo

Array

System.Array

Constructor

System.Reflection.ConstructorInfo

Field

System.Reflection.FieldInfo

Method

System.Reflection.MethodInfo

Modifier

System.Reflection.MethodAttributes

ReflectPermission

System.Security.Permissions.Reflection Permission

Table A.3. A java.lang.ref Package Class and Its .NET Counterpart

Java

.NET

WeakReference

System.WeakReference


A.2 The java.util Package

This package contains the collection classes. Collection classes are handy data structures that make it easy to store application data. In addition to the collection classes, this package contains utility classes such as Properties, Locale, StringTokenizer, and so on. Table A.4 shows the classes in this package and their .NET counterparts.

Table A.4. The java.util Package Classes and Their .NET Counterparts

Java

.NET

AbstractCollection

System.Collections.CollectionsBase

ArrayList

System.Collections.ArrayList

Arrays

System.Array

BitSet

System.Collections.BitArray

Calendar

System.Globalization.Calendar

Currency

System.Globalization.RegionInfo

Date

System.DateTime

Dictionary

System.Collections.DictionaryBase

EventObject

System.EventArgs

GregorianCalendar

System.Globalization.GregorianCalendar

HashMap

System.Collections.Hashtable

Hashtable

System.Collections.Hashtable

ListResourceBundle

System.Resources.ResourceManager

Locale

System.Globalization.CultureInfo

Random

System.Random

ResourceBundle

System.Resources.ResourceSet

SimpleTimeZone

System.DateTime

Stack

System.Collections.Stack

Timer

System.Threading.Timer

TimerTask

System.Threading.TimerCallback

TimeZone

System.DateTime

TreeSet

System.Collections.SortedList

Vector

System.Collections.ArrayList

The .NET Framework does not provide equivalents for classes in the java.util.logging, java.util. jar, java.util.zip, and java.util.pref packages.

Table A.5 shows how classes in java.util.regex match up with the System.Text.RegularExpressions namespace in .NET.

Table A.5. The java.util.regex Package Classes and Their .NET Counterparts

Java

.NET

Matcher

System.Text.RegularExpressions.Match
System.Text.RegularExpressions.MatchCollection
System.Text.RegularExpressions.Group
System.Text.RegularExpressions.GroupCollection

Pattern

[View full width]
System.Text.RegularExpressions.Regex
System.Text.RegularExpressions.
  RegexCompilationInfo
System.Text.RegularExpressions.RegexOptions

PatternSyntaxException

System.ArgumentException


A.3 The java.io Package

This package contains classes for doing disk, stream, and memory I/O operations. Table A.6 shows the classes.

There are no direct mappings for the java.nio package even though asynchronous I/O is supported in C#.

Table A.6. The java.io Package Classes and Their .NET Counterparts

Java

.NET

BufferedInputStream

System.IO.BufferedStream

BufferedOutputStream

System.IO.BufferedStream

BufferedReader

System.IO.StreamReader

BufferedWriter

System.IO.StreamWriter

ByteArrayInputStream

System.IO.MemoryStream

ByteArrayOutputStream

System.IO.MemoryStream

CharArrayReader

System.IO.StreamReader

CharArrayWriter

System.IO.StreamWriter

DataInputStream

System.IO.BinaryReader

DataOutputStream

System.IO.BinaryWriter

File

System.IO.File

FileInputStream

System.IO.FileStream

FileOutputStream

System.IO.FileStream

FileReader

System.IO.StreamReader

FileWriter

System.IO.StreamWriter

InputStream

System.IO.Stream

OutputStream

System.IO.Stream

PrintStream

System.IO.StreamWriter

PrintWriter

System.IO.StreamWriter

PushbackInputStream

System.IO.StreamReader

PushbackOutputStream

System.IO.StreamReader

RandomAccessFile

System.IO.FileStream

StringBufferInputStream

System.IO.StringReader

StringReader

System.IO.StringReader

StringWriter

System.IO.StringWriter


A.4 The java.sql Package

This package contains classes for performing database access. Table A.7 shows the class equivalents.

There is no direct .NET equivalent for classes in the javax.sql package. Connection pooling and transaction classes are provided in the System.Data namespace. The Java Rowset class is somewhat similar to the .NET DataSet class.

Table A.7. The java.sql Package Classes and Their .NET Counterparts.

Java

.NET

Blob

System.Data.SqlClient.SqlDataReader
System.Data.OleDb.OleDbDataReader

CallableStatement

System.Data.SqlClient.SqlCommand
System.Data.OleDb.OleDbCommand

Clob

System.Data.SqlClient.SqlDataReader
System.Data.OleDb.OleDbDataReader

Connection

System.Data.SqlClient.Sql
System.Data.OleDb.OleDb

Date

System.Data.SqlTypes.SqlDateTime

ParameterMetaData

System.Data.SqlClient.SqlParameter
System.Data.OleDb.OleDbParameter

PreparedStatement

System.Data.SqlClient.SqlCommand
System.Data.OleDb.OleDbCommand

ResultSet

System.Data.SqlClient.SqlDataReader
System.Data.OleDb.OleDbDataReader

ResultSetMetaData

System.Data.SqlClient.SqlDataReader
System.Data.OleDb.OleDbDataReader

Savepoint

System.Data.SqlClient.SqlTransaction

SQLPermission

System.Data.SqlClient.SqlClientPermission
System.Data.OleDb.OleDbPermission

Statement

System.Data.SqlClient.SqlCommand
System.Data.OleDb.OleDbCommand

Time

System.Data.SqlTypes.SqlDateTime

Types

System.Data.DbTypes


A.5 The java.text Package

The java.text package contains utility classes for text processing and formatting, including date and numerical formatting. Table A.8 shows the Java and .NET counterparts.

Table A.8. The java.text Package Classes and Their .NET Counterparts

Java

.NET

DateFormat

System.DateTime

DateFormatSymbols

System.Globalization.DateTimeFormatInfo

NumberFormat

System.Globalization.NumberFormatInfo

SimpleDateFormat

System.Globalization.DateTimeFormatInfo

StringCharacterIterator

System.IEnumerator (System.String)

Table A.9. The org.w3c.dom Package Classes and Their .NET Counterparts

Java

.NET

Attr

System.Xml.XmlAttribute

CdataSection

System.Xml.XmlCDataSection

CharacterData

System.Xml.XmlCharacterData

Comment

System.Xml.XmlComment

Document

System.Xml.XmlDocument

DocumentFragment

System.Xml.XmlDocumentFragment

DocumentType

System.Xml.XmlDocumentType

DOMImplementation

System.Xml.XmlImplementation

Element

System.Xml.XmlElement

Entity

System.Xml.XmlEntity

EntityReference

System.Xml.XmlEntityReference

NamedNodeMap

System.Xml.XmlNamedNodeMap

Node

System.Xml.XmlNode

NodeList

System.Xml.XmlNodeList

Notation

System.Xml.XmlNotation

ProcessingInstruction

System.Xml.XmlProcessingInstruction

DOMException

System.ArgumentException


A.6 The org.w3c.dom Package

This package defines classes that model the elements of an XML document. Table A.9 shows the class equivalents.


A.7 The javax.swing Package

The Swing library consists of classes for writing GUI applications in Java. Table A.10 lists the classes and their equivalents.

Table A.10. The javax.swing Package Classes and Their .NET Counterparts

Java

.NET

AbstractButton

System.Windows.Forms.ButtonBase

AbstractListModel

System.Windows.Forms.ListControl

AbstractSpinnerModel

System.Windows.Forms.UpDownBase

ImageIcon

System.Windows.Forms.Image

JButton

System.Windows.Forms.Button

JCheckBox

System.Windows.Forms.CheckBox

JColorChooser

System.Windows.Forms.ColorDialog

JComboBox

System.Windows.Forms.ComboBox

JComponent

System.Windows.Forms.UserControl

JDialog

System.Windows.Forms.CommonDialog

JEditorPane

System.Windows.Forms.TextBoxBase

JFileChooser

System.Windows.Forms.OpenFileDialog

JFormattedTextField

System.Windows.Forms.RichTextBox

JFrame

System.Windows.Forms.Form

JLabel

System.Windows.Forms.Label

JList

System.Windows.Forms.ListBox

JMenuBar

System.Windows.Forms.MainMenu

JMenuItem

System.Windows.Forms.MenuItem

JPanel

System.Windows.Forms.Panel

JPasswordField

System.Windows.Forms.TextBox

JPopupMenu

System.Windows.Forms.ContextMenu

JProgressBar

System.Windows.Forms.StatusBar

JRadioButton

System.Windows.Forms.RadioButton

JScrollBar

System.Windows.Forms.HScrollBar
System.Windows.Forms.VScrollBar

JScrollPane

System.Windows.Forms.Panel

JSlider

System.Windows.Forms.TrackBar

JSpinner

System.Windows.Forms.DomainUpDown

JSplitPane

System.Windows.Forms.Splitter

JTabbedPane

System.Windows.Forms.TabControl

JTable

System.Windows.Forms.ListView

JTextArea

System.Windows.Forms.TextBox

JTextField

System.Windows.Forms.TextBox

JTextPane

System.Windows.Forms.RichTextBox

JToggleButton

System.Windows.Forms.ButtonBase

JToolBar

System.Windows.Forms.ToolBar

JToolTip

System.Windows.Forms.ToolTip

JTree

System.Windows.Forms.ListView

Popup

System.Windows.Forms.ContextMenu

Timer

System.Windows.Forms.Timer

Table A.11 shows the comparison between javax.swing.event package classes and their .NET equivalents.

The .NET Framework does not provide directly equivalent classes for the following packages: javax.swing.plaf, javax.swing.border, javax.swing.table, javax.swing.text (although you can use System.Windows .Form.RichTextBox), and javax.swing.filechooser (although you can use System.Windows.Forms.OpenFileDialog and System.Windows.Forms.SaveFileDialog).

Table A.11. The javax.swing.event Package Classes and Their .NET Counterparts

Java

.NET

CaretEvent

System.EventArgs

ListDataEvent

System.EventArgs

ListSelectionEvent

System.EventArgs

MenuEvent

System.EventArgs

PopupMenuEvent

System.EventArgs


A.8 The java.awt Package

The .NET Framework provides a small set of classes that compare to Java's AWT. Tables A.12 and A.13 show the classes of the java.awt and the java.awt.event packages, along with their C# equivalents.

Table A.12. The java.awt Package Classes and Their .NET Counterparts

Java

.NET

AWTEvent

System.EventArgs

AWTKeyStroke

System.Windows.Forms.KeyPressEventArgs

AWTPermission

System.Security.Permissions.UIPermission

BasicStroke

System.Drawing.Pen

Button

System.Windows.Forms.Button

Canvas

System.Windows.Forms.Control

Checkbox

System.Windows.Forms.RadioButton

Choice

System.Windows.Forms.ComboBox

Color

System.Drawing.Color

Component

System.Windows.Forms.Control

Container

System.Windows.Forms.Control

Cursor

System.Windows.Forms.Cursor

Dialog

System.Windows.Forms.CommonDialog

Dimension

System.Drawing.Size

Event

System.EventArgs

FileDialog

System.Windows.Forms.FileDialog

Font

System.Drawing.Font

Frame

System.Windows.Forms.Form

GradientPaint

System.Drawing.Drawing2D.LinearGradientBrush

Graphics

System.Drawing.Graphics

Graphics2D

System.Drawing.Graphics

Image

System.Drawing.Image

JobAttributes

System.Drawing.Printing.PageSettings

Label

System.Windows.Forms.Label

List

System.Windows.Forms.ListBox

Menu

System.Windows.Forms.MainMenu

MenuItem

System.Windows.Forms.MenuItem

PageAttributes

System.Drawing.Printing.PageSettings

Panel

System.Windows.Forms.Panel

Point

System.Drawing.Point

PopupMenu

System.Windows.Forms.ContextMenu

PrintJob

System.Drawing.Printing.PrintDocument

Rectangle

System.Drawing.Rectangle

Scrollbar

System.Windows.Forms.HscrollBar
System.Windows.Forms.VscrollBar

ScrollPane

System.Windows.Forms.Panel

TextArea

System.Windows.Forms.TextBox

TextField

System.Windows.Forms.TextBox

TexturePaint

System.Drawing.TextureBrush

Table A.13. The java.awt.event Package Classes and Their .NET Counterparts

Java

.NET

ActionEvent

System.EventArgs

AdjustmentEvent

System.Windows.Forms.ScrollEventArgs

ComponentEvent

System.EventArgs

ContainerEvent

System.Windows.Forms.ControlEventArgs

FocusEvent

System.EventArgs

ItemEvent

System.EventArgs

KeyEvent

System.Windows.Forms.KeyPressEventArgs

MouseEvent

System.Windows.Forms.MouseEventArgs

MouseWheelEvent

System.Windows.Forms.MouseEventArgs

PaintEvent

System.Windows.Forms.PaintEventArgs

TextEvent

System.EventArgs

WindowEvent

System.EventArgs

The .NET framework does not provide equivalents for the following Java packages: java.awt.color, java.awt.datatransfer, java.awt.dnd, java.awt.font, java.awt.geom, java.awt.im, java.awt.im.spi, java.awt.image, and java.awt.image.renderable.

Table A.14 shows the classes of the java.awt.print package.

Table A.14. The java.awt.print Package Classes and Their .NET Counterparts

Java

.NET

PageFormat

System.Drawing.Printing.PageSettings

Paper

System.Drawing.Printing.PaperSize

PrinterJob

System.Drawing.Printing.PrintDocument


Team LiB

Brought to You by

Like the book? Buy it!