Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET) ®
™
Delivery Guide Course Number: 2349B
Part Number: X08-76381 Released: 02/2002
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places, and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, places or events is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. 2001−2002 Microsoft Corporation. All rights reserved. Microsoft, ActiveX, BizTalk, IntelliMirror, Jscript, MSDN, MS-DOS, MSN, PowerPoint, Visual Basic, Visual C++, Visual C#, Visual Studio, Win32, Windows, Windows Media, and Window NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
Course Number: 2349B Part Number: X08-76381 Released: 02/2002
Programming with the Microsoft® .NET Framework (Microsoft Visual C#™ .NET)
iii
Contents Introduction Introduction..............................................................................................................1 Course Materials ......................................................................................................2 Prerequisites.............................................................................................................3 Course Outline .........................................................................................................4 Microsoft Certified Professional Program ...............................................................9 Facilities.................................................................................................................11
Module 1: Overview of the Microsoft .NET Framework Overview..................................................................................................................1 Overview of the Microsoft .NET Framework..........................................................2 Overview of Namespaces ......................................................................................13 Review ...................................................................................................................17
Module 2: Introduction to a Managed Execution Environment Overview..................................................................................................................1 Writing a .NET Application.....................................................................................2 Compiling and Running a .NET Application.........................................................11 Lab 2: Building a Simple .NET Application..........................................................29 Review ...................................................................................................................32
Module 3: Working with Components Overview..................................................................................................................1 An Introduction to Key .NET Framework Development Technologies ..................2 Creating a Simple .NET Framework Component ....................................................4 Lab 3.1: Creating a .NET Framework Component ................................................11 Creating a Simple Console Client..........................................................................14 Lab 3.2: Creating a Simple Console-Based Client.................................................19 Demonstration: Creating a Windows Forms Client ...............................................22 Creating an ASP.NET Client .................................................................................27 Lab 3.3: Calling a Component Through an ASP.NET Page..................................36 Review ...................................................................................................................40
Module 4: Deployment and Versioning Overview..................................................................................................................1 Introduction to Application Deployment .................................................................2 Application Deployment Scenarios .........................................................................7 Related Topics and Tools.......................................................................................31 Lab 4: Packaging and Deployment ........................................................................37 Review ...................................................................................................................42
Module 5: Common Type System Overview..................................................................................................................1 An Introduction to the Common Type System ........................................................2 Elements of the Common Type System...................................................................8 Object-Oriented Characteristics.............................................................................25 Lab 5: Building Simple Types ...............................................................................39 Review ...................................................................................................................44
iv
Programming with the Microsoft® .NET Framework (Microsoft Visual C#™ .NET)
Module 6: Working with Types Overview................................................................................................................. 1 System.Object Class Functionality ......................................................................... 2 Specialized Constructors....................................................................................... 12 Type Operations.................................................................................................... 18 Interfaces............................................................................................................... 28 Managing External Types ..................................................................................... 34 Lab 6: Working with Types .................................................................................. 38 Review .................................................................................................................. 43
Module 7: Strings, Arrays, and Collections Overview................................................................................................................. 1 Strings ..................................................................................................................... 2 Terminology – Collections.................................................................................... 20 .NET Framework Arrays....................................................................................... 21 .NET Framework Collections ............................................................................... 39 Lab 7: Working with Strings, Enumerators, and Collections................................ 57 Review .................................................................................................................. 63
Module 8: Delegates and Events Overview................................................................................................................. 1 Delegates................................................................................................................. 2 Multicast Delegates............................................................................................... 12 Events.................................................................................................................... 21 When to Use Delegates, Events, and Interfaces.................................................... 31 Lab 8: Creating a Simple Chat Server................................................................... 32 Review .................................................................................................................. 42
Module 9: Memory and Resource Management Overview................................................................................................................. 1 Memory Management Basics.................................................................................. 2 Non-Memory Resource Management ................................................................... 12 Implicit Resource Management ............................................................................ 13 Explicit Resource Management ............................................................................ 26 Optimizing Garbage Collection ............................................................................ 36 Lab 9: Memory and Resource Management ......................................................... 49 Review .................................................................................................................. 56
Module 10: Data Streams and Files Overview................................................................................................................. 1 Streams.................................................................................................................... 2 Readers and Writers ................................................................................................ 5 Basic File I/O .......................................................................................................... 8 Lab 10: Files ......................................................................................................... 21 Review .................................................................................................................. 26
Programming with the Microsoft® .NET Framework (Microsoft Visual C#™ .NET)
v
Module 11: Internet Access Overview..................................................................................................................1 Internet Application Scenarios.................................................................................2 The WebRequest and WebResponse Model............................................................3 Application Protocols.............................................................................................16 Handling Errors......................................................................................................25 Security ..................................................................................................................28 Best Practices.........................................................................................................35 Lab 11: Creating a DateTime Client/Server Application.......................................36 Review ...................................................................................................................41 Course Evaluation..................................................................................................43
Module 12: Serialization Overview..................................................................................................................1 Serialization Scenarios.............................................................................................2 Serialization Attributes ............................................................................................4 Object Graph............................................................................................................5 Serialization Process ................................................................................................7 Serialization Example ..............................................................................................9 Deserialization Example ........................................................................................10 Custom Serialization..............................................................................................12 Custom Serialization Example...............................................................................14 Security Issues .......................................................................................................17 Lab 12: Serialization ..............................................................................................18 Review ...................................................................................................................27
Module 13: Remoting and XML Web Services Overview..................................................................................................................1 Remoting..................................................................................................................2 Remoting Configuration Files................................................................................19 Demonstration: Remoting ......................................................................................22 Lab 13.1: Building an Order-Processing Application by Using Remoted Servers 28 XML Web Services................................................................................................36 Lab 13.2: Using an XML Web Service..................................................................48 Review ...................................................................................................................54 Course Evaluation..................................................................................................56
vi
Programming with the Microsoft® .NET Framework (Microsoft Visual C#™ .NET)
Optional Modules Module 14 (Optional): Threading and Asynchronous Programming Overview................................................................................................................. 1 Introduction to Threading ....................................................................................... 2 Using Threads in .NET ........................................................................................... 9 Thread Safety ........................................................................................................ 29 Special Thread Topics........................................................................................... 51 Asynchronous Programming in .NET................................................................... 74 Lab 14: Working With Multithreaded Applications ............................................. 92 Review ................................................................................................................ 108
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code Overview................................................................................................................. 1 Integration Services................................................................................................. 2 Platform Invoke ...................................................................................................... 7 Lab 15.1: Calling Win32 APIs.............................................................................. 16 Calling COM Objects from Managed Code.......................................................... 20 Lab 15.2: Calling COM Objects ........................................................................... 39 Calling .NET Objects from COM Objects............................................................ 43 Review .................................................................................................................. 55
Module 16 (Optional): Using Microsoft ADO.NET to Access Data Overview................................................................................................................. 1 Overview of ADO.NET .......................................................................................... 2 Connecting to a Data Source................................................................................. 10 Accessing Data with DataSets .............................................................................. 12 Using Stored Procedures....................................................................................... 26 Lab 16: Using ADO.NET to Access Data ............................................................ 34 Accessing Data with DataReaders ........................................................................ 42 Binding to XML Data ........................................................................................... 50 Review .................................................................................................................. 56
Module 17 (Optional): Attributes Overview................................................................................................................. 1 Overview of Attributes............................................................................................ 2 Defining Custom Attributes .................................................................................. 13 Retrieving Attribute Values .................................................................................. 22 Demonstration: Custom Attributes ....................................................................... 26 Lab 17: Defining and Using Attributes................................................................. 27 Review .................................................................................................................. 36
Programming with the Microsoft® .NET Framework (Microsoft Visual C#™ .NET)
vii
About This Course This section provides you with a brief description of the course, audience, suggested prerequisites, course objectives, and information about course customization.
Description The goal of this five day course is to help application developers understand the Microsoft® .NET Framework. In addition to offering an overview of the .NET Framework and an introduction to key concepts and terminology, the course provides a series of labs, which introduce and explain .NET Framework features that are used to code, debug, tune, and deploy applications.
Audience This course is intended for experienced, professional software developers who work in independent software vendors (ISVs) or work on corporate enterprise development teams. Most students will be Microsoft Visual C++® (or C++) and Java developers.
Student Prerequisites This course requires that students meet the following prerequisites: • Students should be experienced professional developers and have a basic understanding of the C# language. Students can meet the C# language prerequisite by taking Course 2124, Introduction to C# Programming for the Microsoft .NET Platform.
Course Objectives After completing this course, the student will be able to: !
List the major elements of the .NET Framework and explain how they fit into the .NET platform.
!
Explain the main concepts behind the common language runtime and use the features of the .NET Framework to create a simple application.
!
Create and use components in Microsoft Windows® Forms-based and ASP.NET-based applications.
!
Use the deployment and versioning features of the .NET runtime to deploy multiple versions of a component.
!
Create, use, and extend types by understanding the Common Type System architecture.
!
Create classes and interfaces that are functionally efficient and appropriate for specific programming scenarios.
!
Use the .NET Framework class library to efficiently create and manage strings, arrays, collections, and enumerators.
viii
Programming with the Microsoft® .NET Framework (Microsoft Visual C#™ .NET) !
Use delegates and events to have an event sender object signal the occurrence of an action to an event receiver object.
!
Describe and control how memory and other resources are managed in the .NET Framework.
!
Read from and write to data streams and files.
!
Use the basic request/response model to send and receive data over the Internet.
!
Serialize and deserialize an object graph.
!
Create distributed applications through XML Web services and Object Remoting.
Programming with the Microsoft® .NET Framework (Microsoft Visual C#™ .NET)
Course Timing This section provides estimated course timings for all of the modules, labs, and breaks in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C#™ .NET). Your timing may vary.
Day 1 Start
End
Module
9:00
9:30
Introduction
9:30
10:00
Module 1: Overview of the Microsoft .NET Framework
10:00
10:15
Break
10:15
11:00
Module 2: Introduction to a Managed Execution Environment
11:00
11:15
Lab 2: Building a Simple .NET Application
11:15
11:45
Module 3: Working with Components
11:45
12:00
Lab 3.1: Creating a .NET Framework Component
12:00
1:00
Lunch
1:00
1:15
Module 3: Working with Components (continued)
1:15
1:30
Lab 3.2: Creating a Simple Console-Based Client
1:30
2:00
Module 3: Working with Components (continued)
2:00
2:30
Lab 3.3: Calling a Component Through an ASP .NET Page
2:30
2:45
Break
2:45
4:15
Module 4: Deployment and Versioning
Start
End
Module
9:00
9:50
Lab 4: Packaging and Deployment
9:50
10:00
Break
10:00
11:30
Module 5: Common Type System
11:30
12:30
Lunch
12:30
1:15
Lab 5: Building Simple Types
1:15
2:30
Module 6: Working with Types
2:30
2:45
Break
2:45
3:30
Lab 6: Working with Types
3:30
4:00
Module 7: Strings, Arrays, and Collections
Day 2
ix
x
Programming with the Microsoft® .NET Framework (Microsoft Visual C#™ .NET)
Day 3 Start
End
Module
9:00
10:30
Module 7: Strings, Arrays, and Collections (continued)
10:30
10:45
Break
10:45
11:45
Lab 7: Working with Strings, Enumerators, and Collections
11:45
12:45
Lunch
12:45
2:00
Module 8: Delegates and Events
2:00
2:15
Break
2:15
3:30
Lab 8: Creating a Simple Chat Server
3:30
4:00
Module 9: Memory and Resource Management
Start
End
Module
9:00
10:30
Module 9: Memory and Resource Management (continued)
10:30
10:45
Break
10:45
11:45
Lab 9: Memory and Resource Management
11:45
12:45
Lunch
12:45
1:30
Module 10: Data Streams and Files
1:30
2:15
Lab 10: Files
2:15
2:30
Break
2:30
3:30
Module 11: Internet Access
3:30
4:15
Lab 11: Creating a DateTime Client/Server Application
Start
End
Module
9:00
9:30
Module 12: Serialization
9:30
10:15
Lab 12: Serialization
10:15
10:30
Break
10:30
11:30
Module 13: Remoting and Web Services
11:30
12:30
Lunch
12:30
1:20
Lab 13.1: Building an Order-Processing Application by Using Remoted Servers
1:20
2:20
Module 13: Remoting and Web Services (continued)
2:20
2:35
Break
2:35
3:30
Lab 13.2: Using an XML Web Service
Day 4
Day 5
Programming with the Microsoft® .NET Framework (Microsoft Visual C#™ .NET)
xi
Trainer Materials Compact Disc Contents The Trainer Materials compact disc contains the following files and folders: !
Autorun.exe. When the compact disc is inserted into the compact disc drive, or when you double-click the Autorun.exe file, this file opens the compact disc and allows you to browse the Student Materials or Trainer Materials compact disc.
!
Autorun.inf. When the compact disc is inserted into the compact disc drive, this file opens Autorun.exe.
!
Default.htm. This file opens the Trainer Materials Web page.
!
Readme.txt. This file explains how to install the software for viewing the Trainer Materials compact disc and its contents and how to open the Trainer Materials Web page.
!
2349B_ms.doc. This file is the Manual Classroom Setup Guide. It contains the steps for manually setting up the classroom computers.
!
2349B_sg.doc. This file is the Automated Classroom Setup Guide. It contains a description of classroom requirements, classroom configuration, instructions for using the automated classroom setup scripts, and the Classroom Setup Checklist.
!
Powerpnt. This folder contains the Microsoft PowerPoint® slides that are used in this course.
!
Pptview. This folder contains the Microsoft PowerPoint Viewer, which is used to display the PowerPoint slides.
!
Setup. This folder contains the files that install the course and related software to computers in a classroom setting.
!
StudentCD. This folder contains the Web page that provides students with links to resources pertaining to this course, including additional reading, review and lab answers, lab files, multimedia presentations, and courserelated Web sites.
!
Tools. This folder contains files and utilities used to complete the setup of the instructor computer.
!
Webfiles. This folder contains the files that are required to view the course Web page. To open the Web page, open Windows Explorer, and in the root directory of the compact disc, double-click Default.htm or Autorun.exe.
xii
Programming with the Microsoft® .NET Framework (Microsoft Visual C#™ .NET)
Student Materials Compact Disc Contents The Student Materials compact disc contains the following files and folders: !
Autorun.exe. When the compact disc is inserted into the CD-ROM drive, or when you double-click the Autorun.exe file, this file opens the compact disc and allows you to browse the Student Materials compact disc.
!
Autorun.inf. When the compact disc is inserted into the compact disc drive, this file opens Autorun.exe.
!
Default.htm. This file opens the Student Materials Web page. It provides students with resources pertaining to this course, including additional reading, review and lab answers, lab files, multimedia presentations, and course-related Web sites.
!
Readme.txt. This file explains how to install the software for viewing the Student Materials compact disc and its contents and how to open the Student Materials Web page.
!
2349B_ms.doc. This file is the Manual Classroom Setup Guide. It contains a description of classroom requirements, classroom setup instructions, and the classroom configuration.
!
Democode. This folder contains demonstration code.
!
Flash. This folder contains the installer for the Macromedia Flash 5.0 browser plug-in.
!
Fonts. This folder contains fonts that are required to view the PowerPoint presentation and Web-based materials.
!
Labs. This folder contains files that are used in the hands-on labs. These files may be used to prepare the student computers for the hands-on labs.
!
Media. This folder contains files that are used in multimedia presentations for this course.
!
Mplayer. This folder contains the setup file to install Microsoft Windows Media™ Player.
!
Webfiles. This folder contains the files that are required to view the course Web page. To open the Web page, open Windows Explorer, and in the root directory of the compact disc, double-click Default.htm or Autorun.exe.
!
Wordview. This folder contains the Word Viewer that is used to view any Word document (.doc) files that are included on the compact disc.
Programming with the Microsoft® .NET Framework (Microsoft Visual C#™ .NET)
xiii
Document Conventions The following conventions are used in course materials to distinguish elements of the text. Convention
Use
"
Indicates an introductory page. This symbol appears next to a topic heading when additional information on the topic is covered on the page or pages that follow it.
bold
Represents commands, command options, and syntax that must be typed exactly as shown. It also indicates commands on menus and buttons, dialog box titles and options, and icon and menu names.
italic
In syntax statements or descriptive text, indicates argument names or placeholders for variable information. Italic is also used for introducing new terms, for book and course titles, and for emphasis in the text.
Title Capitals
Indicate domain names, user names, computer names, directory names, and folder and file names, except when specifically referring to case-sensitive names. Unless otherwise indicated, you can use lowercase letters when you type a directory name or file name in a dialog box or at a command prompt.
ALL CAPITALS
Indicate the names of keys, key sequences, and key combinations — for example, ALT+SPACEBAR.
monospace
Represents code samples or examples of screen text.
[]
In syntax statements, enclose optional items. For example, [filename] in command syntax indicates that you can choose to type a file name with the command. Type only the information within the brackets, not the brackets themselves.
{}
In syntax statements, enclose required items. Type only the information within the braces, not the braces themselves.
|
In syntax statements, separates an either/or choice.
!
Indicates a procedure with sequential steps.
...
In syntax statements, specifies that the preceding item may be repeated.
. . .
Represents an omitted portion of a code sample.
THIS PAGE INTENTIONALLY LEFT BLANK
Introduction
Contents Introduction
1
Course Materials
2
Prerequisites
3
Course Outline
4
Microsoft Certified Professional Program
9
Facilities
11
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, place or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. 2001-2002 Microsoft Corporation. All rights reserved. Microsoft, ActiveX, BizTalk, IntelliMirror, Jscript, MSDN, MS-DOS, MSN, PowerPoint, Visual Basic, Visual C++, Visual C#, Visual Studio, Win32, Windows, Windows Media, and Window NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
Introduction
iii
Instructor Notes Presentation: 30 Minutes
The Introduction module provides students with an overview of the course content, materials, and logistics for Course 2349B, Programming with the Microsoft® .NET Framework (Microsoft Visual C#™ .NET).
Course Materials and Preparation Required Materials To teach this course, you need the following materials: !
Delivery Guide
!
Trainer Materials compact disc
Preparation Tasks To prepare for this course, you must complete the Course Preparation Checklist that is included with the trainer course materials.
Module Strategy Use the following strategy to present this module: !
Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET) Show the slide that displays the course number and course title.
!
Introduction Welcome students to the course and introduce yourself. Provide a brief overview of your background to establish credibility. Have students introduce themselves and provide their background, product experience, and expectations of the course. Record student expectations on a white board or flip chart that you can reference later in class.
!
Course Materials Explain the purpose of all materials used in this course.
!
Prerequisites Provide the students with the list of prerequisites that they should have met before taking this course. This is an opportunity for you to identify students who may not have the appropriate background or experience to attend this course.
iv
Introduction !
Course Outline Provide an overview of each module and what students will learn. Explain how this course will meet students’ expectations by relating the information covered in individual modules to their expectations. Providing reasonably complete coverage of the Microsoft .NET Framework within a five day class is a challenging undertaking. The thirteen modules that encompass Course 2349B, Programming with the Microsoft.NET Framework (Microsoft Visual C# .NET), will provide most students with a common baseline for working with the .NET Framework. Note For more information about customizing this course see the Optional Course Presentation Strategies section in this module.
!
Setup Provide the students with any necessary setup information for the course.
!
Microsoft Certified Professional Program Inform students about the Microsoft Certified Professional (MCP) program and the various certification options.
!
Facilities Explain the facility information for the training site.
Introduction
v
Optional Course Presentation Strategies Because of the complexity and amount of material that makes up the .NET Framework, additional resources are included to provide instructors with some flexibility in the course delivery.
Course Customization The thirteen modules that make up the five day Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET), will provide most students with a common baseline for working with the .NET Framework. In addition to the first thirteen modules that make up the five days of material, the course contains four optional modules, thus providing you with the opportunity for customization. You are not required to cover the optional modules as part of Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET). The decision about whether to cover the optional modules has been left entirely to you, the instructor, as you will need to consider the circumstances of each class separately, including student levels and preferences, and any requirements of an individual Microsoft Certified Technical Education Center (Microsoft CTEC). However, as part of the course materials, your students will receive all seventeen modules. While Modules 14 through 17 are considered optional, you should be prepared to discuss what you plan to cover during the course introduction. If you only intend to cover the first thirteen modules that make up the official five days of course delivery of Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET), then state that clearly and suggest that the optional modules may be used as additional selfstudy. Alternatively, you may want to discuss covering one or more of the optional modules if there is sufficient interest in those topics. You will then need to consider with your students what module(s) you may safely leave out in order to accommodate the optional module(s). For example, an alternative approach for more advanced students who already have a basic understanding of .NET Framework applications and the Microsoft Visual C#™ .NET object model would be to omit Modules 2 through 6 and teach Modules 7 through 17.
vi
Introduction
Course Flow As a general guideline, modules may be grouped accordingly, as shown in the following table. Module
Course Flow
Module 1: Overview of the Microsoft .NET Framework
This short module is designed to provide a high level overview of the .NET Framework.
Module 2: Introduction to a Managed Execution Environment
Modules 2 through 4 cover core .NET Framework concepts, including managed execution, assemblies, and deployment.
Module 3: Working with Components Module 4: Deployment and Versioning Module 5: Common Type System Module 6: Working with Types Module 7: Strings, Arrays, and Collections Module 8: Delegates and Events Module 9: Memory and Resource Management
Modules 5 and 6 cover core .NET Framework object oriented topics. Modules 7 through 13 cover more advanced material than the preceding modules and may be considered as standalone material. Modules 10 through 12 have some relevance to Module 13: Remoting and XML Web Services.
Module 10: Data Streams and Files Module 11: Internet Access Module 12: Serialization Module 13: Remoting and XML Web Services Module 14: Threading and Asynchronous Programming Module 15: Interoperating Between Managed and Unmanaged Code Module 16: Using Microsoft ADO.NET to Access Data Module 17: Attributes
Modules 14 through 17 may be considered as optional, standalone, and generally more advanced than modules 1 through 6.
Introduction
vii
Course Timing This section provides estimated course timings for all of the modules, labs, and breaks in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET). The following schedule options are provided as a guide to help with ideas about how to organize your class if you decide to customize the course.
Option 1 The following schedule is an estimate of the course timing if you choose to teach Modules 1 through 13. This is the basic approach for students needing additional instruction regarding Visual C# .NET and the .NET Framework approach to assemblies, packaging and the object model. If you do not intend to customize Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET), this is the approach you should use. Your timing may vary.
Day 1 Start
End
Module
9:00
9:30
Introduction
9:30
10:00
Module 1: Overview of the Microsoft .NET Framework
10:00
10:15
Break
10:15
11:00
Module 2: Introduction to a Managed Execution Environment
11:00
11:15
Lab 2: Building a Simple .NET Application
11:15
11:45
Module 3: Working with Components
11:45
12:00
Lab 3.1: Creating a .NET Framework Component
12:00
1:00
Lunch
1:00
1:15
Module 3: Working with Components (continued)
1:15
1:30
Lab 3.2: Creating a Simple Console-Based Client
1:30
2:00
Module 3: Working with Components (continued)
2:00
2:30
Lab 3.3: Calling a Component Through an ASP .NET Page
2:30
2:45
Break
2:45
4:15
Module 4: Deployment and Versioning
Start
End
Module
9:00
9:50
Lab 4: Packaging and Deployment
9:50
10:00
Break
10:00
11:30
Module 5: Common Type System
11:30
12:30
Lunch
12:30
1:15
Lab 5: Building Simple Types
1:15
2:30
Module 6: Working with Types
2:30
2:45
Break
2:45
3:30
Lab 6: Working with Types
3:30
4:00
Module 7: Strings, Arrays, and Collections
Day 2
viii
Introduction
Day 3 Start
End
Module
9:00
10:30
Module 7: Strings, Arrays, and Collections (continued)
10:30
10:45
Break
10:45
11:45
Lab 7: Working with Strings, Enumerators, and Collections
11:45
12:45
Lunch
12:45
2:00
Module 8: Delegates and Events
2:00
2:15
Break
2:15
3:30
Lab 8: Creating a Simple Chat Server
3:30
4:00
Module 9: Memory and Resource Management
Start
End
Module
9:00
10:30
Module 9: Memory and Resource Management (continued)
10:30
10:45
Break
10:45
11:45
Lab 9: Memory and Resource Management
11:45
12:45
Lunch
12:45
1:30
Module 10: Data Streams and Files
1:30
2:15
Lab 10: Files
2:15
2:30
Break
2:30
3:30
Module 11: Internet Access
3:30
4:15
Lab 11: Creating a DateTime Client/Server Application
Start
End
Module
9:00
9:30
Module 12: Serialization
9:30
10:15
Lab 12: Serialization
10:15
10:30
Break
10:30
11:30
Module 13: Remoting and Web Services
11:30
12:30
Lunch
12:30
1:20
Lab 13.1: Building an Order-Processing Application by Using Remoted Servers
1:20
2:20
Module 13: Remoting and Web Services (continued)
2:20
2:35
Break
2:35
3:30
Lab 13.2: Using an XML Web Service
Day 4
Day 5
Introduction
Option 2 The following schedule is an estimate of the course timing if you choose to teach Module 1 followed by Modules 7 through 17. This approach would serve more advanced students who already have a solid understanding of Visual C# .NET and the .NET Framework approach to assemblies, packaging and the object model. Your timing may vary.
Day 1 Start
End
Module
9:00
9:30
Introduction
9:30
10:00
Module 1: Overview of the Microsoft .NET Framework
10:00
10:15
Break
10:15
11:45
Module 7: Strings, Arrays, and Collections
11:45
12:45
Lunch
12:45
1:15
Module 7: Strings, Arrays, and Collections (continued)
1:15
2:15
Lab 7: Working with Strings, Enumerators, and Collections
2:15
2:30
Break
2:30
3:55
Module 8: Delegates and Events
3:55
4:15
Break
4:15
5:15
Lab 8: Creating a Simple Chat Server
Start
End
Module
9:00
10:30
Module 9: Memory and Resource Management
10:30
10:45
Break
10:45
11:20
Module 9: Memory and Resource Management (continued)
11:20
11:50
Lab 9: Memory and Resource Management
11:50
1:00
Lunch
1:00
1:30
Lab 9: Memory and Resource Management (continued)
1:30
2:15
Module 10: Data Streams and Files
2:15
3:00
Lab 10: Files
3:00
3:15
Break
3:15
4:15
Module 11: Internet Access
4:15
5:00
Lab 11: Creating a DateTime Client/Server Application
Day 2
ix
x
Introduction
Day 3 Start
End
Module
9:00
9:30
Module 12: Serialization
9:30
10:15
Lab 12: Serialization
10:15
10:30
Break
10:30
11:30
Module 13: Remoting and XML Web Services
11:30
12:30
Lunch
12:30
1:20
Lab 13.1: Building an Order-Processing Application by Using Remoted Servers
1:20
2:20
Module 13: Remoting and XML Web Services (continued)
2:20
2:35
Break
2:35
3:30
Lab 13.2: Using an XML Web Service
Start
End
Module
9:00
10:15
Module 14: Threading and Asynchronous Programming
10:15
10:30
Break
10:30
11:45
Module 14: Threading and Asynchronous Programming (continued)
11:45
12:45
Lunch
12:45
1:45
Lab 14: Working with Multithreaded Applications
1:45
2:00
Break
2:00
2:45
Module 15: Interoperating Between Managed and Unmanaged Code
2:45
3:15
Lab 15.1: Calling Win32 APIs
3:15
3:30
Break
3:30
4:15
Module 15: Interoperating Between Managed and Unmanaged Code (continued)
4:15
4:45
Lab 15.2: Calling COM Objects
Start
End
Module
9:00
10:30
Module 16: Using Microsoft ADO.NET to Access Data
10:30
10:45
Break
10:45
11:15
Module 16: Using Microsoft ADO.NET to Access Data (continued)
11:15
12:15
Lab 16: Using ADO.NET to Access Data
12:15
1:15
Lunch
1:15
2:15
Module 17: Attributes
2:15
2:30
Break
2:30
3:15
Lab 17: Defining and Using Attributes
Day 4
Day 5
Introduction
xi
Other Options The standard approach could be further customized by substituting one or more of Modules 7 through 13 with Modules 14 through 17. In addition, a more advanced option of delivery would be to extend the course by offering all seventeen modules. However, the decision to offer this delivery option would need to be reached in accordance with the policies and business requirements of individual Microsoft Certified Technical Education Centers (Microsoft CTECs). The following table lists the times for individual labs and modules. Lecture Time (only)
Lab Time (only)
Lecture and Lab Time (combined)
Module 1: Overview of the Microsoft .NET Framework
30 minutes
None
30 minutes
Module 2: Introduction to a Managed Execution Environment
45 minutes
20 minutes
65 minutes
Module 3: Working with Components
70 minutes
60 minutes
130 minutes
Module 4: Deployment and Versioning
90 minutes
50 minutes
140 minutes
Module 5: Common Type System
90 minutes
45 minutes
130 minutes
Module 6: Working with Types
75 minutes
45 minutes
125 minutes
Module 7: Strings, Arrays, and Collections
120 minutes
60 minutes
180 minutes
Module 8: Delegates and Events
75 minutes
75 minutes
150 minutes
Module 9: Memory and Resource Management
120 minutes
60 minutes
180 minutes
Module 10: Data Streams and Files
45 minutes
45 minutes
90 minutes
Module 11: Internet Access
60 minutes
45 minutes
105 minutes
Module 12: Serialization
30 minutes
45 minutes
75 minutes
Module 13: Remoting and XML Web Services
120 minutes
105 minutes
225 minutes
Module 14: Threading and Asynchronous Programming
150 minutes
60 minutes
210 minutes
Module 15: Interoperating Between Managed and Unmanaged Code
90 minutes
60 minutes
150 minutes
Module 16: Using Microsoft ADO.NET to Access Data
120 minutes
60 minutes
180 minutes
Module 17: Attributes
60 minutes
45 minutes
105 minutes
Module Title
Introduction
1
Introduction Topic Objective
To introduce yourself, establish credibility, meet students, and set student expectations for the course.
Lead-in
Good morning. Welcome to Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET). My name is...
!
Name
!
Company Affiliation
!
Title/Function
!
Job Responsibility
!
Programming Experience
!
.NET Framework Experience
!
Expectations for the Course
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Introduce yourself. Provide a brief overview of your background to establish credibility as a .NET Framework instructor. Ask students to introduce themselves, addressing the bulleted items on the slide.
Delivery Tip
As students introduce themselves, use a white board or flip chart to record their expectations of the course.
2
Introduction
Course Materials Topic Objective
To identify and describe the course materials.
Lead-in
We have provided everything you need for this course. You will find the following materials at your desk...
!
Name Card
!
Student Workbook
!
Student Materials Compact Disc
!
Course Evaluation
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The following materials are included with your kit:
Describe the contents of the student workbook and the Student Materials compact disc.
!
Name card. Write your name on both sides of the name card.
!
Have students write their names on both sides of the name card.
Student workbook. The student workbook contains the material covered in class, in addition to the hands-on lab exercises.
!
Student Materials compact disc. The Student Materials compact disc contains the Web page that provides you with links to resources pertaining to this course, including additional readings, review and lab answers, lab files, multimedia presentations, and course-related Web sites.
Tell students where they can send comments with feedback on the course.
Note To open the Web page, insert the Student Materials compact disc into the CD-ROM drive, and then in the root directory of the compact disc, double-click Autorun.exe or Default.htm.
Delivery Tip
Demonstrate how to open the Web page provided on the Student Materials compact disc. On the Trainer Materials compact disc, double-click Autorun.exe or Default.htm in the StudentCD folder.
!
Course evaluation. To provide feedback on the course, training facility, and instructor, you will have the opportunity to complete an online evaluation near the end of the course. To provide additional comments or inquire about the Microsoft Certified Professional program, send e-mail to
[email protected].
Introduction
3
Prerequisites Topic Objective
To present and describe the prerequisites for this course.
Lead-in
The following prerequisite knowledge is needed for this course.
!
Proficiency in the C++ or Java Programming Languages
!
A basic understanding of the C# Language
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This course requires that you meet the following prerequisites: !
Proficiency in the C++ or Java programming languages
!
A basic understanding of the C# language Students can meet the C# language prerequisite by taking Course 2124, Introduction to C# Programming for the Microsoft .NET Platform.
4
Introduction
Course Outline Topic Objective
To provide an overview of each module and what students will learn.
Lead-in
!
Module 1: Overview of the Microsoft .NET Framework
!
Module 2: Introduction to a Managed Execution Environment
!
Module 3: Working with Components
!
Module 4: Deployment and Versioning
!
Module 5: Common Type System
!
Module 6: Working with Types
In this course, we will cover...
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Briefly describe each module. As you describe each module, acknowledge any information that will meet the student expectations that you recorded earlier.
Module 1, “Overview of the Microsoft .NET Framework,” defines terminology specific to the Microsoft® .NET Framework and describes its key features and benefits. This module also discusses the namespaces taught in this course. There will be minimal lecture and no lab. After completing this module, you will be able to list the major elements of the .NET Framework. Module 2, “Introduction to a Managed Execution Environment,” introduces the concept of managed execution and shows developers how to quickly build applications that take advantage of the new .NET Framework common language runtime environment. After completing this module, you will be able to explain the main concepts behind the common language runtime and use the features of the .NET Framework to create a simple application. Module 3, “Working with Components,” discusses how to create a small, componentized application where modules can easily be written in either Microsoft Visual C#™ or Microsoft Visual Basic®. The steps necessary to construct, compile, and run each program are covered in detail. This module also explains how to build the client application by using the Microsoft Windows® Forms library and ASP.NET Web Forms. After completing this module, you will be able to create and use components in Windows Formsbased and ASP.NET-based applications. Module 4, “Deployment and Versioning,” explains how to use deployment and versioning features of the .NET Framework common language runtime to build and deploy applications that are fully managed and protected. After completing this module, you will be able to use the deployment and versioning features of the .NET Framework common language runtime to deploy multiple versions of a component.
Introduction
Module 5, “Common Type System,” introduces the Common Type System. The module discusses how to differentiate between value types and reference types and examines how classes, interfaces, properties, methods, events, and values are represented in the .NET Framework. After completing this module, you will be able to create, use, and extend types by understanding the Common Type System architecture. Module 6, “Working with Types,” discusses the use of attributes to control visibility and inheritance on types and explains how to work with various type operations, such as boxing and unboxing, and type operators. In addition, this module discusses how to build an interface that supports methods and properties and how to make interface designs more efficient. Finally, this module highlights features that are designed to help you work with unmanaged types, such as COM types. After completing this module, you will be able to create classes and interfaces that are functionally efficient and appropriate for specific programming scenarios.
5
6
Introduction
Course Outline (continued) !
Module 7: Strings, Arrays, and Collections
!
Module 8: Delegates and Events
!
Module 9: Memory and Resource Management
!
Module 10: Data Streams and Files
!
Module 11: Internet Access
!
Module 12: Serialization
!
Module 13: Remoting and XML Web Services
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Module 7, “Strings, Arrays, and Collections,” describes some of the key classes in the .NET Framework class library. This module explains how to work with strings, arrays, collections, and enumerators. After completing this module, you will be able to use the .NET Framework class library to efficiently create and manage strings, arrays, collections, and enumerators. Module 8, “Delegates and Events,” explains how the .NET Framework uses delegates in callback and event-handling scenarios. After completing this module, you will be able to use delegates and events to have an event sender object signal the occurrence of an “action” to an event receiver object. Module 9, “Memory and Resource Management,” discusses how the .NET Framework automatically handles the allocation and release of an object’s memory resources through garbage collection. After completing this module, you will be able to describe and control how memory and other resources are managed in the .NET Framework. Module 10, “Data Streams and Files,” introduces the System.IO namespace and discusses the types that it contains that allow synchronous and asynchronous reading from and writing to data streams and files. This module discusses synchronous operations only, as asynchronous operations are beyond the scope of this course. After completing this module, you will be able to read from and write to data streams and files. Module 11, “Internet Access,” discusses the use of the System.Net classes to communicate with other applications by using common protocols, such as HTTP, Transmission Control Protocol (TCP), User Datagram Protocol (UDP), and Socket Internet protocols. After completing this module, you will be able to use the basic request/response model to send and receive data over the Internet.
Introduction
7
Module 12, “Serialization,” explains how to use serialization to convert a graph of objects into a linear sequence of bytes, which can then be sent to a remote computer and deserialized, thereby making a clone in the remote memory of the original graph of objects. After completing this module, you will be able to serialize and deserialize an object graph. Module 13, “Remoting and XML Web Services,” explains how .NET Remoting supports communication between objects in different application domains, in different processes, and on different computers. The module describes how the common language runtime remoting infrastructure provides a rich set of classes that enable you to ignore most of the complexities of deploying and managing remote objects. After completing this module, you will be able to create distributed applications by means of XML Web services and Object Remoting.
8
Introduction
Course Outline (continued) !
Module 14 (Optional): Threading and Asynchronous Programming
!
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
!
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
!
Module 17 (Optional): Attributes
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The following modules are optional and can also be used as further reading about .NET Framework topics. Module 14, “Threading and Asynchronous Programming,” covers the support that the Microsoft .NET Framework provides for working with multithreaded applications and asynchronous programming. After completing this module, in addition to creating and managing threads, you will be able to handle thread synchronization to maintain application responsiveness and avoid potential data corruption and other problems. Module 15, “Interoperating between Managed and Unmanaged Code,” introduces the services that are provided by the .NET Framework to allow managed code to interoperate with unmanaged code. After completing this module, you will be able to use Platform Invoke to call unmanaged functions that are implemented in a DLL. You will also be able to call COM objects from managed code. Module 16, “Using Microsoft ADO.NET to Access Data,” presents ADO.NET as an evolution of the ADO data access model, with a rich suite of data handling and data binding functions for manipulating all types of data. After completing this module, you will use the rapid application design facilities of Microsoft Visual Studio® .NET to create an XML Web service whose methods can be used to read and update a database by using ADO.NET. You will also Use Visual Studio.NET rapid application design facilitates to create a Windows Forms application that displays and enables a user to update a DataGrid control that is bound to a DataSet. The DataSet is obtained and updated by using an XML Web service. Module 17, “Attributes,” provides details about how to use attributes in code. It describes the predefined attributes that are provided by the Microsoft .NET Framework and provides some simple examples of how to use some common attributes. After completing this module, you will be able to use custom predefined attributes, create simple custom attributes, and query attribute information at run time.
Introduction
9
Microsoft Certified Professional Program Topic Objective
To provide students with information about the Microsoft Certified Professional Program.
Lead-in
The Microsoft Certified Professional Program includes these certifications…
http://www.microsoft.com/traincert/ *****************************ILLEGAL FOR NON-TRAINER USE****************************** The Microsoft Certified Professional program is a leading certification program that validates your experience and skills to keep you competitive in today’s changing business environment. The following table describes each certification in more detail. Certification
Description
MCSA on Microsoft Windows 2000
The Microsoft Certified Systems Administrator (MCSA) certification is designed for professionals who implement, manage, and troubleshoot existing network and system environments based on Microsoft Windows 2000 platforms, including the Windows .NET Server family. Implementation responsibilities include installing and configuring parts of the systems. Management responsibilities include administering and supporting the systems.
MCSE on Microsoft Windows 2000
The Microsoft Certified Systems Engineer (MCSE) credential is the premier certification for professionals who analyze the business requirements and design and implement the infrastructure for business solutions based on the Microsoft Windows 2000 platform and Microsoft server software, including the Windows .NET Server family. Implementation responsibilities include installing, configuring, and troubleshooting network systems.
MCSD
The Microsoft Certified Solution Developer (MCSD) credential is the premier certification for professionals who design and develop leading-edge business solutions with Microsoft development tools, technologies, platforms, and the Microsoft Windows DNA architecture. The types of applications MCSDs can develop include desktop applications and multi-user, Web-based, N-tier, and transaction-based applications. The credential covers job tasks ranging from analyzing business requirements to maintaining solutions.
10
Introduction
(continued) Certification
Description
MCDBA on Microsoft SQL Server 2000
The Microsoft Certified Database Administrator (MCDBA) credential is the premier certification for professionals who implement and administer Microsoft SQL Server™ databases. The certification is appropriate for individuals who derive physical database designs, develop logical data models, create physical databases, create data services by using Transact-SQL, manage and maintain databases, configure and manage security, monitor and optimize databases, and install and configure SQL Server.
MCP
The Microsoft Certified Professional (MCP) credential is for individuals who have the skills to successfully implement a Microsoft product or technology as part of a business solution in an organization. Hands-on experience with the product is necessary to successfully achieve certification.
MCT
Microsoft Certified Trainers (MCTs) demonstrate the instructional and technical skills that qualify them to deliver Microsoft Official Curriculum through Microsoft Certified Technical Education Centers (Microsoft CTECs).
Certification Requirements The certification requirements differ for each certification category and are specific to the products and job functions addressed by the certification. To become a Microsoft Certified Professional, you must pass rigorous certification exams that provide a valid and reliable measure of technical proficiency and expertise. For More Information See the Microsoft Training and Certification Web site at http://www.microsoft.com/traincert. You can also send e-mail to
[email protected] if you have specific certification questions.
Acquiring the Skills Tested by an MCP Exam Microsoft Official Curriculum (MOC) and MSDN® Training Curriculum can help you develop the skills that you need to do your job. They also complement the experience that you gain while working with Microsoft products and technologies. However, no one-to-one correlation exists between MOC and MSDN Training courses and MCP exams. Microsoft does not expect or intend for the courses to be the sole preparation method for passing MCP exams. Practical product knowledge and experience is also necessary to pass the MCP exams. To help prepare for the MCP exams, use the preparation guides that are available for each exam. Each Exam Preparation Guide contains exam-specific information, such as a list of the topics on which you will be tested. These guides are available on the Microsoft Training and Certification Web site at http://www.microsoft.com/traincert/.
Introduction
11
Facilities Topic Objective
Class Hours
To inform students of class logistics and rules for the training site.
Lead-in
Before we start, let’s go over the class logistics.
Building Hours
Phones
Parking
Messages
Rest Rooms
Smoking
Meals
Recycling
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Explain the class hours, extended building hours for labs, parking, rest room location, meals, phones, message posting, and where smoking is or isn’t allowed. Let students know if your facility has Internet access that is available for them to use during class breaks. Also make sure that the students are aware of the recycling program if one is available.
THIS PAGE INTENTIONALLY LEFT BLANK
Module 1: Overview of the Microsoft .NET Framework Contents Overview Overview of the Microsoft .NET Framework
1 2
Overview of Namespaces
13
Review
17
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, place or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. 2001-2002 Microsoft Corporation. All rights reserved. Microsoft, ActiveX, BizTalk, IntelliMirror, Jscript, MSDN, MS-DOS, MSN, PowerPoint, Visual Basic, Visual C++, Visual C#, Visual Studio, Win32, Windows, Windows Media, and Window NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
Module 1: Overview of the Microsoft .NET Framework
iii
Instructor Notes Presentation: 30 Minutes Lab: 00 Minutes
This module provides students with an overview of the Microsoft® .NET Framework. It defines some of the terminology that is specific to the .NET Framework and describes the key features and benefits of the .NET Framework. The module starts with an overview of the .NET Framework, and then introduces the namespaces in the .NET Framework. It explains which modules teach which namespaces, and which namespaces are not covered in this course. Do not spend too much time on this module. This module is designed to provide only an overview, so do not go into too much detail. This module contains no labs. After completing this module, students will be able to: !
Describe the .NET Framework and its components.
!
Explain the relationship between the .NET Framework class library and namespaces.
Materials and Preparation This section provides the materials and preparation tasks that you need to teach this module.
Required Materials To teach this module, you need the Microsoft PowerPoint® file 2349B_01.ppt.
Preparation Tasks To prepare for this module, you should read all of the materials for this module.
iv
Module 1: Overview of the Microsoft .NET Framework
Module Strategy Use the following strategy to present this module: !
The .NET Framework Explain each part of the .NET Framework. One important goal of this slide is to explain what this course covers. This course primarily teaches the common language runtime and the .NET Framework class library. This course uses Microsoft Visual C#™ to teach the .NET Framework. Only minimal coverage of XML Web services, user interfaces, ADO.NET, and ASP.NET is provided. Other courses will cover these technologies in more detail. In addition, ADO.NET is covered more fully in Module 16, “Using Microsoft ADO.NET to Access Data,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET).
!
Common Language Runtime This is a build slide. Explain each of the following topics as they appear. Definitions of these topics are found in the content. • Class Loader • Microsoft Intermediate Language (MSIL) to Native compilers, Code Manager, and Garbage Collection • Security Engine, Debugger, Type Checker, Exception Manager, Thread Support, and COM Marshaler • .NET Framework Class Library Support
!
The .NET Framework Class Library Explain the benefits of the .NET Framework class library. Explain that the common type system is covered in more detail in Module 5, “Common Type System,” and Module 6, “Working with Types,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET). Specific classes are covered in Module 7, “Strings, Arrays, and Collections,” and other modules as appropriate.
!
ADO.NET: Data and XML This is a build slide. Explain the following topics as they appear. • System.Data Explain how this namespace works primarily with data, such as data from databases. • System.Xml Explain how this namespace works primarily with XML and Extensible Stylesheet Language (XSL).
Module 1: Overview of the Microsoft .NET Framework !
v
What Is an XML Web Service? This slide is the key slide for explaining XML Web services. Be sure that everyone understands the role of Web Services Description Language (WSDL), Universal Description, Discovery, and Integration (UDDI), SOAP, XML, and HTTP. Also explain that the .NET software development kit (SDK) and Microsoft Visual Studio® .NET provide tools to simplify the creation of XML Web services. Note that XML Web services are appropriate for internal applications in addition to external applications. An organization is likely to run multiple platforms; XML Web services are a good way of working across platforms because they rely on XML and SOAP. Any platform that supports XML and SOAP can use or expose XML Web services.
!
Web Forms and XML Web Services Explain how ASP.NET classes make it easier to work with user data on Web pages.
!
Namespaces Briefly explain the purpose of each namespace. The key point to explain is that there are a lot of data types and functionality in the .NET Framework. Namespaces arrange the types in a hierarchy that make it easier to work with the types.
!
Namespaces Used in this Course Briefly explain which modules teach which namespaces. Explain that not all of the namespaces are taught in their entirety. For example, the System.Reflection namespace is mentioned in Module 4, “Deployment and Versioning,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET), only in terms of versioning. System.Reflection is also mentioned in Module 17, “Attributes,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET). Also, explain that the ADO.NET namespace is covered more fully in Module 16, “Using Microsoft ADO.NET to Access Data,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET). The security namespace is not taught in this course as security is covered extensively in Course 2350A, Securing and Deploying Microsoft .NET Assemblies.
Module 1: Overview of the Microsoft .NET Framework
1
Overview Topic Objective
To provide an overview of the module topics and objectives.
Lead-in
!
Overview of the Microsoft .NET Framework
!
Overview of Namespaces
In this module, you will be introduced to the .NET Framework. You will then learn about the namespaces and which modules teach certain namespaces.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The Microsoft® .NET Framework provides tools and technologies that you need to build distributed Web applications. In this module, you will learn the architecture of the .NET Framework. You will also learn how the .NET Framework class library is divided into namespaces, and which namespaces are taught in this course. After completing this module, you will be able to: !
Describe the .NET Framework and its components.
!
Explain the relationship between the .NET Framework class library and namespaces.
2
Module 1: Overview of the Microsoft .NET Framework
" Overview of the Microsoft .NET Framework Topic Objective
To provide an overview of the .NET Framework topics.
Lead-in
In this section, you will learn about the Microsoft .NET Framework.
!
The .NET Framework
!
Common Language Runtime
!
The .NET Framework Class Library
!
ADO.NET: Data and XML
!
What is an XML Web Service?
!
Web Forms and Services
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this section, you will learn about the .NET Framework. The .NET Framework is a set of technologies that form an integral part of the Microsoft .NET platform. It provides the basic building blocks for developing Web applications and XML Web services. This section includes the following topics: !
The .NET Framework
!
Common Language Runtime
!
The .NET Framework Class Library
!
ADO.NET: Data and XML
!
What is an XML Web Service?
!
Web Forms and Services
Module 1: Overview of the Microsoft .NET Framework
3
The .NET Framework Topic Objective
To understand the architecture of the .NET Framework.
Lead-in
VB
C++
C#
Perl
Web Services
Python
…
User Interface ASP.NET
The .NET Framework is an architecture consisting of a runtime, the class library, and language support.
ADO.NET: Data and XML .NET Framework Class Library Common Language Runtime Message Queuing
COM+ (Transactions, Partitions, Object Pooling)
IIS
WMI
Win32
*****************************ILLEGAL FOR NON-TRAINER USE******************************
The .NET Framework The .NET Framework provides the necessary compile time and runtime foundation to build and run .NET applications. The .NET Framework is the primary focus of this course.
Platform Substrate The .NET Framework must run on an operating system. Currently, the .NET Framework is built to work on the Microsoft Win32® operating systems. In the future, the .NET Framework will be extended to run on other platforms, such as Microsoft Windows® CE.
Application Services When running on Microsoft Windows 2000, application services, such as COM+, Message Queuing, Windows Internet Information Server (IIS), and Windows Management Instrumentation (WMI), are available to the developer. The .NET Framework exposes application services through classes in the .NET Framework class library.
Common Language Runtime The common language runtime simplifies application development, provides a robust and secure execution environment, supports multiple languages, and simplifies application deployment and management. The common language runtime environment is also referred to as a managed environment, in which common services, such as garbage collection and security, are automatically provided.
4
Module 1: Overview of the Microsoft .NET Framework
The .NET Framework Class Library The .NET Framework class library exposes features of the runtime and provides other high-level services that every developer needs. The classes simplify development of .NET applications. Developers can extend them by creating their own libraries of classes.
ADO.NET ADO.NET is the next generation of Microsoft ActiveX® Data Object (ADO) technology. ADO.NET provides improved support for the disconnected programming model. It also provides rich XML support.
ASP.NET Microsoft ASP.NET is a programming framework that is built on the common language runtime. ASP.NET can be used on a server to build powerful Web applications. ASP.NET Web Forms provide an easy and powerful way to build dynamic Web user interfaces (UI).
XML Web Services The .NET Framework provides tools and classes for building, testing, and distributing XML Web services.
User Interfaces The .NET Framework supports three types of user interfaces: !
Web Forms, which work through ASP.NET
!
Windows Forms, which run on Win32 clients
!
Console applications, which for simplicity, are used for most of the labs in this course
Module 1: Overview of the Microsoft .NET Framework
5
Languages Any language that conforms to the Common Language Specification (CLS) can run on the common language runtime. In the .NET Framework, Microsoft provides Microsoft Visual Basic®, Microsoft Visual C++®, Microsoft Visual C#™, and Microsoft JScript® support. Third parties can provide additional languages. Note C# has been submitted for standardization to ECMA, a vendor-neutral international standards organization committed to driving industry-wide adoption of information and communications technologies. This standardization will make it possible for any company which wishes to implement C# programming tools on any platform to do so. Microsoft has also submitted a subset of the Microsoft .NET Framework, called the Common Language Infrastructure (CLI), to ECMA. This will make it possible for other vendors to implement the CLI on a variety of platforms, so that software written using the basic architectural model presented by the .NET Framework can be created using a variety of tools on a variety of platforms.
Building Components in the .NET Framework In the .NET Framework, components are built on a common foundation. You no longer need to write the code to allow objects to interact directly with each other. In addition, you no longer need to write component wrappers in the .NET environment, because components do not use wrappers. The .NET Framework can interpret the constructs that developers are accustomed to using in objectoriented languages. The .NET Framework fully supports class, inheritance, methods, properties, events, polymorphism, constructors, and other objectoriented constructs.
6
Module 1: Overview of the Microsoft .NET Framework
Common Language Runtime Topic Objective
.NET Framework Class Library Support
To highlight some of the key components in the common language runtime.
Lead-in
This topic will give you an overview of the components of the common language runtime. I will briefly describe each component. As a C# developer, you will never see these discrete pieces, but this discussion will give you a better understanding of the richness of the runtime.
Thread Support
COM Marshaler
Type Checker
Exception Manager
Security Engine
Debugger
MSIL to Native Compilers
Code Manager
Garbage Collection
Class Loader
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The common language runtime simplifies application development, provides a robust and secure execution environment, supports multiple languages, and simplifies application deployment and management. The common language runtime environment is also referred to as a managed environment, one in which common services, such as garbage collection and security, are automatically provided. The common language runtime features are described in the following table. Component
Description
Class loader
Manages metadata, and the loading and layout of classes.
Microsoft Intermediate Language (MSIL) to native compiler
Converts MSIL to native code on a just-in-time basis.
Code manager
Manages code execution.
Garbage collection
Provides automatic lifetime management of all of objects in the .NET Framework, garbage collection is a multiprocessor, scalable garbage collector.
Security engine
Provides evidence-based security, based on user identity and the origin of the code.
Debugger
Enables the developer to debug an application and trace the execution of code.
Type checker
Does not allow unsafe casts or uninitialized variables. MSIL can be verified to guarantee type safety.
Exception manager
Provides structured exception handling, which is integrated with Windows Structured Exception Handling (SEH). Error reporting has been improved.
Thread support
Provides classes and interfaces that enable multithreaded programming.
COM marshaler
Provides marshaling to and from COM.
.NET Framework class library support
Integrates code with the runtime that supports the .NET Framework class library.
Module 1: Overview of the Microsoft .NET Framework
7
The .NET Framework Class Library Topic Objective
To provide an overview of the .NET Framework class library and the most common namespace: System.
Lead-in
In this topic, you will learn how .NET Framework class library exposes features of the runtime and provides other high-level services.
!
!
Spans All Programming Languages #
Enables cross-language inheritance and debugging
#
Integrates well with tools
Is Object-Oriented and Consistent #
Enhances developer productivity by reducing the number of APIs to learn
!
Has a Built-In Common Type System
!
Is Extensible
!
Is Secure
#
#
Makes it easy to add or modify framework features Allows creation of secure applications
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The .NET Framework class library exposes features of the runtime and provides other high-level services that every developer needs. Because there are hundreds of classes in the .NET Framework class library, classes are grouped into namespaces. The first part of the full name, which is located before the rightmost dot, is the namespace name. The last part of the name, which is located after the dot, is the type name. For example, System.Collections.ArrayList represents the ArrayList class, which belongs to the System.Collections namespace. The types in System.Collections namespace can be used to manipulate collections of objects.
Spans All Programming Languages The .NET Framework class library is language-independent so it enables crosslanguage inheritance and debugging. The .NET Framework class library also integrates fully with Microsoft Visual Studio® .NET, making it easy to develop applications with the library.
Is Object-Oriented and Consistent Unlike flat APIs that are numerous and unorganized, the .NET Framework class library is organized into namespaces and classes. This object-oriented approach groups related functionality and data together and enables the developer to work with the library in a more natural way.
8
Module 1: Overview of the Microsoft .NET Framework
Has a Built-In Common Type System The .NET Framework class library is type-safe. Type safety is ensured through the common type system, which is part of the common language runtime.
Is Extensible You can extend the library by creating your own classes and compiling them into libraries. If designed properly, your class library will also be objectoriented and language-independent.
Is Secure The .NET Framework class library provides rich security for your applications. You can use code access security and role-based security, and configure your own security policies. Furthermore, there are numerous security tools to assist in certificate creation, permission viewing, and so on.
Module 1: Overview of the Microsoft .NET Framework
9
ADO.NET: Data and XML Topic Objective
To explain the data and XML support in the runtime.
ADO.NET: Data and XML
Lead-in
The .NET Framework provides a new set of ADO.NET classes to handle data.
System.Data
System.Xml
OleDb
SqlClient
XSL
Common
SQLTypes
XPath
Serialization
*****************************ILLEGAL FOR NON-TRAINER USE****************************** ADO.NET, the next generation of ADO technology provides improved support for disconnected programming. It also provides rich XML support in the System.Xml namespace.
System.Data Namespace The System.Data namespace consists of classes that constitute the ADO.NET object model. At a high level, the ADO.NET object model is divided into two layers: the connected layer and the disconnected layer. The System.Data namespace includes the DataSet class, which represents multiple tables and their relations. These data sets are completely self-contained data structures that can be populated from a variety of data sources. One data source could be XML; another data source could be an OLE DB; and a third data source could be the direct adapter for Microsoft SQL Server™.
System.Xml Namespace The System.Xml namespace provides support for XML. It includes an XML parser and a writer, which are W3C-compliant. The Extensible Stylesheet Language for Transformation (XSLT) is provided by the System.Xml.Xsl namespace. The implementation of XPath, a comprehensive language for document addressing, enables data graph navigation in XML. The System.Xml.Serialization namespace provides the entire core infrastructure for XML Web services, including such features as moving back and forth from objects to an XML representation.
10
Module 1: Overview of the Microsoft .NET Framework
What Is an XML Web Service? Topic Objective
Open Internet Protocols
To define an XML Web service.
Lead-in
XML Web services are the fundamental building blocks of the .NET platform.
XML Web service A programmable application component accessible via standard Web protocols
!
Provide a Directory of Services on the Internet
!
XML Web services are defined in terms of the formats and ordering of messages
!
XML Web services consumers can send and receive messages using XML
!
Built using open Internet protocols
UDDI
Universal Description, Discovery, and Integration
WSDL
Web Services Description Language
SOAP XML & HTTP
*****************************ILLEGAL FOR NON-TRAINER USE****************************** XML Web services are an integral part of the .NET platform. They are the fundamental mechanism for exposing and consuming data and functionality across Web applications, both inside organizations and across organizations. XML Web services are units of application logic that provide data and services to other applications. Applications access XML Web services by means of industry standard Web protocols and data formats, such as HTTP, XML, and Simple Object Access Protocol (SOAP), regardless of how each XML Web service is implemented.
XML and HTTP XML Web services are built by using XML and HTTP. Because they are built with XML and HTTP, XML Web services operate without firewall restrictions. Also, because XML and HTTP are industry standards, any platform supporting XML and HTTP can work with XML Web services.
SOAP SOAP defines how messages are formatted, sent, and received when working with XML Web services. SOAP is also an industry standard that is built on XML and HTTP. Any platform that supports the SOAP standard can support XML Web services.
Module 1: Overview of the Microsoft .NET Framework
11
Web Services Description Language The Web Services Description Language (WSDL) is an XML format for describing the network services that are offered by the server. You use WSDL to create a file that identifies the services that are provided by the server and the set of operations within each service that the server supports. For each of the operations, the WSDL file also describes the format that the client must follow when requesting an operation.
Universal Description, Discovery, and Integration Universal Description, Discovery, and Integration (UDDI) is an industry standard for registering and searching for XML Web services. By using UDDI, developers can discover and use XML Web services that are available publicly over the Internet. For more information on UDDI, see Web Service Discovery in Module 13, “Remoting and XML Web Services,” in Course 2349B, Programming with the Microsoft.NET Framework (Microsoft Visual C# .NET), and the UDDI Web site at http://www.uddi.org.
12
Module 1: Overview of the Microsoft .NET Framework
Web Forms and Services Topic Objective
To show where Web Forms and XML Web services are found in the ASP.NET programming model.
ASP.NET System.Web
Lead-in
Services
The Internet is quickly evolving from today’s Web sites that simply deliver UI pages to browsers to a next generation of programmable Web sites that link organizations, applications, services, and devices directly.
UI
Description
HtmlControls
Discovery
WebControls
Protocols Caching
Security
Configuration
SessionState
*****************************ILLEGAL FOR NON-TRAINER USE****************************** ASP.NET is a programming framework built on the common language runtime that can be used on a server to build powerful Web applications. ASP.NET Web Forms provide an easy and powerful way to build dynamic Web UI pages. ASP.NET XML Web services provide the building blocks for constructing distributed Web-based applications. XML Web services are based on open Internet standards, such as HTTP and XML. The common language runtime provides built-in support for creating and exposing XML Web services by using a programming abstraction that is consistent and familiar to both ASP Web Forms and Visual Basic developers. The resulting model is both scalable and extensible. This model is based on open Internet standards, such as HTTP, XML, SOAP, and WSDL, so it can be accessed and interpreted by any client or Internet-enabled device. Some of the more common ASP.NET classes are described in this topic as follows:
System.Web In the System.Web namespace, there are lower-level services, such as caching, security, and configuration, which are shared between XML Web services and Web UIs.
System.Web.Services The System.Web.Services namespace has classes that handle XML Web services, such as protocols and discovery.
Controls There are two types of controls: HTML controls and Web controls. The System.Web.UI.HtmlControls namespace gives you direct mapping of HTML tags, such as input. The System.Web.UI.WebControls namespace enables you to structure controls with templates, such as grid controls.
Module 1: Overview of the Microsoft .NET Framework
13
" Overview of Namespaces Topic Objective
To provide an overview of the namespaces in the .NET Framework.
Lead-in
In this section, you will learn about the namespaces in the .NET Framework.
!
Namespaces
!
Namespaces Used in this Course
!
Namespaces Covered in Optional Modules
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this section, you will learn about the namespaces in the Microsoft .NET Framework. You will also learn about which namespaces are taught in this course. This section includes the following topics: !
Namespaces
!
Namespaces Used in this Course
!
Namespaces Covered in Optional Modules
14
Module 1: Overview of the Microsoft .NET Framework
Namespaces Topic Objective
To understand how namespaces provide an easy-to-use hierarchy of types and functionality.
Lead-in
The .NET Framework includes a large set of class library assemblies, which contain hundreds of types. These assemblies provide access to system functionality in your development process.
System Collections
IO
Security
Configuration
Net
ServiceProcess
Diagnostics
Reflection
Text
Globalization
Resources
Threading
Runtime
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The .NET Framework includes a large set of class library assemblies, which contain hundreds of types. These assemblies provide access to system functionality in your development process.
The Purpose of Namespaces Because the .NET Framework class library includes definitions for so many types, the library is organized in a hierarchical namespace structure. Namespaces use a dot-syntax naming scheme to group logically related classes together so that they can be easily searched and referenced. For example, the System.Data namespace contains the classes that constitute the ADO.NET architecture. The System.Xml namespace is the overall namespace for the XML classes that provide standards-based support for processing XML.
The System Namespace The System namespace is the root namespace for types in the .NET Framework. The System namespace contains the base type Object, from which all other types are derived. The System namespace also contains types for exception handling, garbage collection, console I/O, various tool types, format data types, random number generators, and math functions.
Module 1: Overview of the Microsoft .NET Framework
15
Namespaces Used in this Course Topic Objective
To explain which namespaces are taught in this course, and which namespaces are not taught.
Lead-in
This course covers many of the System namespaces. Not all namespaces are covered, for example the System.Security namespaces.
Module 2 #
System.Console
Module 3
Module 10 #
System.IO
Module 11
#
System.Windows.Forms
#
System.Net
#
System.Drawing
#
System.Net.Sockets
Module 4 #
System.Reflection
Module 7 #
System.Text
#
System.Collections
Module 12 #
System.Runtime.Serialization
Module 13 #
System.Runtime.Remoting.Channels
#
System.Web.Services
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This course covers many of the namespaces in the Microsoft .NET Framework. Module 2 teaches the System.Console namespace for printing output to the console. Module 3 teaches the System.Windows.Forms and System.Drawing namespaces for building a form with buttons that interacts with the user. Module 4 teaches the System.Reflection namespace for storing version and key file information in an assembly. Module 7 teaches the System.Text namespace for advanced string management, and System.Collections for maintaining collections of data. Module 10 teaches the System.IO namespace for reading and writing to files. Module 11 teaches the System.Net and System.Net.Sockets namespaces for transmitting data over the network. Module 12 teaches the System.Runtime.Serialization namespace for persisting objects to storage. Module 13 teaches the System.Runtime.Remoting.Channels and System.Web.Services namespaces for invoking remote objects, and building XML Web services.
16
Module 1: Overview of the Microsoft .NET Framework
Namespaces Covered in Optional Modules Topic Objective
To complete the namespace information that was presented in the preceding slide.
Lead-in
Here are some more namespaces that are covered in optional Modules 14 through 17 of this course.
Module 14 #
System.Threading
Module 16 #
System.Data
Module 17 #
System.Reflection
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Modules 14 through 17 are optional modules. Module 14 teaches the System.Threading namespace for enabling multithreaded programming. Module 16 teaches the System.Data namespace, which provides the base objects and types for the ADO.NET programming model. ADO.NET also provides rich XML support in the System.Xml namespace. Finally, Module 17 teaches the System.Reflection namespace, which contains classes that you can use for examining metadata.
Namespaces Not Covered This course does not teach the System.Security namespace. For more information about System.Security and related security namespaces, see Course 2350A, Securing and Deploying Microsoft .NET Assemblies.
Module 1: Overview of the Microsoft .NET Framework
17
Review Topic Objective
To reinforce module objectives by reviewing key points.
Lead-in
!
Overview of the Microsoft .NET Framework
!
Overview of Namespaces
The review questions cover some of the key concepts taught in the module.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** 1. List the components of the .NET Framework. The common language runtime, .NET Framework class library, data and XML, XML Web services and Web Forms, and Windows Forms.
2. What is the purpose of the common language runtime? It provides an environment in which you can execute code.
3. What is the purpose of the common language specification? It defines a set of features that all .NET languages should support.
4. What is an XML Web service? An XML Web service is a programmable Web component that can be shared among applications on the Internet or an intranet.
5. What is a managed environment? A managed environment is an environment that provides services, such as garbage collection, security, and other related features.
THIS PAGE INTENTIONALLY LEFT BLANK
Module 2: Introduction to a Managed Execution Environment Contents Overview Writing a .NET Application Compiling and Running a .NET Application
1 2 11
Lab 2: Building a Simple .NET Application
29
Review
32
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, place or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. 2001-2002 Microsoft Corporation. All rights reserved. Microsoft, ActiveX, BizTalk, IntelliMirror, Jscript, MSDN, MS-DOS, MSN, PowerPoint, Visual Basic, Visual C++, Visual C#, Visual Studio, Win32, Windows, Windows Media, and Window NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
Module 2: Introduction to a Managed Execution Environment
Instructor Notes Presentation: 45 Minutes Lab: 20 Minutes
After completing this module, students will be able to: !
Create simple console applications in C#.
!
Explain how code is compiled and executed in a managed execution environment.
!
Explain the concept of garbage collection.
Materials and Preparation This section provides the materials and preparation tasks that you need to teach this module.
Required Materials To teach this module, you need the following materials: !
Microsoft® PowerPoint® file 2349B_02.ppt
!
Sample managed module HelloDemoCS.exe
Preparation Tasks To prepare for this module, you should: !
Read all of the materials for this module.
!
Practice the demonstrations.
!
Review the animation.
!
Complete the lab.
iii
iv
Module 2: Introduction to a Managed Execution Environment
Demonstrations This section provides demonstration procedures that will not fit in the margin notes or are not appropriate for the student notes.
Hello World This demonstration shows how to build a simple application in C#. In the following procedures, use Notepad to create the simple Hello World application, and build and run the HelloDemoCS.exe application from the command line.
! To create the source code in C# 1. Open Notepad and type the following code: // Allow easy reference to System namespace classes using System; // Create class to hold program entry point class MainApp { public static void Main() { // Write text to the console Console.WriteLine(“Hello World using C#!”); } }
2. Save the file as HelloDemoCS.cs. Important To use Microsoft Visual Studio® .NET tools within a command prompt window, the command prompt window must have the proper environment settings. The Visual Studio .NET Command Prompt window provides such an environment. To run a Visual Studio .NET Command Prompt window, click Start, All Programs, Microsoft Visual Studio .NET, Visual Studio .NET Tools, and Visual Studio .NET Command Prompt.
Module 2: Introduction to a Managed Execution Environment
! To compile the source code and build an executable program • From a Visual Studio .NET Command Prompt window, type the following syntax: csc HelloDemoCS.cs
Running the resulting executable file will generate the following output: Hello World using C#!
Viewing Assembly Metadata by Using the MSIL Disassembler This demonstration shows how to use the Microsoft Intermediate Language (MSIL) Disassembler (Ildasm.exe) to view an assembly’s metadata. The code for this demonstration is contained in one project and is located in
\Democode\Mod02. To demonstrate how to use the MSIL Disassembler to view the contents of the HelloDemoCS.exe assembly, follow the directions in Demonstration: Using the MSIL Disassembler in this module.
v
vi
Module 2: Introduction to a Managed Execution Environment
Multimedia This section lists the multimedia items that are part of this module. Instructions for launching and playing the multimedia are included with the relevant slides.
Application Loading and Single-File Assembly Execution This animation will show students how a single-file private assembly is loaded and executed.
Module Strategy Use the following strategy to present this module: !
Writing a .NET Application Stress the importance of understanding the process of compiling and running Microsoft .NET Framework applications, by using the simple Hello World application. Focus primarily on the compilation and execution processes.
!
Compiling and Running a .NET Application This section introduces basic concepts of a managed execution environment and presents new terminology. Many of these concepts are covered in greater detail in subsequent modules in this course, in subsequent courses, and in the .NET Framework software development kit (SDK) documentation. Emphasize that you are primarily introducing new concepts and terminology. Be prepared to postpone answering questions that pertain to information that is covered in later modules. Encourage students to start reading the .NET Framework SDK documentation.
Module 2: Introduction to a Managed Execution Environment
1
Overview Topic Objective
To provide an overview of the module topics and objectives.
Lead-in
!
Writing a .NET Application
!
Compiling and Running a .NET Application
This module introduces the concept of managed execution and shows you how to quickly build applications that use the Microsoft .NET Framework common language runtime environment.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This module introduces the concept of managed execution and shows you how to quickly build applications that use the Microsoft® .NET Framework common language runtime environment. A simple Hello World version of a console application illustrates most of the concepts that are introduced in this module. Because this course is an introduction to programming in the .NET Framework, you should spend some time reading the .NET Framework software development kit (SDK) documentation. In fact, the labs, demonstrations, and material for this module and other modules in this course are based on several tutorials in the .NET Framework SDK. After completing this module, you will be able to: !
Create simple console applications in C#.
!
Explain how code is compiled and executed in a managed execution environment.
!
Explain the concept of garbage collection.
2
Module 2: Introduction to a Managed Execution Environment
" Writing a .NET Application Topic Objective
To introduce the topics in the section.
Lead-in
Because all supported languages use the Common Type System and the .NET Framework class library, and run in the common language runtime, programs in the supported languages are similar.
!
Using a Namespace
!
Defining a Namespace and a Class
!
Entry Points, Scope, and Declarations
!
Console Input and Output
!
Case Sensitivity
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Because all supported languages use the Common Type System and the .NET Framework base class library, and run in the common language runtime, programs in the supported languages are similar. The most significant difference in programming with the supported languages is syntax. Delivery Tip
Stress the importance of understanding the process of compiling and running the .NET applications. Using Visual Studio .NET at this time may obscure the underlying processes.
Note In this module, and in Modules 3 and 4, Notepad is used as the source code editor, instead of the Microsoft Visual Studio® .NET development environment. The examples in these modules are simple enough to be compiled and built directly from a command prompt window. Working in Notepad will allow you to focus on the compilation and execution processes.
Module 2: Introduction to a Managed Execution Environment
3
Demonstration: Hello World Topic Objective
To demonstrate how to build a simple application in C#.
Lead-in
In this demonstration, you will learn how to build a simple application in C#.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this demonstration, you will learn how to build a simple application in C#. Delivery Tip
As this is a short, simple demonstration, you may want to let students try it themselves.
! To create the source code in C# 1. Open Notepad and type the following code: // Allow easy reference to System namespace classes using System; // Create class to hold program entry point class MainApp { public static void Main() { // Write text to the console Console.WriteLine(“Hello World using C#!”); } }
2. Save the file as HelloDemoCS.cs
4
Module 2: Introduction to a Managed Execution Environment
! To compile the source code and build an executable program Important To use Visual Studio .NET tools within a command prompt window, the command prompt window must have the proper environment settings. The Visual Studio .NET Command Prompt window provides such an environment. To run a Visual Studio .NET Command Prompt window, click Start, All Programs, Microsoft Visual Studio .NET, Visual Studio .NET Tools, and Visual Studio .NET Command Prompt. • From a Visual Studio .NET Command Prompt window, type the following syntax: csc HelloDemoCS.cs
Running the resulting executable will generate the following output: Hello World using C#!
Module 2: Introduction to a Managed Execution Environment
5
Using a Namespace Topic Objective
To describe how to use namespaces in the .NET Framework.
!
Classes Can Be Fully Referenced // // declares declares aa FileStream FileStream object object System.IO.FileStream System.IO.FileStream aFileStream; aFileStream;
Lead-in
You can fully reference classes in which an instance of System.IO.FileStream is declared by using C#:
!
Or the Namespace of a Class Can Be Referenced #
No need to fully qualify contained class names using using System.IO; System.IO; ... ... FileStream FileStream aFileStream; aFileStream;
*****************************ILLEGAL FOR NON-TRAINER USE****************************** You can fully reference classes, as in the following example, in which an instance of System.IO.FileStream is declared by using C#: System.IO.Filestream aFileStream;
However, it is more convenient to reference the required namespaces in your program. Using the namespace effectively disposes of the need to qualify all class library references, as in the following example: using System.IO; ... FileStream aFileStream;
For example, in order to have convenient access to System objects, you must use the System namespace.
6
Module 2: Introduction to a Managed Execution Environment
Defining a Namespace and a Class Topic Objective
To describe how to define namespaces and classes in C#.
Lead-in
C# supports the creation of custom namespaces and classes within those namespaces.
!
C# Supports Creation of Custom Namespaces and Classes Within Those Namespaces namespace namespace CompCS CompCS {{ public public class class StringComponent StringComponent {{ ... ... }} }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** C# supports the creation of custom namespaces and classes within those namespaces. Tip The following is the general rule for naming namespaces: CompanyName.TechnologyName For example: Microsoft.Office This is merely a guideline. Third-party companies can choose other names.
Namespaces in C# In C#, you use the namespace statement to define a new namespace, which encapsulates the classes that you create, as in the following example: namespace CompCS { public class StringComponent { ... } }
Note that a namespace may be nested in other namespaces, and a single namespace may be defined in multiple files. A single source code file may also define multiple namespaces.
Module 2: Introduction to a Managed Execution Environment
7
Entry Points, Scope, and Declarations Topic Objective
To describe how to create program entry points in C#.
!
In C#, the External Entry Point for a Program Is in a Class
!
class class MainApp MainApp {{ public public static static void void Main() Main() {. . .} {. . .} }} C# Supports the Use of a Period As a Scope Resolution Operator
Lead-in
Every executable program must contain an external entry point, where the application begins its execution.
Console.WriteLine Console.WriteLine ("First ("First String"); String"); !
In C#, Objects Must Be Declared Before They Can Be Used and Are Instantiated Using the New Keyword Lib.Comp Lib.Comp myComp myComp == new new Lib.Comp(); Lib.Comp();
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Every executable program must contain an external entry point, where the application begins its execution. In C#, all code must be contained in methods of a class.
Entry Points in C# To accommodate the entry point code in C#, you must first specify the class, as in the following example: class MainApp
{...}
Next, you specify the entry point for your program. The compiler requires this entry point to be a public static method called Main, as in the following example: public static void Main () {...}
Scope C# uses the period as a scope resolution operator. For example, you use the syntax Console.WriteLine when referencing the WriteLine method of the Console object.
Declaring and Instantiating Variables In C#, you must declare a variable before it can be used. To instantiate the object, use the new keyword. The following example in C# shows how to declare an object of type Comp, in namespace Lib, with the name myComp: Lib.Comp myComp = new Lib.Comp();
8
Module 2: Introduction to a Managed Execution Environment
Console Input and Output Topic Objective
To describe how to use Console class methods in C#.
!
Lead-in
Console Class Methods #
You can use the runtime Console class of the System namespace for input and output to the console of any string or numeric value by using the Read, ReadLine, Write, and WriteLine methods.
Read, ReadLine, Write, and WriteLine
Console.WriteLine("Hello Console.WriteLine("Hello World World using using C#!"); C#!");
*****************************ILLEGAL FOR NON-TRAINER USE****************************** You can use the common language runtime Console class of the System namespace for input and output to the console of any string or numeric value by using the Read, ReadLine, Write, and WriteLine methods. The following example shows a C# program that outputs a string to the console: using System; class MainApp { public static void Main() { // Write text to the console Console.WriteLine(“Hello World using C#!”); } }
Module 2: Introduction to a Managed Execution Environment
9
Case Sensitivity Topic Objective
To describe case sensitivity issues in programming languages.
!
Do Not Use Names That Require Case Sensitivity #
Components should be fully usable from both casesensitive and case-insensitive languages
#
Case should not be used to distinguish between identifiers within a single name scope
Lead-in
C++ and C# are casesensitive, but Visual Basic is not case-sensitive. !
Avoid the Following class class class class
customer customer Customer Customer
{...} {...} {...} {...}
void void foo(int foo(int X, X, int int x) x)
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Microsoft Visual C++® and C# are case-sensitive, but Microsoft Visual Basic® is not case-sensitive. To ensure that a program is compliant with the Common Language Specification (CLS), however, you must take special care with public names. You cannot use case to distinguish between identifiers within a single name scope, such as types within assemblies and members within types. The following examples show situations to avoid: !
Do not have two classes or namespaces whose names differ only by case. class customer { ... } class Customer { ... }
!
Do not have a function with two parameters whose names differ only by case. void foo(int X, int x)
This constraint enables Visual Basic (and potentially other case-insensitive languages) to produce and use components that have been created in other casesensitive languages. This constraint does not apply to your definitions of private classes, private methods on public classes, or local variables.
10
Module 2: Introduction to a Managed Execution Environment
Note To fully interact with other objects regardless of the language they were implemented in, objects must expose to callers only those features that are common to all the languages they must interoperate with. For this reason, a set of language features has been defined, called the Common Language Specification (CLS), which includes common language features that are needed by many applications. The CLS rules define a subset of the common type system; that is, all the rules that apply to the common type system apply to the CLS, except where stricter rules are defined in the CLS. If your component uses only CLS features in the API that it exposes to other code (including derived classes), the component is guaranteed to be accessible from any programming language that supports the CLS. Components that adhere to the CLS rules and use only the features included in the CLS are said to be CLS-compliant components.
Module 2: Introduction to a Managed Execution Environment
11
" Compiling and Running a .NET Application Topic Objective
To introduce the topics in the section.
!
Compiler Options
Lead-in
!
The Process of Managed Execution
!
Metadata
!
Microsoft Intermediate Language
!
Assemblies
!
Common Language Runtime Tools
!
Just-In-Time Compilation
!
Application Domains
!
Garbage Collection
Most aspects of programming in the .NET Framework are the same for all compatible languages; each supported language compiler produces selfdescribing, managed Microsoft intermediate language (MSIL) code.
*****************************ILLEGAL FOR NON-TRAINER USE******************************
Delivery Tip
Emphasize that you are primarily introducing new concepts and terminology. Be prepared to postpone answering questions that pertain to later modules. Encourage students to start reading the .NET Framework SDK documentation.
Key Points
String literals in an application are stored and transported as clear text. Therefore, you should avoid putting sensitive information such as passwords in string literals.
Most aspects of programming in the .NET Framework are the same for all compatible languages; each supported language compiler produces selfdescribing, managed Microsoft intermediate language (MSIL) code. All managed code runs using the common language runtime, which provides cross-language integration, automatic memory management, cross-language exception handling, enhanced security, and a consistent and simplified programming model. This section introduces basic concepts of a managed execution environment and presents new terminology. Many of these concepts are covered in greater detail in subsequent modules in this course, in subsequent courses, and in the .NET Framework SDK documentation.
12
Module 2: Introduction to a Managed Execution Environment
Compiler Options Topic Objective
To introduce compiler options in C#.
!
Compile Directly from a Command Prompt Window
Lead-in
The .NET Framework includes a command line compiler for C#.
>csc >csc HelloDemoCS.cs HelloDemoCS.cs !
Use /t to indicate target >csc >csc /t:exe /t:exe HelloDemoCS.cs HelloDemoCS.cs
!
Use /reference to reference assemblies
>csc >csc /t:exe /t:exe /reference:assemb1.dll /reference:assemb1.dll HelloDemoCS.cs HelloDemoCS.cs
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The .NET Framework includes a command line compiler for C#. The file name of the compiler is Csc.exe.
Compiling in C# To compile the source code for the Hello World application presented in the Hello World demonstration at the beginning of this module, type the following: csc HelloDemoCS.cs
This syntax invokes the C# compiler. In this example, you only need to specify the name of the file to be compiled. The compiler generates the program executable, HelloDemoCS.exe.
Module 2: Introduction to a Managed Execution Environment
13
Command Line Options In C#, you can obtain the complete list of command line options by using the /? switch as follows: csc /?
Common options include the /out switch, which specifies the name of the output file, and the /target switch, which specifies the target type. By default, the name of the output file is the name of the input file with an .exe extension. The default for the target type is an executable program. The following example shows the use of both the /out and /t switches in C#: csc /out:HelloDemoCS.exe /t:exe HelloDemoCS.cs
The /t switch is equivalent to the /target switch. For more information about compiler options, see the .NET Framework SDK documentation.
Using the /reference Compilation Option When referring to other assemblies, you must use the /reference compilation switch. The /reference compilation option allows the compiler to make information in the specified libraries available to the source that is currently being compiled. The following example shows how to build an executable program from the command line by using the /reference compilation option. csc /r:assemb1.dll,assemb2.dll /out:output.exe input.cs
The /r switch is equivalent to the /reference compilation switch.
14
Module 2: Introduction to a Managed Execution Environment
The Process of Managed Execution Topic Objective
EXE/DLL EXE/DLL (MSIL (MSILand and metadata) metadata)
To introduce fundamental concepts of compiling and executing code in a managed execution environment. In the .NET Framework, the common language runtime provides the infrastructure for a managed execution environment.
Source Source Code Code
Class Class Loader Loader
Class Class Libraries Libraries (MSIL (MSILand and metadata) metadata)
Lead-in
Compiler Compiler
JIT JIT Compiler Compiler with with optional optional verification verification Trusted, pre-JITed code only
Managed Native Code
Call to an uncompiled method
Execution Execution Security Security Checks Checks
Runtime Engine
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In the .NET Framework, the common language runtime provides the infrastructure for a managed execution environment. This topic introduces fundamental concepts of compiling and executing code in a managed execution environment and identifies new terminology.
Compiling Source Code When you develop an application in the .NET Framework, you can write the source code in any programming language as long as the compiler that you use to compile the code targets the common language runtime. Compilation of the source code produces a managed module. The managed module is contained within a physical file also known as a portable executable (PE) file. The file may contain the following items: !
Microsoft Intermediate Language (MSIL) The compiler translates the source code into MSIL, a CPU-independent set of instructions that can be efficiently converted to native code.
!
Type Metadata This information fully describes types, members, and other references, and is used by the common language runtime at run time.
!
A Set of Resources For example, .bmp or .jpg files.
Module 2: Introduction to a Managed Execution Environment
15
If the C# compiler’s target option is either exe or library, then the compiler produces a managed module that is an assembly. Assemblies are a fundamental part of programming with the .NET Framework. Assemblies are the fundamental units of sharing, deployment, security, and versioning in the common language runtime. The .NET common language runtime only executes MSIL code that is contained in an assembly. If the C# compiler’s target option is module, then the compiler produces a managed module that is not an assembly, it does not contain a manifest and cannot be executed by the common language runtime. A managed module can be added to an assembly by the C# compiler, or by using the .NET’s Assembly Generation Tool, Al.exe. Subsequent topics in this module cover MSIL, metadata, and assemblies in more detail.
Executing Code When a user executes a managed application, the operating system loader loads the common language runtime, which then begins executing the module’s managed MSIL code. Because current host CPUs cannot execute the MSIL instructions directly, the common language runtime must first convert the MSIL instructions into native code. The common language runtime does not convert all of the module’s MSIL code into CPU instructions at load time. Instead, it converts the instructions when functions are called. The MSIL is compiled only when needed. The component of the common language runtime that performs this function is called the justin-time (JIT) compiler. JIT compilation conserves memory and saves time during application initialization. For more information about the JIT compiler, see Just-In-Time Compilation in this module.
Application Domain Operating systems and runtime environments typically provide some form of isolation between applications. This isolation is necessary to ensure that code running in one application cannot adversely affect other, unrelated applications. Application domains provide a secure and versatile unit of processing that the common language runtime can use to provide isolation between applications. Application domains are typically created by runtime hosts, which are responsible for bootstrapping the common language runtime before an application is run.
16
Module 2: Introduction to a Managed Execution Environment
Metadata Topic Objective
To explain how metadata is used in the common language runtime.
Lead-in
Every compiler that targets the common language runtime is required to emit full metadata into every managed module.
!
Declarative Information Emitted at Compile Time
!
Included with All .NET Framework Files and Assemblies
!
Metadata Allows the Runtime to: #
Load and locate code
#
Enforce code security
#
Generate native code at runtime
#
Provide reflection
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Every compiler that targets the common language runtime is required to emit full metadata into every managed module. This topic explains how the common language runtime uses metadata.
Definition of Metadata Metadata is a set of data tables, which fully describe every element that is defined in a module. This information can include data types and members with their declarations and implementations, and references to other types and members. Metadata provides the common language runtime with all the information that is required for software component interaction. It replaces older technologies, such as Interface Definition Language (IDL) files, type libraries, and external registration. Metadata is always embedded in the .exe or .dll file containing the MSIL code. Therefore, it is impossible to separate metadata from the MSIL code.
Module 2: Introduction to a Managed Execution Environment
17
Uses for Metadata Metadata has many uses, but the following uses are most important: !
Locating and loading classes Because metadata and MSIL are included in the same file, all type information in that file is available to the common language runtime at compile time. There is no need for header files because all types in a particular assembly are described by the assembly’s manifest.
!
Enforcing security The metadata may or may not contain the permissions required for the code to run. The security system uses permissions to prevent code from accessing resources that it does not have authority to access.
Other uses for metadata include: !
Resolving method calls.
!
Setting up runtime context boundaries.
!
Providing reflection capability.
For more information about metadata, see “Metadata and Self-Describing Components” in the .NET Framework SDK documentation. For more information about verification of type safety, see Module 4, “Deployment and Versioning,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C#™ .NET).
18
Module 2: Introduction to a Managed Execution Environment
Microsoft Intermediate Language Topic Objective
To introduce MSIL and JIT compilation.
Lead-in
Microsoft intermediate language, sometimes called managed code, is the set of instructions that the compiler produces as it compiles source code.
!
Produced by Each Supported Language Compiler
!
Converted to Native Language by the Common Language Runtime's JIT Compilers
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Microsoft intermediate language (MSIL), sometimes called managed code, is the set of instructions that the compiler produces as it compiles source code. This topic explains MSIL and the general process for converting MSIL to native code.
Compiled MSIL Regardless of their logical arrangement, most assemblies contain code in the form of MSIL. MSIL is a CPU-independent machine language created by Microsoft in consultation with third-party compiler vendors. However, MSIL is a much higher-level language than most CPU machine languages. MSIL contains instructions for many common operations, including instructions for creating and initializing objects, and for calling methods on objects. In addition, it includes instructions for arithmetic and logical operations, control flow, direct memory access, and exception handling.
Conversion to Native Code Before MSIL code can be executed, it must be converted to CPU-specific or native code by a JIT compiler. The common language runtime provides an architecture-specific JIT compiler for each CPU architecture. These architecture-specific JIT compilers allow you to write managed code that can be JIT compiled and executed on any supported architecture. Note Any managed code that calls platform-specific native APIs or libraries can only run on a specific operating system. For more information about MSIL, see the .NET Framework SDK documentation.
Module 2: Introduction to a Managed Execution Environment
19
Assemblies Topic Objective
This topic introduces the concept of an assembly and the role of the assembly manifest.
ManagedModule Module Managed (MSILand andMetadata) Metadata) (MSIL ManagedModule Module Managed (MSILand andMetadata) Metadata) (MSIL
Assembly Assembly
Lead-in
The common language runtime uses an assembly as the functional unit of sharing and reuse.
Manifest Manifest
.html .gif
Resource Files
Multiple Managed Modules and Resource Files Are Compiled to Produce an Assembly
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The common language runtime uses an assembly as the functional unit of sharing and reuse.
Definition of an Assembly An assembly is a unit of class deployment, analogous to a logical .dll. Each assembly consists of all the physical files that make up the functional unit: any managed modules, and resource or data files. Conceptually, assemblies provide a way to consider a group of files as a single entity. You must use assemblies to build an application, but you can choose how to package those assemblies for deployment. An assembly provides the common language runtime with the information that it needs to understand types and their implementations. As such, an assembly is used to locate and bind to referenced types at run time.
20
Module 2: Introduction to a Managed Execution Environment
The Assembly Manifest An assembly contains a block of data known as a manifest, which is a table in which each entry is the name of a file that is part of the assembly. The manifest includes the metadata that is needed to specify the version requirements, security identity, and the information that is used to define the scope of the assembly and resolve references to resources and classes. Because the metadata makes an assembly self-describing, the common language runtime has the information it requires for each assembly to execute. All applications that are executed by the common language runtime must be composed of an assembly or assemblies. All files that make up an assembly must be listed in the assembly’s manifest. The manifest can be stored in singlefile assemblies or multi-file assemblies: !
Single-file assemblies When an assembly has only one associated file, the manifest is integrated into a single PE file.
!
Multi-file assemblies When an assembly has more than one associated file, the manifest can be a standalone file, or it can be incorporated into one of the PE files in the assembly.
For more information about assemblies, see “Assemblies” in the .NET Framework SDK documentation.
Module 2: Introduction to a Managed Execution Environment
21
Common Language Runtime Tools Topic Objective
This topic describes how the MSIL Assembler and MSIL Disassembler work.
!
Lead-in
The common language runtime provides two tools that you can use together to test and debug MSIL code.
Runtime Utilities for Working with MSIL #
MSIL Assembler (ilasm.exe) produces a final executable binary
#
MSIL Disassembler (ildasm.exe) inspects metadata and code of a managed binary
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The common language runtime provides two tools that you can use to test and debug MSIL code. !
The MSIL Assembler The MSIL Assembler (Ilasm.exe) takes MSIL as text input and generates a PE file, which contains the binary representation of the MSIL code and required metadata. The basic syntax is as follows: ilasm [options] filename [options]
!
The MSIL Disassembler You can use the MSIL Disassembler (Ildasm.exe) to examine the metadata and disassembled code of any managed module. You will use this tool to examine MSIL code in Lab 2, Building a Simple .NET Application.
22
Module 2: Introduction to a Managed Execution Environment
Demonstration: Using the MSIL Disassembler Topic Objective
To demonstrate how the MSIL Disassembler works.
Lead-in
This demonstration shows how to use the MSIL Disassembler to view an assembly’s metadata.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This demonstration shows how to use the MSIL Disassembler to view an assembly’s metadata.
Viewing Assembly Metadata by Using the MSIL Disassembler In the following procedures, you will see how to use the MSIL Disassembler (Ildasm.exe) to view the contents of the HelloDemoCS.exe assembly.
! To run the MSIL Disassembler on the HelloDemoCS.exe assembly • At a Visual Studio .NET Command Prompt window, change the directory to \Democode\Mod02 where the sample file HelloDemoCS.exe has been copied, and type the following command: ildasm HelloDemoCS.exe
Module 2: Introduction to a Managed Execution Environment
After you expand the MainApp icon, the MSIL Disassembler graphical user interface (GUI) displays information about the file HelloDemoCS.exe, as shown in the following illustration:
! To display the contents of the manifest 1. Double-click MANIFEST. The MANIFEST window appears, as follows:
2. Close the Manifest window, and then close ILDASM.
23
24
Module 2: Introduction to a Managed Execution Environment
Just-In-Time Compilation Topic Objective
To describe JIT compilation and introduce JIT compiler options.
Lead-in
MSIL code must be converted into native code before it can execute. Because an intermediate step is involved, the common language runtime optimizes the compilation process for efficiency.
!
Process for Code Execution #
MSIL converted to native code as needed
#
Resulting native code stored for subsequent calls
#
JIT compiler supplies the CPU-specific conversion
*****************************ILLEGAL FOR NON-TRAINER USE****************************** As previously stated in this module, MSIL code must be converted into native code before it can execute. Because an intermediate step is involved, the common language runtime optimizes the compilation process for efficiency.
The Code Execution Process The common language runtime compiles MSIL as needed. This just-in-time, or JIT, compiling saves time and memory. The basic process is as follows: 1. When the common language runtime loads a class type, it attaches stub code to each method. 2. For subsequent method calls, the stub directs program execution to the common language runtime component that is responsible for compiling a method’s MSIL into native code. This component of the common language runtime is frequently referred to as the JIT compiler. 3. The JIT compiler compiles the MSIL and the method’s stub is substituted with the address of the compiled code. Future calls to that method will not involve the JIT compiler because the compiled native code will simply execute.
Module 2: Introduction to a Managed Execution Environment
25
Application Domains Topic Objective
To describe how application domains provide application isolation.
Lead-in
Historically, process boundaries have been used to isolate applications running on the same computer.
!
Historically, Process Boundaries Used to Isolate Applications
!
In the Common Language Runtime, Application Domains Provide Isolation Between Applications
!
#
The ability to verify code as type-safe enables isolation at a much lower performance cost
#
Several application domains can run in a single process
Faults in One Application Cannot Affect Other Applications
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Historically, process boundaries have been used to isolate applications running on the same computer. Each application is loaded into a separate process, which isolates the application from other applications running on the same computer. The applications are isolated because memory addresses are process-relative; a memory pointer passed from one process to another cannot be used in any meaningful way in the target process. In addition, you cannot make direct calls between two processes. Instead, you must use proxies, which provide a level of indirection. Managed code must be passed through a verification process before it can be run (unless the administrator has granted permission to skip the verification). The verification process determines whether the code can attempt to access invalid memory addresses or perform some other action that could cause the process in which it is running to fail to operate properly. Code that passes the verification test is said to be type-safe. The ability to verify code as type-safe enables the common language runtime to provide as great a level of isolation as the process boundary, at a much lower performance cost. Application domains provide a secure and versatile unit of processing that the common language runtime can use to provide 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 dramatically increases server scalability.
26
Module 2: Introduction to a Managed Execution Environment
Isolating applications is also important for application security. For example, you can run controls from several Web applications in a single browser process in such a way that the controls cannot access each other's data and resources. 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.
!
Individual applications can be stopped without stopping the entire process. Using application domains enables you to unload the code running in a single application.
Note You cannot unload individual assemblies or types. Only a complete domain can be unloaded. Code running in one application cannot directly access code or resources from another application. The common language runtime enforces this isolation by preventing direct calls between objects in different application domains. Objects that pass between domains are either copied or accessed by proxy. If the object is copied, the call to the object is local. That is, both the caller and the object being referenced are in the same application domain. If the object is accessed through a proxy, the call to the object is remote. In this case, the caller and the object being referenced are in different application domains. Crossdomain calls use the same remote call infrastructure as calls between two processes or between two computers.
Module 2: Introduction to a Managed Execution Environment
27
Multimedia: Application Loading and Single-File Assembly Execution Topic Objective
To describe how assembly code is executed.
Lead-in
In this demonstration, you will see how a single-file private assembly is loaded and executed.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Delivery Tip
To start the animation, click the button in the lower-left corner of the slide. The animation plays automatically. To pause or rewind the animation, click the controls in the lower-left of the screen.
Applications that are implemented by using MSIL code require the common language runtime to be installed on the user’s computer in order to run. The common language runtime manages the execution of code. For example, when an application that is implemented as a single-file private assembly is run, the following tasks are performed: !
The Microsoft Windows® loader loads the PE file. The PE file contains a call to a function found in the file MSCorEE.dll.
!
Windows loads the file MSCorEE.dll and transfers control to it to initialize the common language runtime.
!
The common language runtime parses the metadata of the application assembly, and then the JIT compiler compiles the code.
!
The common language runtime then locates the PE file’s managed entrypoint (the Main method in the case of a C# program), and transfers control to this entry point.
If the application calls a private assembly: !
The common language runtime uses probing to locate the referenced assembly, beginning in the application’s root directory and then traversing the subfolders until the assembly is located. • If the assembly is not found, a TypeLoadException error occurs. • If the assembly is found, it is loaded by the common language runtime.
The common language runtime loader parses the manifest in the referenced assembly. The JIT compiler then compiles the required code in the assembly and passes control to the called function.
28
Module 2: Introduction to a Managed Execution Environment
Garbage Collection Topic Objective
To introduce memory and resource management in the .NET Framework.
!
Garbage Collection Provides Automatic Object Memory Management in the .NET Framework
!
You No Longer Need to Track and Free Object Memory
Lead-in
Every program uses resources, such as files, screen space, network connections, and database resources.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This topic introduces memory management in the .NET Framework. For more information about memory and resource management, see Module 9, “Memory and Resource Management,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET).
Current Memory Management Model When you create an object programmatically, you generally follow these steps: 1. Allocate memory for the object 2. Initialize the memory 3. Use the object 4. Perform cleanup on the object 5. Free the object’s memory If you forget to free memory when it is no longer required or try to use memory after it has been freed, you can generate programming errors. The tracking and correction of such errors are complicated tasks because the consequences of the errors are unpredictable.
Memory Management in the .NET Framework The common language runtime uses a heap called the managed heap to allocate memory for all objects. This managed heap is similar to a C-Runtime heap, however you never free objects from the managed heap. In the common language runtime, garbage collection is used to manage memory deallocation. The garbage collection process frees objects when they are no longer needed by the application. For more information about garbage collection, see Module 9, “Memory and Resource Management,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET).
Module 2: Introduction to a Managed Execution Environment
29
Lab 2: Building a Simple .NET Application Topic Objective
To introduce the lab.
Lead-in
In this lab, you will learn how to write, compile, and run a simple application in C# and how to use the MSIL Disassembler to examine an assembly.
*****************************ILLEGAL FOR NON-TRAINER USE******************************
Objectives After completing this lab, you will be able to: !
Write, compile, and run a simple application in C#.
!
Use the MSIL Disassembler to examine an assembly.
Lab Setup Only solution files are associated with this lab. The solution files for this lab are in the folder \Labs\Lab02\Solution.
Estimated time to complete this lab: 20 minutes
30
Module 2: Introduction to a Managed Execution Environment
Exercise 1 Creating the Program in C# In this exercise, you will create the source code for a small console application that takes user input and writes a string to the console by using C#. The lab uses the classic Hello World application to allow you to focus on basic concepts in a managed execution environment. To help you concentrate on the syntactical aspects of this lab, you will use Notepad to create and edit the source files. From a command prompt window, you will then compile the application and test the resulting executable program.
! Write the source code 1. Open Notepad and create a class in C# called MainApp. 2. Import the System namespace. 3. Define the program entry point. The entry point takes no arguments and does not return a value. 4. Create methods that accomplish the following: a. Print the following text to the console: “Type your name and press Enter”. b. Read in the resulting user input. c. Print the text “Hello” and append to the text the value that was read in. 5. Save the file as HelloLabCS.cs in the \Labs\Lab02 folder.
! Build and test the program Important To use Microsoft Visual Studio .NET tools within a command prompt window, the command prompt window must have the proper environment settings. The Visual Studio .NET Command Prompt window provides such an environment. To run a Visual Studio .NET Command Prompt window, click Start, All Programs, Microsoft Visual Studio .NET, Visual Studio .NET Tools, and Visual Studio .NET Command Prompt. 1. From a Visual Studio .NET Command Prompt window, type the syntax to build an executable program from HelloLabCS.cs. 2. Run the resulting executable program. Your C# program should generate the following output: Type your name and press Enter
When you press ENTER, the program outputs the text “Hello ” and whatever text you typed as input.
Module 2: Introduction to a Managed Execution Environment
31
Exercise 2 Using the MSIL Disassembler In this exercise, you will use the MSIL Disassembler to open a single assembly and familiarize yourself with the assembly manifest. In subsequent labs, you will explore assemblies in greater detail.
! Examine the metadata for the Hello World applications 1. Open a Visual Studio .NET Command Prompt window. 2. From the Visual Studio .NET Command Prompt window, type: >ildasm /source
3. Open HelloLabCS.exe and double-click Manifest. 4. Note the following: a. The externally referenced library named mscorlib b. The assembly name HelloLabCS c. Version information (for the HelloLabCS assembly and mscorlib) 5. Close the Manifest window, double-click MainApp, double-click Main, and view the MSIL and source code.
32
Module 2: Introduction to a Managed Execution Environment
Review Topic Objective
To reinforce module objectives by reviewing key points.
Lead-in
!
Writing a .NET Application
!
Compiling and Running a .NET Application
The review questions cover some of the key concepts taught in the module.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** 1. Name the root namespace for types in the .NET Framework. The System namespace is the root namespace for types in the .NET Framework.
2. What class and methods can your application use to input and output to the console? You can use the common language runtime’s Console class of the System namespace for input and output to the console of any string or numeric value by using the Read, ReadLine, Write, and WriteLine methods.
3. When compiling code that makes references to classes in assemblies other than mscorlib.dll what must you do? You must use the /reference compilation switch. The /reference compilation option allows the compiler to make information in the specified libraries available to the source that is currently being compiled. The /r switch is equivalent to /reference.
4. What is the code produced by a .NET compiler called? Microsoft intermediate language (MSIL), sometimes called managed code.
Module 2: Introduction to a Managed Execution Environment
5. What .NET component compiles MSIL into CPU specific native code? The just-in-time (JIT) compiler.
6. What feature of .NET ensures that object memory is freed? The garbage collection process.
33
THIS PAGE INTENTIONALLY LEFT BLANK
Module 3: Working with Components Contents Overview
1
An Introduction to Key .NET Framework Development Technologies
2
Creating a Simple .NET Framework Component
4
Lab 3.1: Creating a .NET Framework Component
11
Creating a Simple Console Client
14
Lab 3.2: Creating a Simple Console-Based Client 19 Demonstration: Creating a Windows Forms Client
22
Creating an ASP.NET Client
27
Lab 3.3: Calling a Component Through an ASP.NET Page
36
Review
40
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, place or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. 2001-2002 Microsoft Corporation. All rights reserved. Microsoft, ActiveX, BizTalk, IntelliMirror, Jscript, MSDN, MS-DOS, MSN, PowerPoint, Visual Basic, Visual C++, Visual C#, Visual Studio, Win32, Windows, Windows Media, and Window NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
Module 3: Working with Components
Instructor Notes Presentation: 70 Minutes Lab: 60 Minutes
After completing this module, students will be able to: !
Create a simple Microsoft® .NET Framework component in C#.
!
Implement structured exception handling.
!
Create a simple .NET Framework console application that calls a component.
!
Create a .NET Framework client application by using the Windows Forms library.
!
Create an ASP.NET page that uses the previously developed .NET Framework component to create an ASP.NET application.
Materials and Preparation This section provides the materials and preparation tasks that you need to teach this module.
Required Materials To teach this module, you need the Microsoft PowerPoint® file 2349B_03.ppt.
Preparation Tasks To prepare for this module, you should: !
Read all of the materials for this module.
!
Practice the demonstration.
!
Review the animation.
!
Complete the lab.
iii
iv
Module 3: Working with Components
Demonstrations This section provides demonstration procedures that will not fit in the margin notes or are not appropriate for the student notes.
Creating a Windows Forms Client This demonstration shows how to build a simple Windows Forms client in Microsoft Visual Studio® .NET. If time permits, you can show the demonstration by using Microsoft Visual Basic®. The solution code for this demonstration is located in \DemoCode\Mod03\C# and \DemoCode\Mod03\VB. In this demonstration, point out how the Windows Form is created in the Main method. Also, point out how the Windows Forms Designer adds code to create and initialize the controls. Consider stepping through the code to explain the code to students.
! To create a Windows Form 1. Open Visual Studio .NET and create a Microsoft Visual C#™ project from the Windows Application template. Name the project WinForm_Client. 2. Add two buttons and a listbox to the form. Set the following properties. a. Set the button1 Text property to &Execute. b. Set the button2 Text property to &Close. c. Set the Form1 Text property to Client.
! To add references 1. On the Project menu, click Add Reference. 2. In the Add Reference dialog box, click the Browse button. 3. Select the CompCS.dll and CompVB.dll assemblies as references. These assemblies are located in \DemoCode\Mod03\C#. Then click the Open button. 4. Click OK. 5. Add references to the assemblies in the form code. Use aliases to avoid name conflicts. using CSStringComp = CompCS.StringComponent; using VBStringComp = CompVB.StringComponent;
Module 3: Working with Components
! To add button event handlers 1. In the form, double-click the Execute button to create a button-click event handler. 2. Add the following code to the event handler to create an instance of the C# component and Visual Basic component, and add their strings to the listbox. //Local Variables CSStringComp myCompCS = new CSStringComp(); VBStringComp myCompVB = new VBStringComp(); int stringCount=0; //Display results from C# Component for (stringCount=0; stringCount<myCompCS.Count;! stringCount++) { listBox1.Items.Add(myCompCS.GetString(stringCount)); } //Display results from Visual Basic Component for (stringCount=0; stringCount<myCompVB.Count;! stringCount++) { listBox1.Items.Add(myCompVB.GetString(stringCount)); }
3. In the form, double-click the Close button to create a button-click event handler. 4. In the Close button event handler, call the Close method to close the form. 5. Compile and run the application. When you click the Execute button, the list box should be filled with four C# strings and four Visual Basic strings.
Testing the ASP.NET Client To test the ASP.NET page, the following software must be installed on the test computer: !
Microsoft Internet Information Services (IIS)
!
The .NET Framework common language runtime
!
The .aspx file that contains the client code
!
All compiled components that are required by the client application
You must configure a virtual directory that points to the directory that contains the .aspx file. You can use the New Directory Wizard in the IIS snap-in to do this.
v
vi
Module 3: Working with Components
! To configure a virtual directory 1. Click the Start menu, click Control Panel, click Performance and Maintenance, click Administrative Tools, and then click Internet Information Services. 2. Expand the computer icon, expand the Web Sites folder, and then expand and select Default Web Site. 3. On the Action menu, point to New, and then click Virtual Directory. The Virtual Directory Creation Wizard is launched. 4. Click Next to continue. 5. In the Alias text box, type Test and click Next. 6. Browse to the \Labs\Lab03.3\SOLUTION\ASP.NET_Client folder that contains the file ClientASP.NET.aspx, click OK, and then click Next. 7. On the Access Permissions page, accept the default selections, click Next, and then click Finish. The Test folder is added to the Default Web Site. Ensure that the compiled component DLLs, CompCS.dll and CompVB.dll, are in a \Bin subdirectory under the starting point for the application virtual directory.
Module 3: Working with Components
vii
! To test the ASP.NET Client • Open Microsoft Internet Explorer, and type the following text in the Address bar: http://localhost/Test/ClientASP.NET.aspx When you press ENTER, the following display, or one similar to it, appears.
Multimedia This section lists the multimedia items that are part of this module. Instructions for launching and playing the multimedia are included with the relevant slides.
ASP.NET Execution Model This animation will show students how ASP.NET pages are processed on the server.
viii
Module 3: Working with Components
Module Strategy Use the following strategy to present this module: !
An Introduction to Key .NET Framework Technologies Briefly introduce Windows Forms, Web Forms, and XML Web services. Explain that you will show an example of a Windows Form in this module. Tell students that they will learn about XML Web services in Module 13, “Remoting and XML Web Services,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET).
!
Creating a Simple .NET Framework Component Show the basic approach to creating reusable classes by using C# and Visual Basic. Spend some time on the new structured exception handling techniques. Use the command line to compile the component. Break the lecture at this point and instruct students to do Lab 3.1, Creating a .NET Framework Component.
!
Creating a Simple Console Client Show how to write a simple console application that calls the .NET Framework runtime-compatible component that was created in Creating a Simple .NET Framework Component. Be sure to fully explain how to use a namespace alias to remove ambiguity to type references. Instruct students to do Lab 3.2, Creating a Simple Console-Based Client.
!
Creating an ASP.NET Client Emphasize the .NET Framework’s capability to execute the component code that was created in Creating a Simple .NET Framework Component in a variety of environments. Contrast the use of the component by the standalone client applications with that of an IIS ASP.NET page. Be sure to present the ASP.NET Execution Model animation to help students understand how ASP.NET pages are processed on the server. Instructions for running the animation are included in the Instructor Notes in the margins. The main objective of this section is to show students how to use an ASP.NET page to obtain the string data from the .NET Framework component, to format that data in HTML, and to return this HTML to a Web browser for display. Be sure to demonstrate how to test the ASP.NET page that was created in Creating an ASP.NET Client in this module. Students must understand how to configure a virtual directory that points to the directory that contains the .aspx file. Instruct students to do Lab 3.3, Calling a Component Through an ASP.NET Page.
Module 3: Working with Components
1
Overview Topic Objective
To provide an overview of the module topics and objectives.
!
An Introduction to Key .NET Framework Development Technologies
!
Creating a Simple .NET Framework Component
!
Creating a Simple Console Client
!
Creating an ASP.NET Client
Lead-in
In this module, you will learn how to create a small client/server application.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** For Your Information
Tell students that the code examples used in this module are from the tutorial, “Introduction to Developing with the .NET Framework.” This tutorial is found in the .NET Framework Software Development Kit (SDK).
In this module, you will learn how to create a small client/server application in C#. The steps that are necessary to construct, compile, and run each program are covered in detail. You will also learn how to build the client application by using the Windows Forms library and an ASP.NET page. As in Module 2, “Introduction to a Managed Execution Environment” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C#™ .NET), the content of this module is based on the “Introduction to Developing with the .NET Framework” tutorial in the .NET Framework Software Development Kit (SDK). After completing this module, you will be able to: !
Create a simple Microsoft® .NET Framework component in C#.
!
Implement structured exception handling.
!
Create a simple .NET Framework console application that calls a component.
!
Create a .NET Framework client application by using the Windows Forms library.
!
Create an ASP.NET page that uses the previously developed .NET Framework component to create an ASP.NET application.
2
Module 3: Working with Components
An Introduction to Key .NET Framework Development Technologies Topic Objective
To introduce the technologies that are used to develop .NET Framework applications.
Lead-in
To develop applications in the .NET Framework, you should be familiar with Windows Forms, ASP.NET Web Forms, and XML Web services.
!
Windows Forms
!
Web Forms
!
XML Web Services
*****************************ILLEGAL FOR NON-TRAINER USE****************************** To develop applications in the .NET Framework, you should be familiar with the following technologies: Windows Forms, ASP.NET Web Forms, and XML Web services. Your choice of technology, or technologies, depends on the type of software solution that you wish to implement. This topic briefly introduces the technologies and provides references to additional information.
Windows Forms Windows Forms are used to develop applications in which the client computer handles most of the application processing. Classic Microsoft Win32® desktop applications, such as drawing and graphics applications, data-entry systems, point-of-sale systems, and games, are well suited to Window Forms. All of these applications rely on the power of the desktop computer for processing and for high-performance content display. The System.Windows.Forms namespace contains the classes used to create Windows Forms.
Module 3: Working with Components
Web Forms ASP.NET Web Forms are used to create applications in which the primary user interface is a browser. Obviously, you can use Web Forms to create applications that are available on the World Wide Web, such as e-commerce applications, but you will also find them helpful for creating other types of applications, such as intranet applications, which users can run by using only their browser, which is already installed on their computers. Because Web Forms applications are platform-independent, users can interact with your application regardless of the type of browser or computer that they are using. You can also optimize Web Forms applications to use features that are built into the most recent browsers, such as Dynamic Hypertext Markup Language (DHTML) and HTML 4.0, which enhance performance and responsiveness. The System.Web namespace contains the classes used to create Web Forms. For more information about Windows Forms and ASP.NET Web Forms, see “Windows Forms and Web Forms Recommendations” in the .NET Framework SDK documentation.
XML Web Services An XML Web Service is a programmable entity that resides on a Web server and is exposed through standard Internet protocols. In many respects, the programming model for creating and using XML Web services is similar to the programming model for creating and using COM-based components. However, unlike COM, the XML Web services programming model is based on simple, open standards that are broadly supported. Instead of using binary communication methods between applications, XML Web services use communication based on the SOAP protocol to transport XML messages between applications. In an environment of integrated, programmable XML Web services, XML is the universal language for communication of raw data and information that can be understood and acted upon. For more information about XML Web services, see Module 13, “Remoting and XML Web Services” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET).
3
4
Module 3: Working with Components
" Creating a Simple .NET Framework Component Topic Objective
To provide an overview of the section topics.
Lead-in
In this section, you will learn how to use C# to create a simple component.
!
Using Namespaces and Declaring the Class
!
Creating the Class Implementation
!
Implementing Structured Exception Handling
!
Creating a Property
!
Compiling the Component
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this section, you will learn how to create a simple component using C#. The component provides a wrapper for an array of strings and includes a GetString method that takes an integer and returns a string. The component includes a read-only Count property that contains the number of elements in the string array, which is used to iterate over all of the array members. The GetString method also demonstrates the use of structured exception handling. Delivery Tip
If you prefer, you can use Notepad to demonstrate the code in this section, instead of discussing the content of each slide.
In subsequent sections of this module, you will learn how clients use the GetString method and the Count property to determine and display the output from the component’s string array. The simple string component that is described in this section shows the basic approach to creating reusable classes in C#.
Module 3: Working with Components
5
Using Namespaces and Declaring the Class Topic Objective
To explain how to create a new namespace that encapsulates classes.
!
Create a New Namespace
Lead-in
To create a new namespace that encapsulates the classes that you will be creating, you use the namespace statement.
using using System; System; namespace namespace CompCS CompCS {...} {...} !
Declare the Class public public class class StringComponent StringComponent {...} {...}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** To create a new namespace that encapsulates the classes that you will be creating, you use the namespace statement, as in the following example: using System; namespace CompCS {...}
The following statement denotes that instances of the StringComponent class will now be created by the runtime and managed in the garbage-collected heap. public class StringComponent {...}
6
Module 3: Working with Components
Creating the Class Implementation Topic Objective
To explain how to create the class implementation.
!
private private string[] string[] stringSet; stringSet;
Lead-in
The component provides a wrapper for an array of strings.
Declare a Private Field of Type Array of String Elements
!
Create a Public Default Constructor public public StringComponent() StringComponent() {...} {...}
!
Assign the stringSet Field to an Array of Strings stringSet stringSet == new new string[] string[] {{ "C# String 0", "C# String 0", "C# "C# String String 1", 1", ... ... }; };
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The component provides a wrapper for an array of strings. You can declare a private field of type array of string elements, as in the following example: private string[ ] stringSet;
The following example shows how to create a public default constructor, which executes each time a new instance of the class is created, and how to assign the stringSet field to an array of strings: public StringComponent() { stringSet = new string[ ] { "C# String 0", "C# String 1", "C# String 2", "C# String 3" }; }
In the preceding example, you can see that the constructor has the same name as the class and does not have a return type.
Module 3: Working with Components
7
Implementing Structured Exception Handling Topic Objective
To explain how to implement structured exception handling.
!
Implement the GetString Method public public string string GetString(int GetString(int index) index) {...} {...}
Lead-in
The component uses the GetString method to return the strings in the stringSet array.
!
Create and Throw a New Object of Type IndexOutOfRangeException
if((index if((index << 0) 0) || || (index (index >= >= stringSet.Length)) stringSet.Length)) {{ throw throw new new IndexOutOfRangeException(); IndexOutOfRangeException(); }} return return stringSet[index]; stringSet[index]; !
Exceptions Caught by the Caller Using a try/catch/finally Statement
!
Structured Exception Handling Replaces HRESULT-Based Error Handling in COM
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Programs must be able to uniformly handle errors and exceptions that occur during execution. The common language runtime helps you design errortolerant software by providing a platform for notifying programs of errors in a uniform manner. All .NET Framework methods indicate failure by throwing exceptions. Traditionally, a language’s error-handling model relied on either the language’s unique way of detecting errors and locating handlers for them, or on the errorhandling mechanism that is provided by the operating system. The runtime implements exception handling with the following features: !
It handles exceptions without regard for the language that generates the exception or the language that will be called upon to handles the exception.
!
It does not require any particular language syntax for handling exceptions, but allows each language to define its own syntax.
!
It allows exceptions to be thrown across process boundaries and machine boundaries.
Exceptions offer several advantages over other methods of error notification. Failures do not go unnoticed. Invalid values do not continue to propagate through the system. You do not have to check return codes. Exception-handling code can be easily added to increase program reliability. Finally, the runtime’s exception handling is faster than Windows-based C++ error handling.
8
Module 3: Working with Components
The component uses the GetString method to return the strings in the stringSet array. You can use a throw statement to implement structured exception handling within the GetString method, as in the following example: public string GetString(int index) { if ((index < 0) || (index >= stringSet.Length)) { throw new IndexOutOfRangeException(); } return stringSet[index]; }
In the preceding example, the program evaluates whether the integer that is passed as an argument to GetString is a valid index for the stringSet array. If the index is invalid, the following statement creates and throws a new object of type IndexOutOfRangeException: throw new IndexOutOfRangeException();
If the index is valid, the program returns the string element of the stringSet array at that particular index. Exceptions may be caught by the caller by using a try/catch/finally statement. Place the sections of code that might throw exceptions in a try block and place code that handles exceptions in a catch block. You can also write a finally block that always runs regardless of how the try block runs. The finally block is useful for cleaning up resources after a try block. For example, in C#: try { // code that might } catch(Exception e) // place code that } finally { // place code that }
throw exceptions { handles exceptions
runs after try or catch runs
In general, it is good programming practice to catch a specific type of exception rather than using the preceding general catch statement that catches any exception. Structured exception handling replaces the HRESULT-based error-handling system that is used in COM. All .NET Framework exception-handling classes, such as IndexOutOfRangeException and user-defined exceptions, must derive from System.Exception. For more information about exception handling, see “Handling and Throwing Exceptions” in the .NET Framework SDK documentation.
Module 3: Working with Components
9
Creating a Property Topic Objective
To explain how to create a property.
Lead-in
To provide the number of string elements in the stringSet array in C#, you create a read-only property called Count.
!
Create a Read-Only Count Property to Get the Number of String Elements in the stringSet Array public public int int Count Count {{ get get {{ return return stringSet.Length; stringSet.Length; }} }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** To provide the number of string elements in the stringSet array, you create a read-only property called Count, as in the following example: public int Count { get { return stringSet.Length; } }
For Your Information
Point out that the GetString method and Count property may be used by a client to iterate over the component’s string elements. This information will help students when they create the clients in subsequent labs.
10
Module 3: Working with Components
Compiling the Component Topic Objective
To explain how to compile the source code for a component.
Lead-in
To compile the source code, first you must save the source file.
!
Use the /target:library Switch to Create a DLL #
Otherwise, an executable with a .dll file extension is created instead of a DLL library
csc csc /out:CompCS.dll /out:CompCS.dll /target:library /target:library CompCS.cs CompCS.cs
*****************************ILLEGAL FOR NON-TRAINER USE****************************** To compile the source code for the C# component, first you must save the source file, and then enter the following command from a command prompt window: Important To use Microsoft Visual Studio® .NET tools within a command prompt window, the command prompt window must have the proper environment settings. The Visual Studio .NET Command Prompt window provides such an environment. To run a Visual Studio .NET Command Prompt window, click Start, All Programs, Microsoft Visual Studio .NET, Visual Studio .NET Tools, and Visual Studio .NET Command Prompt. csc /out:CompCS.dll /target:library CompCS.cs
This command directs the compiler to produce the file CompCS.dll. The /target:library switch is required to actually create a DLL library, instead of an executable.
Module 3: Working with Components
11
Lab 3.1: Creating a .NET Framework Component Topic Objective
To introduce the lab.
Lead-in
In this lab, you will learn how to create a simple .NET Framework component in C#.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** For Your Information
In this introductory module, the lab problems have intentionally been made very similar to the problems presented in the module. This approach is designed to allow the student to experience a wide range of .NET software approaches within a very limited amount of time.
Objective After completing this lab, you will be able to create a simple .NET Framework component in C#.
Lab Setup Only solution files are associated with this lab. The solution files for this lab are in the folder \Labs\Lab03.1\Solution.
Scenario This lab is based on a small client server application scenario in which you write both the client application and server component using C#. This lab focuses on building the server component. Lab 3.2, “Creating a Simple Console-Based Client,” focuses on building a client program that calls the server component.
Estimated time to complete this lab: 15 minutes
12
Module 3: Working with Components
Exercise 1 Creating a Component in C# In this exercise, you will use C# to create a component that provides a wrapper for a private array of strings. The wrapper includes a read-only Count property, which returns the number of strings in the array, and a public GetString method, which takes an integer argument and returns the string in the private array at that index. The GetString method will also implement structured exception handling to ensure that the argument of the index is within range of the array.
! Specify namespace information and class declarations 1. Open Notepad and reference the System namespace. 2. Create a new namespace named CompCS. CompCS will contain the class for your component.
!
Create the class implementation
1. Create a public class named StringComponent. 2. Declare a private field named StringSet of type array of string elements. 3. Create a public default constructor. The default constructor takes no arguments. 4. Assign to the field StringSet an array of strings initialized with the following four strings: "C# "C# "C# "C#
String String String String
0", 1", 2", 3"
5. Create the public GetString method, which takes an integer as an argument. That integer must be a valid index for the StringSet array. a. If the integer is an invalid index, throw an index out of range exception. b. If the integer is a valid index, return the StringSet string at that index. 6. Create a read-only Count property that gets the number of string elements in the StringSet array. 7. Save the file as CompCS.cs in the \Labs\Lab03.1 folder.
Module 3: Working with Components
13
! Compile the source code Important To use Microsoft Visual Studio .NET tools within a command prompt window, the command prompt window must have the proper environment settings. The Visual Studio .NET Command Prompt window provides such an environment. To run a Visual Studio .NET Command Prompt window, click Start, All Programs, Microsoft Visual Studio .NET, Visual Studio .NET Tools, and Visual Studio .NET Command Prompt. • From a Visual Studio .NET command prompt window, enter the command to build a library named CompCS.dll from the CompCS.cs. source. You must specify that the target is a library.
14
Module 3: Working with Components
" Creating a Simple Console Client Topic Objective
To provide an overview of the section topics.
Lead-in
In this section, you will learn how to write a simple console application that calls a .NET Framework runtime-compatible component.
!
Using the Libraries
!
Instantiating the Component
!
Calling the Component
!
Building the Client
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this section, you will learn how to write a simple console application that calls the .NET Framework runtime-compatible component that was created in the previous section.
Module 3: Working with Components
15
Using the Libraries Topic Objective
To explain how to import libraries and to use namespace aliasing to remove ambiguity.
!
Reference Types Without Having to Fully Qualify the Type Name using using CompCS; CompCS;
Lead-in
By using the namespaces in the program, you can reference types in the library without fully qualifying the type name.
using using CompVB; CompVB; !
If Multiple Namespaces Contain the Same Type Name, Create a Namespace Alias to Remove Ambiguity
using using CSStringComp CSStringComp == CompCS.StringComponent; CompCS.StringComponent; using using VBStringComp VBStringComp == CompVB.StringComponent; CompVB.StringComponent;
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This topic explains how to reference types in your applications by using namespaces and how to use a namespace alias if you need to remove ambiguity to type references.
Using the Namespaces By using the namespaces in the program, you can reference types in the library without fully qualifying the type name. To use the component namespaces in C#, you must enter a using statement followed by the name of the component namespaces: using CompCS; using CompVB;
Using an Alias Consider a scenario where similar C# and Microsoft Visual Basic® components use the same type name (StringComponent). You must still fully qualify the type name to remove any ambiguity when referring to the GetString method and the Count property. In C#, you can create and use aliases to solve this problem. The following C# example shows how to alias the component namespaces so that you do not have to fully qualify the type names: using CSStringComp = CompCS.StringComponent; using VBStringComp = CompVB.StringComponent;
16
Module 3: Working with Components
Instantiating the Component Topic Objective
To describe how to instantiate the component in C#.
Lead-in
To instantiate the StringComponent class, you declare a local variable of type StringComponent and create a new instance of StringComponent.
!
Declare a Local Variable of Type StringComponent
!
Create a New Instance of the StringComponent Class
//… //… using using CSStringComp CSStringComp == CompCS.StringComponent; CompCS.StringComponent; //… //… CSStringComp CSStringComp myCSStringComp myCSStringComp == new new CSStringComp(); CSStringComp();
*****************************ILLEGAL FOR NON-TRAINER USE****************************** To instantiate the StringComponent class, you declare a local variable of type StringComponent and create a new instance of StringComponent, as in the following example: //… using CSStringComp = CompCS.StringComponent; class MainApp { public static void Main() { CSStringComp myCSStringComp = new CSStringComp(); //… } }
Module 3: Working with Components
17
Calling the Component Topic Objective
To describe how to call a component after it has been instantiated.
!
Lead-in
After instantiating the StringComponent class, the client can iterate over the string array in the C# or Visual Basic component and return the appropriate output.
Iterate over All the Members of StringComponent and Output the Strings to the Console
for for (int (int index index == 0; 0; index index << myCSStringComp.Count; myCSStringComp.Count; index++) index++) {{ Console.WriteLine Console.WriteLine (myCSStringComp.GetString(index)); (myCSStringComp.GetString(index)); }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** After instantiating the StringComponent class, the client can iterate over the string array in the component and return the appropriate output, as in the following example: using System; using CSStringComp = CompCS.StringComponent; class MainApp { public static void Main() { CSStringComp myCSStringComp = new CSStringComp(); // Display result strings from CS component Console.WriteLine("Strings from CS StringComponent"); for (int index = 0; index < myCSStringComp.Count; index++) { Console.WriteLine(myCSStringComp.GetString(index)); } } }
18
Module 3: Working with Components
Building the Client Topic Objective
To show how to build the client in C#.
Lead-in
To build the client, you can compile the source code from a command prompt window.
!
Use the /reference Switch to Reference the Assemblies That Contain the StringComponent Class csc csc /reference:CompCS.dll,CompVB.dll! /reference:CompCS.dll,CompVB.dll! /out:ClientCS.exe /out:ClientCS.exe ClientCS.cs ClientCS.cs
*****************************ILLEGAL FOR NON-TRAINER USE****************************** To build the client, you can compile the source code from a command prompt window. You must use the /reference option to reference all of the assemblies that contain the StringComponent class, as in the following example: csc /reference:CompCS.dll,CompVB.dll! /out:ClientCS.exe ClientCS.cs
Running a client program that first iterates over the C# component’s strings and then iterates over the Visual Basic component’s strings will produce the following output: Strings from C# StringComponent C# String 0 C# String 1 C# String 2 C# String 3 Strings from VB StringComponent VB String 0 VB String 1 VB String 2 VB String 3
Module 3: Working with Components
19
Lab 3.2: Creating a Simple Console-Based Client Topic Objective
To introduce the lab.
Lead-in
In this lab, you will learn how to create a simple console-based client application that calls a component.
*****************************ILLEGAL FOR NON-TRAINER USE******************************
Objectives After completing this lab, you will be able to create a simple console-based client application that calls a component.
Lab Setup To complete this lab you will need the solution files from Lab 3.1 and a Visual Basic component provided for you in the \Labs\Lab03.2\ Starter\VB\Component folder. The solution files for this lab are located in \Labs\Lab03.2\Solution.
Scenario This lab is based on a small client/server application scenario in which both the client application and server component are written by using C#. This lab focuses on building a simple console-based client application that calls the component that was created in Lab 3.1, “Creating a .NET Framework Component.” Also, another version of the component written in Visual Basic is provided. The client will use both the C# and Visual Basic component versions to show cross-language compatibility.
Estimated time to complete this lab: 15 minutes
20
Module 3: Working with Components
Exercise 1 Creating a Client Application in C# In this exercise, you will create a simple console application to call the C# component that you created in Lab 3.1, “Creating a .NET Framework Component,” and a Visual Basic component that is already provided. The Visual Basic component provides the same StringComponent class as the C# component.
! Specify namespace information 1. Open Notepad and import the System namespace. 2. Use the namespace of the C# component that you created in Lab 3.1, “Creating a .NET Framework Component.” 3. Use the namespace of the Visual Basic component. The namespace of the Visual Basic component is CompVB. Because you are importing two libraries that contain the same type name but have different namespaces, use an alias directive to allow for unambiguous references to StringComponent classes. 4. Create a class in C# called MainApp. 5. Specify the program entry point. The entry point takes no arguments and does not return a value.
! Write the code to call the C# and Visual Basic components 1. Assign to a local variable named myCSStringComp a new instance of the C# version of StringComponent. 2. Write the code to print the following string to the console: Strings from C# StringComponent
3. Iterate over all of the members of the C# string component and output the strings to the console. 4. Repeat steps 1 through 3 for the Visual Basic version of StringComponent. Name the local variable myVBStringComp. 5. Save the file as ClientCS.cs in the \Labs\Lab03.2 folder.
Module 3: Working with Components
21
! Compile and run the program Important To use Microsoft Visual Studio .NET tools within a command prompt window, the command prompt window must have the proper environment settings. The Visual Studio .NET Command Prompt window provides such an environment. To run a Visual Studio .NET Command Prompt window, click Start, All Programs, Microsoft Visual Studio .NET, Visual Studio .NET Tools, and Visual Studio .NET Command Prompt. 1. From a Visual Studio .NET command prompt window, enter the command to build the executable program ClientCS.exe from ClientCS.cs. You must reference the assemblies that contain the C# and Visual Basic StringComponent class. You should copy these assemblies into the same directory as ClientCS.cs. The Visual Basic assembly is located in \Labs\Lab03.2\Starter\VB\Component. The C# assembly is located in \Labs\Lab03.1\Solution\C#\Component. 2. Run the resulting executable program. Your C# program should generate the following output: Strings from C# StringComponent C# String 0 C# String 1 C# String 2 C# String 3 Strings from VB StringComponent VB String 0 VB String 1 VB String 2 VB String 3
22
Module 3: Working with Components
Demonstration: Creating a Windows Forms Client Topic Objective
To demonstrate how to create a Windows Forms client.
Lead-in
In this demonstration, you will see how to create a Windows Forms client.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In previous sections of this course, all examples have been command-line programs that wrote to the system console. In this demonstration, you will learn how to rewrite the client application so it can use the new .NET Windows Forms library, which is available to all .NET Framework runtime-compatible languages. In this demonstration, the client displays in a list box on a form the string output that was displayed in the system console in previous examples.
Module 3: Working with Components using using using using using using
System; System.Drawing; System.Collections; System.ComponentModel; System.Windows.Forms; System.Data;
using CSStringComp = CompCS.StringComponent; using VBStringComp = CompVB.StringComponent; namespace WinForm_Client { public class Form1 : System.Windows.Forms.Form { private System.Windows.Forms.Button button1; private System.Windows.Forms.Button button2; private System.Windows.Forms.ListBox listBox1; /// <summary> /// Required designer variable. /// private System.ComponentModel.Container components = null; public Form1() { // // Required for Windows Form Designer support // InitializeComponent(); } /// <summary> /// Clean up any resources being used. /// protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); }
(Code continued on the following page.)
23
24
Module 3: Working with Components #region Windows Form Designer generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { this.button1 = new System.Windows.Forms.Button(); this.button2 = new System.Windows.Forms.Button(); this.listBox1 = new System.Windows.Forms.ListBox(); this.SuspendLayout(); // // button1 // this.button1.Location = new System.Drawing.Point(24, 232); this.button1.Name = "button1"; this.button1.Size = new System.Drawing.Size(64, 24); this.button1.TabIndex = 0; this.button1.Text = "&Execute"; this.button1.Click += new System.EventHandler(this.button1_Click); // // button2 // this.button2.Location = new System.Drawing.Point(192, 232); this.button2.Name = "button2"; this.button2.Size = new System.Drawing.Size(64, 24); this.button2.TabIndex = 1; this.button2.Text = "&Close"; this.button2.Click += new System.EventHandler(this.button2_Click); // // listBox1 // this.listBox1.Location = new System.Drawing.Point(8, 8); this.listBox1.Name = "listBox1"; this.listBox1.Size = new System.Drawing.Size(264, 199); this.listBox1.TabIndex = 2; //
(Code continued on the following page.)
Module 3: Working with Components
25
// Form1 // this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(292, 273); this.Controls.AddRange(new System.Windows.Forms.Control[] { this.listBox1, this.button2, this.button1}); this.Name = "Form1"; this.Text = "Client"; this.ResumeLayout(false); } #endregion /// <summary> /// The main entry point for the application. /// [STAThread] static void Main() { Application.Run(new Form1()); } private void button1_Click(object sender, System.EventArgs e) { //Local Variables CSStringComp myCompCS = new CSStringComp(); VBStringComp myCompVB = new VBStringComp(); int stringCount=0; //Display results from C# Component for (stringCount=0; stringCount<myCompCS.Count;stringCount++) { listBox1.Items.Add( myCompCS.GetString(stringCount)); }
(Code continued on the following page.)
26
Module 3: Working with Components //Display results from Visual Basic Component for (stringCount=0; stringCount<myCompVB.Count;stringCount++) { listBox1.Items.Add( myCompVB.GetString(stringCount)); } } private void button2_Click(object sender, System.EventArgs e) { Close(); } } }
Module 3: Working with Components
27
" Creating an ASP.NET Client Topic Objective
To provide an overview of the section topics.
Lead-in
In this section, you will learn how to use an ASP.NET page to obtain the string data from the .NET Framework component, to format the data in HTML, and to return this HTML to a Web browser for display.
!
Writing the HTML for the ASP.NET Application
!
Coding the Page_Load Event Handler
!
Generating the HTML Response
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this module, you have learned how to create two stand-alone versions of a client application that calls the simple string component: a console application and a form based on the Windows Forms library. Another important feature of the .NET Framework is the capability to execute the component code that was used by such stand-alone client applications in a Microsoft Internet Information Services (IIS) ASP.NET page. In this section, you will learn how to use an ASP.NET page to obtain the string data from the .NET Framework component, to format that data in HTML, and to return this HTML to a Web browser for display.
28
Module 3: Working with Components
Multimedia: ASP.NET Execution Model Topic Objective
To describe the ASP.NET execution model.
Lead-in
In this animation, you will see how ASP.NET pages are processed on the server.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** To launch the animation, click the button in the lowerleft corner of the slide. To play the animation, click the Start button, and then click the First Request, Second Request, and Output Cache buttons at the top of the screen. There is no audio, but explanatory text appears in the Info column when you click one of the buttons.
Tell students that they can view the animation again later for themselves by opening the 2349B_mod3.htm file from the Media folder.
The .NET Framework provides the capability to execute code that was used in the preceding stand-alone client applications in an ASP.NET page. However, there are some differences in the code execution processes. An ASP.NET page generally produces HTML in response to a HTTP request; and the page itself is compiled dynamically, unlike the stand-alone client applications. Each ASP.NET page is individually parsed, and the syntax is checked. Then, a .NET runtime class is produced, compiled, and invoked. ASP.NET caches the compiled object, so subsequent requests do not perform the parse and compile step and thus execute much faster. In this animation, you will see how ASP.NET pages are processed on the server. To view the animation, open the 2349B_mod3.htm file from the Media folder.
Module 3: Working with Components
29
Writing the HTML for the ASP.NET Application Topic Objective
To describe how to write the HTML for an ASP.NET application.
!
<%@ <%@ Page Page Language="C#" Language="C#" Description="ASP.NET Description="ASP.NET Client" Client" %> %>
Lead-in
An ASP.NET file is a text file that contains markup syntax for coding server-side page logic, dynamic output, and literal content.
Specify Page-Specific Attributes Within a Page Directive
!
Import the Namespace and the Physical Assembly <%@ <%@ Import Import Namespace="CompCS"%> Namespace="CompCS"%> <%@ <%@ Import Import Namespace="CompVB"%> Namespace="CompVB"%>
!
Specify Code Declaration Blocks <script <script language="C#" language="C#" runat=server> runat=server> ... ...
*****************************ILLEGAL FOR NON-TRAINER USE****************************** An ASP.NET file is a text file that contains markup syntax for coding serverside page logic, dynamic output, and literal content. By default, ASP.NET files have an .aspx extension or .ascx for user controls. However, ASP.NET will parse and compile any file that is mapped to the aspnet_isapi.dll under IIS.
Specifying @ Page Directives To create the ASP.NET page that calls the string component, you first specify page-specific attributes within a page directive. Page directives specify optional settings that are used by the page compiler when processing ASP.NET files, as shown in the following example: <%@ Page Language="C#" Description="ASP.NET Client" %>
The following table describes the two @ Page directive attributes that are used in the sample client ASP.NET page. Attribute
Description
Language
Language that is used when compiling all <% %> and <%= %> blocks within a page. Can be Visual Basic, C#, or Microsoft JScript® .NET.
Description
Provides a text description of the page. Supports any string description.
30
Module 3: Working with Components
Specifying @ Import Directives After specifying an @ Page directive, you include an @ Import directive, which explicitly imports a namespace into a page, as in the following example: <%@ Import Namespace="CompCS"%> <%@ Import Namespace="CompVB"%>
When you use an @ Import directive to import a namespace into a page, all classes and interfaces of the imported namespace are made available to the page. The imported namespace can be part of the .NET Framework class library or a user-defined namespace. In addition, the @ Import directive specifies the name of the assembly that will be used. The assembly must be located in the \Bin subdirectory of the application’s starting point.
Specifying Code Declaration Blocks After specifying @ Page directives and importing the required namespaces, you add code declaration blocks in which you place the code that calls the string component in the ASP.NET page. You define code declaration blocks by using <script> tags that contain a runat attribute, which tells the server to execute the code on the server, instead of sending the code text back to the client as part of the HTML stream. The script element may optionally use a language attribute to specify the language of its inner code, as in the following example: ... <script language="C#" runat=server> ... ...
If you do not specify a language, ASP.NET defaults to the language that was configured for the base page, which is determined by the @ Page directive. For more information about directives in ASP.NET, see “Directive Syntax” in the .NET Framework SDK documentation.
Module 3: Working with Components
31
Coding the Page_Load Event Handler Topic Objective
void void Page_Load(Object Page_Load(Object sender, sender, EventArgs EventArgs EvArgs) EvArgs) {{ StringBuilder StringBuilder Out Out == new new StringBuilder(""); StringBuilder(""); int Count = 0; int Count = 0; // // Iterate Iterate over over component's component's strings strings and and concatenate concatenate Out.Append("Strings Out.Append("Strings from from C# C# Component
"); Component
"); CompCS.StringComponent CompCS.StringComponent myCSStringComp myCSStringComp == new new CompCS.StringComponent(); CompCS.StringComponent(); for(int for(int index index == 0; 0; index index << myCSStringComp.Count; myCSStringComp.Count; index++) index++) {{ Out.Append(myCSStringComp.GetString(index)); Out.Append(myCSStringComp.GetString(index)); Out.Append("
"); Out.Append("
"); }} // // …… Message.InnerHtml Message.InnerHtml == Out.ToString(); Out.ToString(); }}
To explain how to code the Page_Load event handler.
Lead-in
The Page_Load event contains most of the code for the sample client program.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The Page_Load event contains most of the code for the sample client program. The following example shows the Page_Load event-handling code: void Page_Load(Object sender, EventArgs EvArgs) { StringBuilder Out = new StringBuilder(""); int Count = 0; // Iterate over component's strings and concatenate Out.Append("Strings from C# Component
"); CompCS.StringComponent myCSStringComp = new CompCS.StringComponent(); for(int index = 0; index < myCSStringComp.Count; index++) { Out.Append(myCSStringComp.GetString(index)); Out.Append("
"); } Out.Append("
"); // Iterate over component's strings and concatenate Out.Append("Strings from VB Component
"); CompVB.StringComponent myVBStringComp = new CompVB.StringComponent(); for(int index = 0; index < myVBStringComp.Count; index++) { Out.Append(myVBStringComp.GetString(index)); Out.Append("
"); } Message.InnerHtml = Out.ToString(); }
32
Module 3: Working with Components
Notice that the client code that calls the string component is very similar to the code for the stand-alone console and Windows Forms-based applications. After declaring the Page_Load event handler and initializing variables, you create a new instance of the StringComponent class and iterate over the string array. Each of the strings is appended to the variable Out. You then assign Out to a property of an HTML server control, as in the following example: Message.InnerHTML = Out.ToString();
Instead of assigning Out to a property of an HTML server control, you can use Response.Write to write the stream directly into the HTML stream. Note It is a recommended practice with ASP.NET forms to place the executable code in a separate file from the declarative HTML. However, for the sake of simplicity, the code for the ASP.NET page in this module has not been placed in a separate file.
Module 3: Working with Components
33
Generating the HTML Response Topic Objective
To show how to generate the HTML response by using the Message object.
Lead-in
The following example shows how to create the HTML response by using the Message object.
!
Specify the Body of the HTML Response
<span <span id="Message" id="Message" runat=server/> runat=server/>
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The following example shows how to create the HTML response by using the Message object: <span id="Message" runat=server/>
This code places the string output that was generated by the Page_Load event handler in the HTTP response.
34
Module 3: Working with Components
The code for the final ASP.NET page is as follows: <%@ <%@ <%@ <%@
Page Language="C#" Description="ASP.NET Component Test" %> Import Namespace="CompCS"%> Import Namespace="CompVB"%> Import Namespace="System.Text"%>
<script language="C#" runat=server> void Page_Load(Object sender, EventArgs EvArgs) { StringBuilder Out = new StringBuilder(); int Count = 0; // Iterate over component's strings and concatenate Out.Append("Strings from C# Component
"); CompCS.StringComponent myCSStringComp = new CompCS.StringComponent(); for (int index = 0; index < myCSStringComp.Count; index++){ Out.Append(myCSStringComp.GetString(index)); Out.Append("
"); } Out.Append("
"); // Iterate over component's strings and concatenate Out.Append("Strings from VB Component
"); CompVB.StringComponent myVBStringComp = new CompVB.StringComponent(); for (int index = 0; index < myVBStringComp.Count; index++){ Out.Append(myVBStringComp.GetString(index)); Out.Append("
"); } Message.InnerHtml = Out.ToString(); } <span id="Message" runat=server/>
Module 3: Working with Components
35
Demonstration: Testing the ASP.NET Client Topic Objective
To demonstrate how to test the ASP.NET page.
Lead-in
In this demonstration, you will see how to test the ASP.NET page.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** For Your Information
For instructions on running this demonstration, see the Instructor Notes. This exact procedure is repeated in Lab 3.3, “Calling a Component Through an ASP.NET Page.”
This demonstration shows how to test the ASP.NET page that was created in Creating an ASP.NET Client in this module. Before testing the page, you must first configure a virtual directory that points to the directory that contains the .aspx file. You can use the New Directory Wizard in the IIS snap-in to do this.
36
Module 3: Working with Components
Lab 3.3: Calling a Component Through an ASP.NET Page Topic Objective
To introduce the lab.
Lead-in
In this lab, you will learn how to create an ASP.NET page that uses a previously developed .NET Framework component to create an ASP.NET application.
*****************************ILLEGAL FOR NON-TRAINER USE******************************
Objectives After completing this lab, you will be able to create an ASP.NET page that uses a previously developed .NET Framework component to create an ASP.NET application.
Lab Setup Starter and solution files are associated with this lab. The starter files are in the folder \Labs\Lab03.3\Starter, and the solution files are in the folder \Labs\Lab03.3\Solution.
Scenario The labs associated with Module 3 are based on a small client/server application scenario in which a .NET Framework component may be used in several different client scenarios. The component may be written in any .NET Framework runtime-compatible language, but you will only create a C# component. Lab 3.2, “Creating a Simple Console-Based Client,” and Lab 3.3, “Calling a Component Through an ASP.NET Page,” demonstrate how the same component code runs unchanged in different client environments. This lab focuses on using an ASP.NET page to call the component and display the results in a Web browser.
Estimated time to complete this lab: 30 minutes
Module 3: Working with Components
Exercise 1 Writing the ASP.NET Page In this exercise, you will create an ASP.NET page that uses the same StringComponent component that was used in the stand-alone client application that you created in Lab 3.2, “Creating a Simple Console-Based Client.” You will build a string object that holds the results of processing the string array in the StringComponent class and generates output that can be displayed in a Web browser.
! Create an ASP.NET page 1. Open Notepad and enter an @ Page directive that specifies the following page attributes: a. Language = C# b. Description = “ASP.NET Component Test” 2. Import the following namespaces into the page: a. CompCS b. CompVB c. System.Text 3. Create HTML script that specifies C# as the script language and uses the runat=server directive.
! Call the StringComponent class 1. Create a handler for the Page_Load event and initialize a local variable called Out of type StringBuilder to represent the string array in the StringComponent class. StringBuilder Out = new StringBuilder("");
2. Declare a local variable called Count of type int to obtain the number of string elements in the string array. 3. Call the Append method on the Out variable with the following string that has as it final characters an HTML line break, "
": "Strings from C# Component
" 4. Assign to a local variable called myCSStringComp a new instance of the C# version of the StringComponent class. 5. Iterate over all the strings of the C# string component. In each iteration, append the component’s string to the string variable named Out and then append an HTML line break, "
". 6. Append the string "
Strings from the VB Component
" to the Out string and repeat steps 4 and 5 for the Visual Basic version of the StringComponent class. The local variable name is myVBStringComp. 7. Assign the result of processing the string array to an HTML server control so that the output is dynamically generated on a Web page. Use the ToString method to get the output string from the Out variable. 8. Save the file as ClientASP.NET.aspx in the \Labs\ Lab03.3\Starter\ASP.NET_Client folder.
37
38
Module 3: Working with Components
Exercise 2 Building and Testing the ASP.NET Page In this exercise, you will configure a virtual directory to point to a directory where the ASP.NET page is located, so that you can test the ASP.NET application that is a client of the previously created string component.
! Configure a virtual directory 1. Click Start, Control Panel, Performance and Maintenance, Administrative Tools, and then click Internet Information Services. 2. Expand the computer icon, expand the Web Sites folder, and then expand and select Default Web Site. 3. Click the Action menu, point to New, and then click Virtual Directory. The Virtual Directory Creation Wizard starts. 4. Click Next to continue. 5. In the Alias text box, type Test and click Next. 6. Browse to the \Labs\Lab03.3\Starter\ASP.NET_Client folder that contains the file ClientASP.NET.aspx that you created in Exercise 1, click OK, and then click Next. 7. On the Access Permissions page, accept the default selections, click Next, and then click Finish. The Test folder is added to the Default Web Site.
Module 3: Working with Components
39
! Test the ASP.NET Client 1. Ensure that the compiled component DLLs, CompCS.dll and CompVB.dll, are in a \Bin subdirectory under the starting point for the application virtual directory. 2. Open Microsoft Internet Explorer, in the Address bar, type http://localhost/Test/ClientASP.NET.aspx and then press ENTER. A dialog box that generates the correct string output should appear, as shown in the following illustration:
40
Module 3: Working with Components
Review Topic Objective
To reinforce module objectives by reviewing key points.
!
An Introduction to Key .NET Framework Development Technologies
!
Creating a Simple .NET Framework Component
!
Creating a Simple Console Client
!
Creating an ASP.NET Client
Lead-in
The review questions cover some of the key concepts taught in the module.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** 1. How do .NET methods indicate failure? All .NET Framework methods indicate failure by throwing exceptions.
2. What namespace contains the classes to create a Windows Form application? The Windows Forms library is located in the System.Windows.Forms namespace.
3. What is the default extension for ASP.NET pages? By default, ASP.NET files have an .aspx extension or .ascx for user controls.
Module 4: Deployment and Versioning Contents Overview
1
Introduction to Application Deployment
2
Application Deployment Scenarios
7
Related Topics and Tools
31
Lab 4: Packaging and Deployment
37
Review
42
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, place or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. 2001-2002 Microsoft Corporation. All rights reserved. Microsoft, ActiveX, BizTalk, IntelliMirror, Jscript, MSDN, MS-DOS, MSN, PowerPoint, Visual Basic, Visual C++, Visual C#, Visual Studio, Win32, Windows, Windows Media, and Window NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
Module 4: Deployment and Versioning
iii
Instructor Notes Presentation: 90 Minutes Lab: 50 Minutes
After completing this module, students will be able to: !
Package and deploy simple and componentized applications.
!
Create strong-named assemblies.
!
Install and remove assemblies from the global assembly cache.
!
Configure an application to control its binding to an assembly based on the assembly’s location and version data.
Materials and Preparation This section provides the materials and preparation tasks that you need to teach this module.
Required Materials To teach this module, you need the Microsoft® PowerPoint® file 2349B_04.ppt.
Preparation Tasks To prepare for this module, you should: !
Read all of the materials for this module.
!
Complete the lab.
iv
Module 4: Deployment and Versioning
Module Strategy Use the following strategy to present this module: !
Introduction to Application Deployment Introduce common concepts, such as the Microsoft .NET Framework hierarchy of namespaces, the organization of assemblies, and the role of the assembly manifest. Describe simple and componentized applications, and their configuration and distribution scenarios.
!
Application Deployment Scenarios The examples in this section show how to deploy a simple stand-alone application, an application that uses a shared assembly, and an application that makes use of assembly versioning. Introduce compapp, a componentized application, which uses multiple assemblies. Contrast compapp with the simple single assembly Hello World application that was created in Module 2, “Introduction to a Managed Execution Environment,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C#™ .NET).
!
Related Topics and Tools Briefly introduce additional topics that are related to deployment and versioning but are beyond the scope of this course. This section also provides a list of tools that you can use to work with assemblies. You should just inform the students of these topics and encourage them to look for more information in the .NET Framework Software Development Kit (SDK) documentation. In addition, Course 2350A, Securing and Deploying Microsoft .NET Assemblies (Prerelease), covers code access security and role-based security in greater detail.
Module 4: Deployment and Versioning
1
Overview Topic Objective
To provide an overview of the module topics and objectives.
Lead-in
In this module, you will learn about packaging and deployment of Microsoft .NET Framework applications and assemblies.
!
Introduction to Application Deployment
!
Application Deployment Scenarios
!
Related Topics and Tools
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This module introduces the concepts of packaging and deployment of Microsoft® .NET Framework applications and assemblies. The .NET Framework provides improved isolation of application assemblies, simplified application deployment, and robust versioning. This module walks you through the packaging and deployment of a simple Hello World application, and a small, componentized application. These applications are written in C#, the new language designed for the .NET Framework. The steps that are necessary to construct, compile, and run C# applications were explained in Module 2, “Introduction to a Managed Execution Environment,” and Module 3, “Working with Components,” both in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C#™ .NET). Because this course is an introduction to programming in the .NET Framework, you should spend some time reading the .NET Framework Software Development Kit (SDK) documentation. In fact, the labs, demonstrations, and material for this module and other modules in this course are based on several tutorials in the .NET Framework SDK. After completing this module, you will be able to: !
Package and deploy simple and componentized applications.
!
Create strong-named assemblies.
!
Install and remove assemblies from the global assembly cache.
!
Configure an application to control its binding to an assembly based on the assembly’s location and version data.
2
Module 4: Deployment and Versioning
" Introduction to Application Deployment Topic Objective
To introduce issues that arise in application deployment and to introduce the topics in the section.
Lead-in
You can deploy a .NET Framework application in several ways.
!
Common Concepts
!
Simple Applications
!
Componentized Applications
!
Configuration and Distribution
*****************************ILLEGAL FOR NON-TRAINER USE****************************** You can deploy a .NET Framework application in several ways depending on the following considerations: !
The complexity of the application
!
The sharing of assemblies with other applications
!
The application’s security and protection requirements
!
The application’s method of distribution
An application’s deployment is not affected by the .NET Framework common language runtime-compatible language that is used to develop the application. All applications that are written for use with the .NET Framework are compiled to the same self-describing, Microsoft intermediate language (MSIL) code and run with he same .NET Framework runtime. This section introduces common concepts, such as the .NET Framework hierarchy of namespaces, the organization of assemblies, and the role of the assembly manifest. It then describes simple and componentized applications, and their configuration and distribution.
Module 4: Deployment and Versioning
3
Common Concepts Topic Objective
To review the organization of the .NET Framework class library and assemblies, and the role of the assembly manifest.
!
Lead-in
The .NET Framework provides a common class library that is organized into a hierarchical tree of namespaces.
!
Classes and Types Used in .NET Framework Applications Are: #
Organized in a hierarchy of namespaces
#
Stored in PE files, such as DLLs and EXEs
#
Fully described by metadata
Assemblies: #
Are made up of one or more PE files
#
Contain a manifest that identifies the assembly and its files
#
Specify exported and imported classes and types
#
Are units of deployment, reuse, and versioning
*****************************ILLEGAL FOR NON-TRAINER USE****************************** As you learned in Module 2, “Introduction to a Managed Execution Environment,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET), in addition to the common language runtime, the .NET Framework provides a common class library that is organized into a hierarchical tree of namespaces. At the root of this hierarchy is the System namespace, which contains objects for many other useful classes that can be used from any .NET-compatible language. These objects include objects that are used for file I/O, messaging, networking, and security. For Your Information
This topic reviews information that was covered in Module 2. Do not spend much time on this topic. Present the material as the starting point for a discussion of deployment and versioning.
The Organization of PE Files into Assemblies The .NET Framework class library that you and others create are also organized into hierarchical namespaces and stored in portable executable (PE) files, most typically DLLs and EXEs. A single PE file can contain several namespaces, including nested namespaces. You also can split a namespace across multiple PE files. One or more PE files, and possibly non-PE files, such as resources, are combined to create an assembly, which is a physical unit that can be deployed, versioned, and reused.
The Role of the Assembly Manifest In the .NET Framework, each class type is fully described through the type’s metadata. Each assembly contains a manifest that includes the name of each type that is exported from the assembly, along with information about the file that contains that type’s metadata. The manifest also includes information about the identity of the assembly, such as name, files that make up the assembly, and version information, and full information about any dependencies on other assemblies. The .NET Framework runtime uses assembly manifests to locate and bind to the referenced types.
4
Module 4: Deployment and Versioning
Simple Applications Topic Objective
To describe how easily an application can be executed in the .NET Framework.
Lead-in
In the simplest case, a .NET Framework application can be executed locally on any computer on which the .NET runtime is already installed.
!
Require .NET Runtime Be Installed on Local Computer
!
Can Be Run Directly from a File Server or Copied Locally
!
Make No Registry Entries
!
Cannot Break Another Application #
!
Eliminate DLL Hell
Can Be Uninstalled by Deleting Locally Copied File(s)
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In the simplest case, a .NET Framework application can be executed locally on any computer on which the .NET runtime is already installed. The program can run directly from a file server, or the files can be copied locally. Nothing else is required. No registry entries are made, and no other applications are broken or caused to stop running as a result. The fact that no registry entries are made eliminates DLL versioning issues, commonly referred to as “DLL hell.” Just deleting the executable file, if it was copied locally, is sufficient to remove the application and leave no trace on the computer.
Module 4: Deployment and Versioning
5
Componentized Applications Topic Objective
To describe how assemblies are handled in componentized applications.
!
Lead-in
Assemblies Private to an Application #
Componentized applications are only slightly more complex than applications.
!
Assemblies Private to Related Applications #
!
Same as a simple application Deployed into a common subdirectory
Assemblies Shared with Other Unrelated Applications #
Require a strong name and version information
#
Deployed into the global assembly cache
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Componentized applications are only slightly more complex than simple applications. The complexity of componentized applications depends on whether their components are: !
Contained in assemblies that are private to the application.
!
Shared with other related applications.
!
Shared with other potentially unknown applications.
If all of the component assemblies are private, the componentized application can be treated in the same manner as the application. The application can run from a file server, or the application files can be copied to a local volume. Deleting all of a componentized application’s files is sufficient to remove the program. Likewise, if several related applications use the same component assemblies, those assemblies can be located in a common subdirectory. However, if the application uses assemblies that are shared with other unrelated applications, these assemblies can be installed in the global assembly cache and have certain properties, such as a unique strong name that includes version information, that enable the .NET runtime to ensure that the application binds to the appropriate versions.
6
Module 4: Deployment and Versioning
Configuration and Distribution Topic Objective
To introduce options for configuring and deploying applications in the .NET Framework.
Lead-in
As a developer of .NET Framework applications, you should be aware of the different ways in which applications may be configured and packaged for distribution.
!
Configuration #
!
Maintained in plain-text files
Deployment #
Common distribution formats, such as a .CAB file or a .MSI file
#
Common distribution mechanisms, such as Windows 2000 IntelliMirror or Microsoft Systems Management Server
*****************************ILLEGAL FOR NON-TRAINER USE****************************** As a developer of .NET Framework applications, you should be aware of the different ways in which applications may be configured and packaged for distribution. Usually, individual organizations, or an administrator within an organization, decide how an application is packaged for distribution and configured for the organization.
Configuration In the .NET Framework, you can maintain application configuration in plain text files. This use of plain text XML-based application configuration files allows administrators to tailor an application’s behavior on a particular computer without having to involve developers in the process. The following section, Application Deployment Scenarios, presents several common application scenarios. While this module does not cover ASP.NET deployment, most of the concepts that are presented in this module apply to ASP.NET deployment.
Deployment Many client applications may be further packaged in a common distribution format, such as a .CAB file or .MSI file. Client applications may also be installed by using application distribution mechanisms, such as Microsoft Windows® 2000 IntelliMirror® or Microsoft Systems Management Server (SMS), which both use the Microsoft Windows Installer technology. The Microsoft Windows Installer is an installation and configuration service that is included in the Windows 2000 operating system. It is provided in a service pack to Microsoft Windows 95, Microsoft Windows 98, and Microsoft Windows NT® version 4.0. For more information about the Microsoft Windows Installer, see the Platform SDK documentation.
Module 4: Deployment and Versioning
7
" Application Deployment Scenarios Topic Objective
To introduce the topics in the section.
!
A Simple Application
Lead-in
!
A Componentized Application
!
Specifying a Path for Private Assemblies
!
A Strong-Named Assembly
!
Deploying Shared Components
!
A Versioned Assembly
!
Creating Multiple Versions of a Strong-Named Assembly
!
Binding Policy
!
Deploying Multiple Versions of a Strong-Named Assembly
This section shows how to deploy a variety of applications, from simple applications to applications that make use of assembly versioning.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The examples in this section show how to deploy a simple application, an application that uses a shared assembly, and an application that makes use of assembly versioning.
8
Module 4: Deployment and Versioning
A Simple Application Topic Objective
To explain how a simple application is deployed.
!
Lead-in
In Module 2, you looked at the simplest .NET Framework application, the traditional Hello World application written in C#. !
Use the Microsoft Intermediate Language Disassembler (Ildasm.exe) to Examine the Assembly Manifest #
Version information
#
Imported types
#
Exported types
Deploy the Application by: #
Running the executable file directly from a file server, or
#
Installing it locally by copying the file
#
Uninstall the application by deleting the file
*****************************ILLEGAL FOR NON-TRAINER USE****************************** For Your Information
Point out that while Hello World is simplistic, it is extremely versatile in its ability to easily demonstrate many new concepts of the .NET Framework, including deployment and versioning.
In Module 2, “Introduction to a Managed Execution Environment,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET), you looked at the simplest .NET Framework application, the traditional Hello World application written in C#. The C# source code for that program, HelloDemoCS, is as follows: using System; class MainApp { public static void Main() { Console.WriteLine("Hello World using C#!"); } }
This simple executable file prints a single line to the System.Console, a type that is contained in the .NET Framework class library. It does not reference any other libraries and does not itself produce a library.
Module 4: Deployment and Versioning
Using Microsoft Intermediate Language Disassembler to Examine Hello World Compiling this small application generates the HelloDemoCS.exe. Important To use Microsoft Visual Studio® .NET tools within a command prompt window, the command prompt window must have the proper environment settings. The Visual Studio .NET Command Prompt window provides such an environment. To run a Visual Studio .NET Command Prompt window, click Start, All Programs, Microsoft Visual Studio .NET, Visual Studio .NET Tools, and Visual Studio .NET Command Prompt. If you run the Microsoft intermediate language (MSIL) disassembler (Ildasm.exe) against this executable file, a window appears, similar to the following illustration.
The simple Hello World application highlights a particularly important concept behind programming for the .NET Framework. The application is clearly selfdescribing: all of the information that is needed to understand the application is contained in the metadata. The MSIL disassembler shows the classes or types that are created within the application. In the case of the Hello World application, the only class is MainApp. The MSIL disassembler also shows the methods Main and a default constructor, indicated by .ctor. The program does not have any other members. To save information about the assembly to a file, use the Dump command on the File menu.
9
10
Module 4: Deployment and Versioning
To see additional information about the application, double-click Manifest. The following window appears.
The preceding illustration shows the manifest, which contains information about the assembly, including the version (not yet set), external libraries, and types within those libraries, which the application uses.
Deployment Deployment to computers on which the .NET runtime is installed is a simple process. The Hello World application can run directly from a file server. More advanced programs may involve security issues. In the simple Hello World case, no files are placed on the workstation, no entries are made in the system registry, and, in effect, there is no affect on the client workstation. There is also nothing to clean up because there is nothing to remove on the client workstation. As you would expect, HelloDemoCS.exe can also be copied to a local volume. In this scenario, you can remove the program by deleting the file, and as before, nothing would remain on the workstation. Whether you run HelloDemoCS.exe from a file server or copy it to a local volume, running this application will not break another program, and no other application can cause HelloDemoCS.exe to stop functioning.
Module 4: Deployment and Versioning
11
A Componentized Application Topic Objective
To introduce compapp, a componentized application, which uses multiple assemblies.
Lead-in
The Hello World application that is discussed in the preceding topic is completely trivial and hardly representative of even the simplest real-world application.
!
Assembly Component to Be Used by Application #
Assembly Stringer.dll is built from Stringer.cs as follows:
csc csc /target:library /target:library Stringer.cs Stringer.cs !
Client Needs to Reference Assembly
csc csc /reference:Stringer.dll /reference:Stringer.dll Client.cs Client.cs !
Deployment by File Server or Local Copy
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The Hello World application that is discussed in the preceding topic is completely trivial and hardly representative of even the simplest real-world application. This topic covers compapp, a componentized application, which uses multiple assemblies.
12
Module 4: Deployment and Versioning
Creating Stringer.dll The client, Client.exe, calls types that are contained in a component assembly that is named Stringer (Stringer.dll). The source code for the Stringer assembly, is located in Stringer.cs: using System; namespace org { public class Stringer { private string[] StringSet; public Stringer() { StringSet = new string[] { "C# String 0", "C# String 1", "C# String 2", "C# String 3" }; } public string GetString(int index) { if ((index < 0) || (index >= StringSet.Length)) { throw new IndexOutOfRangeException(); } return StringSet[index]; } public int Count { get { return StringSet.Length; } } } }
Module 4: Deployment and Versioning
13
A client application that uses this component could fully qualify a reference to the Stringer class, for instance, as org.Stringer. Alternatively, a client application could include a using statement to allow easy access to the types in Stringer.dll by specifying the namespace, as shown in the Client.cs source code: using System; using org; class MainApp { public static void Main() { Stringer myStringComp = new Stringer(); string[] StringsSet = new string[4]; // Iterate over component's strings Console.WriteLine("Strings from StringComponent"); for (int index = 0; index < myStringComp.Count; index++) { StringsSet[index] = myStringComp.GetString(index); Console.WriteLine(StringsSet[index]); } // ... } }
Building the Application To build this componentized application, you first build the Stringer.dll assembly component. Then, you build Client.exe, importing the Stringer component by using the name of the file that contains the manifest, Stringer.dll, rather than the namespace name, which is org in this case. csc /target:library Stringer.cs csc /reference:Stringer.dll Client.cs
The MSIL disassembler displays the Stringer.dll and shows all of the members, as in the following illustration.
14
Module 4: Deployment and Versioning
Like HelloDemoCS.exe, Client.exe contains manifest information about itself, the System library, and the types that the application uses. However, the manifest now contains information about the Stringer assembly. A MSIL disassembler display of the manifest for Client.exe shows the reference to the Stringer assembly, as in the following illustration.
Deployment Like the simple application, Client.exe can run directly from a file server on any workstation that has the .NET runtime installed. Client.exe and Stringer.dll can also be copied to a local volume. To remove the application, you need only delete the two files.
Module 4: Deployment and Versioning
15
Specifying a Path for Private Assemblies Topic Objective
To show how to use a configuration file to specify a path for private assemblies.
Lead-in
The .NET Framework provides a configuration mechanism that allows administrators to specify a directory from which to load private assemblies.
!
Specifying a Directory From Which to Load Private Assemblies. #
Client.exe.config file specifies a privatePath tag
xmlns="urn:schemas-microsoft-com:asm.v1"> <probing <probing privatePath=“MyStringer"/> privatePath=“MyStringer"/> !
Configuration File’s XML Tags Are Case-Sensitive
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The preceding Client.exe example has one important weakness: both Client.exe and Stringer.dll reside in the same directory. In the real world, an administrator may wish to use a directory structure to manage assemblies. The .NET Framework provides a configuration mechanism that allows administrators to specify a directory from which to load private assemblies.
Locating Stringer.dll and Client.exe in Separate Directories Using the preceding Client example, all of the source code is the same, but for illustration purposes the build process has been modified so that the component application’s Stringer.dll is built in its own subdirectory named MyStringer. cd \compapp csc /target:library /out:MyStringer\Stringer.dll! MyStringer\Stringer.cs csc /reference:MyStringer\Stringer.dll Client.cs
Using a Configuration File to Locate Assemblies at Run Time Although the /reference: compile option locates an assembly in a directory when compiling the program, a separate XML-based application configuration file is required at run time to support assemblies that are located in other directories. For client executable files like the examples that are covered in this module, the configuration file resides in the same directory as the executable file. The configuration file has the complete name of the executable file with an additional extension of .config.
16
Module 4: Deployment and Versioning
The Client.exe configuration file is called Client.exe.config. It specifies a privatePath attribute, as shown in the following example: <probing privatePath="MyStringer"/>
Important The configuration file’s XML tags are case-sensitive. When this configuration file is placed in the same directory as the executable file, at run time, the .NET Framework uses the privatePath attribute to determine where to look for components—in addition to looking in the application directory. Note When loading an assembly, the runtime also searches for a private path that is equal to the name of the assembly. If the Stringer.dll assembly was located in a subdirectory named Stringer, instead of MyStringer, then a configuration file would be unnecessary.
Deployment Like the simple application, the revised Client.exe can run directly from a file server on any workstation that has the .NET runtime installed. Client.exe, Stringer.dll, and the application’s .config file can also be copied to a local volume, using the same relative directory structure. Deleting the files and directory effectively removes the application. While they are not used in the preceding example, it is important to know that in addition to application configuration files, the .NET Framework also supports separate user and Computer Configuration Files for many common configuration settings.
Module 4: Deployment and Versioning
17
A Strong-Named Assembly Topic Objective
To discuss the problems that may occur when multiple applications share components, and to introduce the use of strong names.
Lead-in
The client applications discussed in the preceding topics are limited in that they only illustrate the use of assemblies that are private to the Client.exe and do not demonstrate the use of assemblies that are shared by more than one application.
!
Global Assembly Cache #
!
Contains assemblies shared by unrelated applications
Strong Names Are Required for Assemblies in the Cache #
Generate a public-private key pair:
sn sn –k –k orgKey.snk orgKey.snk #
Add code to source file to specify version and key information:
#if #if STRONG STRONG [assembly: [assembly: System.Reflection.AssemblyVersion("1.0.0.0")] System.Reflection.AssemblyVersion("1.0.0.0")] [assembly: [assembly: System.Reflection.AssemblyKeyFile("orgKey.snk")] System.Reflection.AssemblyKeyFile("orgKey.snk")] #endif #endif #
Compile:
csc csc /define:STRONG /define:STRONG /target:library /target:library /out:AReverser.dll! /out:AReverser.dll! AReverser.cs AReverser.cs
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The client applications discussed in the preceding topics show the basics for constructing a complex program, but they are limited in that they only illustrate the use of assemblies that are private to the Client.exe and do not demonstrate the use of assemblies that are shared by more than one application. This topic discusses the problems that may occur when multiple applications share components. It then introduces the use of strong names, the .NET Framework solution to these problems.
Issues with Sharing Components Many applications use assemblies that are shared by more than one application. These shared assemblies, which are typically provided by third-party developers, are installed in a common location on the system, the global assembly cache. By default, the system looks for each application’s assemblies in the global assembly cache. In classic COM and COM+ applications, the sharing mechanism depends heavily on the Windows System Registry in which information about each component, including its version and physical file location, is stored. This mechanism allows multiple applications to share a single component, but it also allows a component of a newly installed application to replace an existing component and possibly cause other applications to break. Such overwriting of existing components is often difficult to detect because the application that is causing the problem appears to work properly.
18
Module 4: Deployment and Versioning
Strong Names In the .NET Framework, you can solve the problems that are caused by the sharing of components with multiple applications by more strongly associating a distinct build of a component assembly with the client application. A distinct build is indicated by a combination of a version number and a special value that is called the publicKeyToken. Important Strong names provide a strong integrity check. Passing the .NET Framework security checks guarantees that the contents of the assembly have not been changed since it was built. Note, however, that strong names in and of themselves do not imply a level of trust, such as that provided by a digital signature and supporting certificate. When component assemblies are associated with a distinct build, the system can isolate these component assemblies, thus allowing different versions to run at the same time for different client applications. This system of protection is sometimes called side-by-side execution. It differs from backward compatibility because with side-by-side execution, applications can run alongside other versions of the same applications without affecting their respective execution environments. You can facilitate side-by-side execution by assigning strong names to your assemblies. A strong name consists of the assembly’s identity, which includes its simple text name, version number, and culture information, if provided, and a public key. To demonstrate these additional build attributes the client and component scenario is extended by adding an assembly named AReverser. This assembly contains a class with a method that is named Invert, which uses the System.Array.Reverse method to reverse an array of strings. Note The code details of AReverser are not relevant to deployment issues and therefore are not shown. This module’s lab contains the complete code details.
Building the Assembly Without a Strong Name You begin by building the new component assembly without specifying any options to make it have a strong name. First, you compile the new AReverser assembly, as follows: csc /target:library /out:AReverser.dll AReverser.cs
After compiling the new AReverser assembly, you can examine the metadata by using the MSIL disassembler. The MSIL disassembler indicates that the assembly does not have an established version number, as shown in the following code: .assembly AReverser { ... .hash algorithm 0x00008004 .ver 0:0:0:0 } .module AReverser.dll
Module 4: Deployment and Versioning
19
Generating a Public-Private Key Pair To create an assembly with a strong name, you must compile the assembly by using a private key. Public keys are used for verification. Therefore, before compiling the assembly, you must first generate a public-private key pair. You use the Strong Name tool (Sn.exe) to generate a new key pair and place them in a file, as in the following example: sn –k orgKey.snk
Compiling a Strong-Named Assembly Now that you have a private key, you are ready to compile the component, specifying the key file and the version number to be assigned. You can do this by specifying AssemblyVersion and AssemblyKeyFile attributes in the AReverser.cs file using the STRONG conditional compilation symbol: #if STRONG [assembly: System.Reflection.AssemblyVersion("1.0.0.0")] [assembly: System.Reflection.AssemblyKeyFile("orgKey.snk")] #endif
You must then define STRONG in the compile process: csc /define:STRONG /target:library /out:AReverser.dll! AReverser.cs
If you run the MSIL disassembler again on AReverser.dll, you can verify that the assembly is now strong-named from the presence of a .publickey property and a non-default version, which is specified by the .ver property of 1:0:0:0. The following example shows the disassembled code: .assembly AReverser { ... .publickey = (00 ... 71 8A 7D 6A D7 ) .hash algorithm 0x00008004 .ver 1:0:0:0 } .module AReverser.dll
For more information about the Strong Name tool, see Packaging and Deployment Tools in this module.
20
Module 4: Deployment and Versioning
Deploying Shared Components Topic Objective
To explain the commands that are used to install, examine, and remove assemblies from the global assembly cache.
Lead-in
While components can easily be shared by related applications by putting them in a common subdirectory, shared assemblies that are used by many applications on the system are often stored in the system’s global assembly cache.
!
Installing the Strong-Named Component in the Global Assembly Cache gacutil gacutil /i /i AReverser.dll AReverser.dll
!
Examining the Global Assembly Cache gacutil gacutil /l /l
!
Removing a Shared Component File gacutil gacutil /u /u AReverser AReverser
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Deployment with strong-named assemblies is usually more complicated than deployment with assemblies that are not strong-named. While components can easily be shared by related applications by putting them in a common subdirectory, shared assemblies that are used by many applications on the system are often stored in the system’s global assembly cache. As with the previous examples of componentized applications, this revised Client.exe can run directly from a file server on any workstation on which the .NET runtime is installed. The Client.exe and Stringer.dll files can also be copied to a local volume.
Installing a Strong-Named Assembly into the Global Assembly Cache Installing the strong-named component into the global assembly cache requires the following additional command on the computer that will be running the corresponding Client.exe program: gacutil /i AReverser.dll
Note You must have Administrator privileges on a computer to install assemblies into the global assembly cache. Assemblies that are installed in the global assembly cache must have a strong name. After installing the AReverser assembly, you can then examine the system assembly cache by typing: gacutil /l
Module 4: Deployment and Versioning
21
The output of this command is: ... AReverser, Version=1.0.0.0, Culture=neutral, PublicKeyToken=! 0588613cb04b772e, Custom=null ...
You can also examine the system assembly cache by navigating to the \WindowsDirectory\Assembly directory and by using the cache shell extension.
Uninstalling a Strong-Named Assembly from the System Cache Cleaning up applications that use strong-named assemblies requires more work than cleaning up the simple applications or componentized applications that are described in the preceding topics. In addition to deleting the executable files, you should remove the shared component file from the global assembly cache since the global assembly cache is not automatically scavenged. To remove a shared component file, an administrator can use the cache shell extension, select the appropriate components, and delete them. Developers and administrators who want to automate the process, however, should use the command-line interface to the assembly cache manager as follows: gacutil /u AReverser
To confirm the removal, you can view the contents of the global assembly cache by using the following command: gacutil /l
For more information about installing and uninstalling shared assemblies to the global assembly cache, see Packaging and Deployment Tools in this module.
22
Module 4: Deployment and Versioning
A Versioned Assembly Topic Objective
To discuss the physical representation and logical mapping of the compatibility version number.
!
Applications Need to Bind to a Suitable Version of a Shared Assembly
Lead-in
!
The Version Number Is Represented by a 4-Part Number
The final packaging and deployment example in this module involves updating the shared assembly to a new version.
<major <major version>.<minor version>.<minor version>.... number> !
Applications Get the Versions of Assemblies with Which They Were Built and Tested #
Unless overridden by explicit policy rules
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The final packaging and deployment example in this module involves updating the shared assembly to a new version. In Creating Multiple Versions of a Strong-Named Assembly in this module, the shared assembly is updated to deliberately break compatibility with the client and to demonstrate how the .NET Framework allows you to configure the client application to bind to the desired version of the shared assembly.
Versioning Each assembly has a specific compatibility version number as part of its identity. As such, two assemblies that differ by compatibility version are completely different assemblies as far as the .NET runtime class loader is concerned. This compatibility version number is physically represented as a four-part number with the following format: <major version>.<minor version>..
Module 4: Deployment and Versioning
23
Each segment of this number has a specific meaning to the .NET runtime as it decides which version of an assembly to load. Logically, the compatibility version number has three parts, with the following meanings: 1. Incompatible A change has been made to the assembly that is known to be incompatible with previous versions. Example: Major new release of the product 2. Maybe Compatible A change has been made to the assembly that is thought to be compatible and carries less risk than an incompatible change. However, backward compatibility is not guaranteed. Example: Service Pack or release of a new daily build 3. Quick Fix Engineering (QFE) An engineering fix that customers may want to upgrade to. Example: An emergency security fix These three logical parts correspond to the physical four-part version number as follows:
For example, an assembly with compatibility version number 2.0.0.0 is considered incompatible with an assembly whose compatibility number is 1.0.0.0. Compatibility number 2.0.2.11 is considered a QFE to compatibility number 2.0.2.1.
24
Module 4: Deployment and Versioning
Creating Multiple Versions of a Strong-Named Assembly Topic Objective
To explain how to create multiple versions of a shared component by using strong-named assemblies.
Lead-in
In the .NET Framework, you can solve the problems that are inherent in working with multiple versions of a shared component by using strong-named assemblies.
!
Building Two New Versions of AReverser with a New Key Pair #
Specify version 2.0.0.0 for one and 2.0.1.0 for the other For AReverser_v2.0.0.0\AReverser.cs:
#if #if STRONG STRONG [assembly: [assembly: System.Reflection.AssemblyVersion("2.0.0.0")] System.Reflection.AssemblyVersion("2.0.0.0")] [assembly: [assembly: System.Reflection.AssemblyKeyFile("orgVerKey.snk")] System.Reflection.AssemblyKeyFile("orgVerKey.snk")] #endif #endif !
Build
csc csc /define:STRONG /define:STRONG /target:library! /target:library! /out:AReverser_v2.0.0.0\AReverser.dll! /out:AReverser_v2.0.0.0\AReverser.dll! AReverser_v2.0.0.0\AReverser.cs AReverser_v2.0.0.0\AReverser.cs !
Use Ildasm.exe to Examine Different Versions #
Note the publickey and version numbers
.assembly .assembly AReverser AReverser {{ ... ... .publickey .publickey == (00 (00 24 24 ... ... 82 82 B1 B1 F2 F2 A0 A0 )) .hash .hash algorithm algorithm 0x00008004 0x00008004 .ver .ver 2:0:1:0 2:0:1:0
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In the .NET Framework, you can solve the problems that are inherent in working with multiple versions of a shared component by using strong-named assemblies. This topic examines how multiple versions of an assembly are created. Building on the code in the preceding strong-named AReverser assembly, two separate versions of the strong-named component are created, and additional application configuration options are used to show how an application can be made to run. A method in version 2.0.1.0 of AReverser.dll is deliberately made incompatible with the same method in version 2.0.0.0 so that a client that successfully calls the method that is using version 2.0.0.0 will fail with the later revision. Versioning keys can change from one version of an assembly to the next. To illustrate this, you generate a new key pair using the Strong Name (Sn.exe) tool and place that new key pair in a file, as follows: sn –k orgVerKey.snk
After creating a new private key, you add the following to the AReverser.cs file in the AReverser_v2.0.0.0 subdirectory: #if STRONG [assembly: System.Reflection.AssemblyVersion("2.0.0.0")] [assembly: System.Reflection.AssemblyKeyFile("orgVerKey.snk")] #endif
Module 4: Deployment and Versioning
25
You add the following to the AReverser.cs file in the AReverser_v2.0.1.0 subdirectory: #if STRONG [assembly: System.Reflection.AssemblyVersion("2.0.1.0")] [assembly: System.Reflection.AssemblyKeyFile("orgVerKey.snk")] #endif
You compile both of these versions by using the commands: csc /define:STRONG /target:library! /out:AReverser_v2.0.0.0\AReverser.dll! AReverser_v2.0.0.0\AReverser.cs csc /define:STRONG /target:library! /out:AReverser_v2.0.1.0\AReverser.dll! AReverser_v2.0.1.0\AReverser.cs
If you run the MSIL disassembler on these two updated files, you can verify that the assemblies are strong-named as indicated by the version number, 2.0.0.0 or 2.0.1.0, depending on which one you inspect with the MSIL disassembler. Notice that these two assemblies have the same publickey property, but the value of this property is different than the previous version 1.0.0.0 AReverser.dll. This property is different because a different key pair was used, orgVerKey.snk, instead of orgKey.snk. The following example shows the MSIL disassembler output for version 2.0.1.0 of the AReverser.dll: .assembly AReverser { ... .publickey = (00 24 ... 82 B1 F2 A0 ) .hash algorithm 0x00008004 .ver 2:0:1:0 }
26
Module 4: Deployment and Versioning
Binding Policy Topic Objective
!
To discuss binding policy.
#
Lead-in
Whenever the .NET Framework is asked to bind to a specific version of a shared assembly, the version of the assembly reference may be altered at several policy-resolution stages before the .NET Framework finally decides to which version to bind.
Policy Resolution
!
Allows an assembly reference, specified at compile time, to be modified after the application has been deployed without recompiling
Happens in the Following Stages: 1. Application-policy resolution 2. Publisher-policy resolution 3. Administrator-policy resolution
!
In Each Stage, an XML Configuration File Is Read #
! !
Note: XML is case-sensitive
Version Numbers of Assemblies That Are Not Strong-Named Are Not Checked Configuration File Tag Examples # privatePath # bindingRedirect
*****************************ILLEGAL FOR NON-TRAINER USE****************************** When the .NET Framework is asked to bind to a specific version of a shared assembly, the version of the assembly reference may be altered at several policy-resolution stages before the .NET Framework finally decides which version to bind to. This policy-resolution process allows an assembly reference, which is specified at compile time, to be modified after the application has been deployed, without recompiling the assemblies involved. Policy resolution happens in the following three stages: 1. Application-policy resolution 2. Publisher-policy resolution 3. Administrator-policy resolution In each stage, an XML configuration file that describes the policy is read. The privatePath attribute in the application-configuration file illustrates the simplest form of application-policy resolution. In addition, the bindingRedirect tag can be used to redirect the reference to a different version of a shared assembly. Note The version numbers of assemblies that are not strong-named are not checked.
Module 4: Deployment and Versioning
27
Publisher-policy resolution allows shared-component vendors to make compatibility statements among different revisions of their software. These perassembly configuration files are distributed as strong-named assemblies and are installed into the global assembly cache as part of a service pack-style update. Any binding redirects that are specified by the publisher-policy file are then applied to the reference, which is then subject to administrator-policy resolution. Because publisher-policy assemblies affect all applications on the system, it is important that these assemblies are installed separately from the application. Administrator-policy resolution is the final—and the strongest—stage in the binding policy-resolution process. The administrator-policy file is located in the WindowsDirectory\Microsoft.NET\Framework\v1.0.FinalBuildNumber\ CONFIG directory and is called Machine.config. This file has the same XMLbased schema as the policy files that are used in the two previous stages of policy resolution. Administrator policy affects all assembly bindings that occur on the system and can never be bypassed.
28
Module 4: Deployment and Versioning
Deploying Multiple Versions of a Strong-Named Assembly Topic Objective
To explain how to deploy multiple versions of a strong-named assembly.
!
Lead-in
After you have configured both versions of the AReverser assembly to use strong names, you can deploy those versions.
Install Both Versions of AReverser.dll into the Global Assembly Cache gacutil gacutil /i /i AReverser_v2.0.0.0\AReverser.dll AReverser_v2.0.0.0\AReverser.dll gacutil /i gacutil /i AReverser_v2.0.1.0\AReverser.dll AReverser_v2.0.1.0\AReverser.dll
!
Compile the VerClient Executable and Specify Version 2.0.0.0 of the AReverser Component
csc csc /reference:MyStringer\Stringer.dll! /reference:MyStringer\Stringer.dll! /reference:AReverser_v2.0.0.0\AReverser.dll /reference:AReverser_v2.0.0.0\AReverser.dll VerClient.cs VerClient.cs !
Use Version Policies to Control Assembly Binding at Run Time
*****************************ILLEGAL FOR NON-TRAINER USE****************************** After you have configured both versions of the AReverser assembly to use strong names, you can deploy those versions. To configure client applications to use strong-named assemblies of a specific version, you must first install both 2.0 versions of AReverser.dll into the global assembly cache as follows: gacutil /i AReverser_v2.0.0.0\AReverser.dll gacutil /i AReverser_v2.0.1.0\AReverser.dll
After installing these AReverser assemblies, you can then examine the global assembly cache by using: gacutil /l
You can also examine the system assembly cache by navigating to the \WindowsDirectory\Assembly directory, and using the cache shell extension. You now compile the VerClient executable file, for which you specify the version 2.0.0.0 of the AReverser component, as shown in the following example: csc /reference:MyStringer\Stringer.dll! /reference:AReverser_v2.0.0.0\AReverser.dll VerClient.cs
Module 4: Deployment and Versioning
29
Version Policies All versioning of assemblies that use the common language runtime takes place at the assembly level. The specific version of an assembly and the versions of dependent assemblies are recorded in the assembly’s manifest. The rules that specify the acceptable versions of an assembly are called version policies. Version policies are expressed in configuration files.
Using Configuration Files to Override Version Policy According to the default version policy for the runtime, applications run only with the versions that they were built and tested with unless these versions were overridden by explicit version policy in configuration files. These configuration files, which include the application configuration file, a publisher policy file, and the machine’s administrator configuration file, provide the means for overriding the version policy that is recorded in the assembly manifest.
Binding to a Specific Assembly Version In a configuration file, you can specify a version policy that instructs the runtime to bind to a specific version of an assembly that you want the application to use. This specific version policy allows you to bind to a different version of an assembly than the version that is recorded in the assembly manifest of the calling assembly. In particular, the bindingRedirect tag can be used to redirect the reference to a different version of a shared assembly, by overriding the version in the original reference with this newer version. The following option says that for any assembly reference from version 2.0.0.0 through 2.0.0.9, the version that should instead be used at run time is 2.0.1.0:
This option allows an administrator to reconfigure an application without having to have it recompiled.
30
Module 4: Deployment and Versioning
The following sample VerClient.exe.config file instructs the runtime to look in subdirectory MyStringer for assembly references and to bind to version 2.0.0.0 of AReverser: <probing privatePath="MyStringer"/> <dependentAssembly>
Note The publicKeyToken tag’s value can be obtained using the Sn.exe tool or by examining the global assembly cache by using gacutil /l or the cache shell extension. Important The Sn.exe switches to obtain the publicKeyToken do not work correctly in some of the earlier .NET Framework versions, such as build 9188. Key Points
Reinforce the fact that the configuration file’s XML tags are case-sensitive.
Because a method of a type in version 2.0.1.0 of AReverser.dll was deliberately made incompatible with the same method in version 2.0.0.0, a version 2.0.0.0compatible client that attempted to call this later revision would fail. If VerClient.exe.config changed the line: newVersion="2.0.0.0"/>
to: newVersion="2.0.1.0"/>
The resulting error message would be similar to: Unhandled Exception: System.MissingMethodException:! Method not found: at MainApp.Main()
More typically, the configuration file mechanism allows an administrator to repair an application so that it will continue to run even if it is broken by a subsequent install of another application that used a different version of the same shared component. Finally, when you need to clean up the application, remove the shared component files from the global assembly cache, as shown in the following example: gacutil /u AReverser
Module 4: Deployment and Versioning
31
" Related Topics and Tools Topic Objective
To introduce the topics in the section.
Lead-in
This section briefly introduces additional topics that are related to deployment and versioning.
!
Related Topics
!
Packaging and Deployment Tools
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This section briefly introduces additional topics that are related to deployment and versioning, but are beyond the scope of this course. This section also provides a list of tools that you can use to work with assemblies.
32
Module 4: Deployment and Versioning
Related Topics Topic Objective
To briefly introduce several related topics and provide references for students to follow up on.
Lead-in
This module introduces the concepts of packaging and deployment of .NET Framework applications and assemblies. Several related topics are worth mentioning, and, when appropriate, references to additional information are provided.
!
ASP.NET
!
Assemblies
!
Security
!
Localization
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This module introduces the concepts of packaging and deployment of .NET Framework applications and assemblies. Several related topics are worth mentioning, and, when appropriate, references to additional information are provided. For Your Information
Do not spend much time on this topic. The subject matter is mostly beyond the scope of the course. You should just inform the students of these topics and encourage them to look for more information in the provided references.
ASP.NET This module focuses on packaging and deploying traditional client applications. For more information about deploying ASP.NET applications to a Web server, see the .NET Framework SDK documentation.
Assemblies For more information about how the .NET Framework locates assemblies when they are referenced at run time, see “How the Runtime Locates Assemblies” in the .NET Framework SDK documentation. This topic covers the Assembly Resolver, strong-named assemblies, application and administrator version policies, codebase locations, and QFE updates.
Security The.NET Framework offers code access security and role-based security to address mobile code security concerns and to provide support that enables components to determine what users are authorized to do. These security mechanisms use a simple, consistent model so that developers who are familiar with code access security can easily use role-based security and developers who are familiar with role-based security can easily use code access security. Code access security and role-based security are implemented by using a common infrastructure that is supplied by the common language runtime.
Module 4: Deployment and Versioning
33
Code Access Security and Role-based Security Code access security uses permissions to control the access that code has to protected resources and operations. It helps to protect computer systems from malicious mobile code and provides a way to allow mobile code to run safely. Role-based security provides information that is needed to make decisions about what a user is allowed to do. These decisions can be based on the user’s identity, role membership, or both. A more detailed discussion of security is outside the scope of this course. For more information about security, see “Securing Your Application” in the .NET Framework SDK documentation. Additionally, Course 2350A, Securing and Deploying Microsoft .NET Assemblies (Prerelease) covers code access security and role-based security in greater detail.
Localization To localize an application, you typically perform the following steps: 1. Separate your default resources from the code, and specify the localized text in a text file. 2. Compile the text file into a .resources file. 3. Package your default resources in the main assembly by using the C# compiler. 4. Create satellite resource assemblies, including satellites for .NET Framework cultures, by using the Alink tool. 5. Deploy the satellites to a directory structure underneath the main application. 6. Write the code to access the resources at run time. For more information about localization, see the .NET Framework SDK documentation.
34
Module 4: Deployment and Versioning
Packaging and Deployment Tools Topic Objective
To provide a brief overview of packaging and deployment tools that are included in the .NET Framework SDK.
Lead-in
The .NET Framework SDK includes several useful tools for examining assemblies and working with the global assembly cache.
!
Assembly Linker (Al.exe)
!
Global Assembly Cache tool (Gacutil.exe)
!
MSIL Disassembler (Ildasm.exe)
!
Strong Name (Sn.exe)
!
Native Image Generator (Ngen.exe)
!
Assembly Binding Log Viewer (Fuslogvw.exe)
!
.NET Framework Configuration Tool (Mscorcfg.msc)
!
Code Access Security Policy Tool (Caspol.exe)
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The .NET Framework SDK includes several useful tools for examining assemblies and working with the global assembly cache. For more information about the tools that are introduced in this topic, see “.NET Framework Tools and Debugger” in the .NET Framework SDK documentation.
Assembly Linker (Al.exe) The Assembly Linker is most useful to developers who need to create a single assembly from multiple components’ files, such as those that may be produced with mixed-language development.
Global Assembly Cache Tool (Gacutil.exe) The global assembly cache tool (Gacutil.exe) allows you to view and manipulate the contents of the global assembly cache. This tool provides much of the same cache-viewing functionality as the Windows Shell Extension (Shfusion.dll), but the global assembly cache tool is more usable from build scripts, makefiles, and batch files. Specifically, Gacutil.exe allows you to install assemblies into the cache, remove them from the cache, and list the contents of the cache. You must have Administrator privileges on a computer to install assemblies into the global assembly cache. To avoid removing more than one assembly from the global assembly cache, you can use a command with the name of your specific assembly, as follows: gacutil /u! hello,ver=1.0.0.1,Culture=en,PublicKeyToken=874e23ab874e23ab
The preceding command removes only the hello assembly that matches the fully specified version number, culture, and public key.
Module 4: Deployment and Versioning
35
The following command lists the contents of the global assembly cache: gacutil /l
MSIL Disassembler (Ildasm.exe) You can also explore the namespaces in files that come with the runtime or in files that you or others create by using the command-line MSIL disassembler. To open a window that displays information about System.Data.dll, use the following command: >ildasm System.Data.dll
Each of the namespace nodes represents a separate namespace, which can be expanded to explore the class objects and their methods and properties. The MSIL disassembler also features a number of command-line options, which are particularly useful when you want to redirect output to the console or to a text file for subsequent analysis. Tip Place a shortcut to Ildasm.exe in your SendTo folder.
Strong Name Tool (Sn.exe) When working with shared components, you can use the Strong Name (Sn.exe) command-line tool for several purposes. For example, you can use the tool to generate a new public-private key pair and write that pair to an output file, as follows: sn -k
Native Image Generator (Ngen.exe) The Native Image Generator creates a native image from a managed assembly and installs it into the native image cache on the local computer. Running Ngen.exe on an assembly allows the assembly to load faster, because it restores code and data structures from the native image cache rather than generating them dynamically. Pre-compiling assemblies with Ngen.exe can improve the startup time for applications, because much of the work required to execute code has been done in advance. Therefore, it is more appropriate to use Ngen.exe for client-side applications where you have determined that the CPU cycles consumed by justin-time (JIT) compilation cause slower performance. Because there are many factors that affect the startup time of an application, you should carefully determine which applications would benefit from the use of Ngen.exe. Experiment by running both a JIT-compiled and a pre-compiled version of a candidate assembly in the environment in which it will be used. This will allow you to compare the startup times for the same assembly executing under different compilation schemes.
36
Module 4: Deployment and Versioning
Assembly Binding Log Viewer (Fuslogvw.exe) For Your Information
The Assembly Binding Log Viewer tool is used in the lab.
The Assembly Binding Log Viewer can be used to display details for assembly binds. This information helps you diagnose why the .NET Framework cannot locate an assembly at run time. These failures are usually the result of an assembly deployed to the wrong location or a mismatch in version numbers or cultures.
.NET Framework Configuration Tool (Mscorcfg.msc) The .NET Framework Configuration Tool provides a graphical interface for managing .NET Framework security policy and applications that use remoting services. This tool also allows you to manage and configure assemblies in the global assembly cache. The .NET Framework Configuration Tool is a Microsoft Management Console (MMC) snap-in.
Code Access Security Policy Tool (Caspol.exe) The Code Access Security Policy Tool allows you to examine and modify machine, user, and enterprise-level code access security policies. For more information about Caspol, see Course 2350A, Securing and Deploying Microsoft .NET Assemblies and the .NET SDK.
Module 4: Deployment and Versioning
37
Lab 4: Packaging and Deployment Topic Objective
To introduce the lab.
Lead-in
In this lab, you will build an application that is named client, which retrieves an array of strings from an instance of the Stringer class and reverses the order of these strings by using an instance of the AReverser class. These classes are packaged in two separate assembly libraries that are named Stringer.dll and AReverser.dll.
*****************************ILLEGAL FOR NON-TRAINER USE******************************
Objectives After completing this lab, you will be able to: !
Create an application by using private assemblies in multiple directories.
!
Install and remove a strong-named assembly from the global assembly cache.
!
Configure an application to control its binding to an assembly based on the assembly’s location and version data.
Lab Setup Starter and solution files are associated with this lab. The starter files are in the folder \Labs\Lab04\Starter. The solution files are in the folder \Labs\Lab04\Solution.
Scenario You will build an application that is named client, which retrieves an array of strings from an instance of the Stringer class and reverses the order of these strings by using an instance of the AReverser class. These classes are packaged in two separate assembly libraries that are named Stringer.dll and AReverser.dll.
Estimated time to complete this lab: 50 minutes
38
Module 4: Deployment and Versioning
Exercise 1 Locating Private Assemblies in Multiple Directories In this exercise, you will build the client application by using private assemblies that are located in multiple directories.
! Examine the application • Open Notepad and examine the source code for the files that are listed in the following table. Filename
Directory location
Client.cs
\Labs\Lab04\Starter\Compapp
Stringer.cs
\Labs\Lab04\Starter\Compapp\MyStringer
AReverser.cs
\Labs\Lab04\Starter\Compapp\AReverser
! Compile and build the application in a command window Important To use Microsoft Visual Studio .NET tools within a command prompt window, the command prompt window must have the proper environment settings. The Visual Studio .NET Command Prompt window provides such an environment. To run a Visual Studio .NET Command Prompt window, click Start, All Programs, Microsoft Visual Studio .NET, Visual Studio .NET Tools, and Visual Studio .NET Command Prompt. 1. Go to the MyStringer directory and build the private assembly library named Stringer.dll in that directory. 2. Go to the AReverser directory and make the private assembly named AReverser.dll in that directory. 3. Go to the Compapp directory and build the client application, referencing the private assemblies from steps 1 and 2. Reference the DLLs in their own directories. Do not copy them to the Compapp directory.
Module 4: Deployment and Versioning
39
! Execute the application 1. Run the Assembly Binding Log Viewer by executing Fuslogvw.exe. Click on the right hand check box labeled Log Failures. 2. Execute the Client.exe application and note the exception in the space that is provided. 3. In the Assembly Binding Log Viewer click on Refresh. Then double click on the Client.exe application, and view the log entry. 4. Create a configuration file in the Client directory to specify the appropriate privatePath so that the application can successfully bind and load the necessary classes. The Client.exe application should generate the following output: Strings from StringComponent C# String 0 C# String 1 C# String 2 C# String 3 Reversed Array Strings C# String 3 C# String 2 C# String 1 C# String 0
40
Module 4: Deployment and Versioning
Exercise 2 Using Strong-Named Assemblies and Versioning In this exercise, you will build the client application by using strong-named assemblies that are located in the global assembly cache. You will then configure an application to control its binding to an assembly based on the assembly’s location and version data.
! Build a compatible strong-named version of AReverser 1. From a command prompt window, navigate to the \Labs\ Lab04\Starter\Compapp directory, and use the Strong Name tool to create a public-private key pair file that is named OrgVerKey.snk. 2. Open Notepad. On the File menu, click Open, browse to \ Labs\Lab04\Starter\ Compapp\AReverser_v2.0.0.0, and open AReverser.cs. 3. Examine the source code and note that it is the same code that was used in the preceding exercise. In particular, note that the signature of the Invert method is: public string[] Invert(string[] myString)
4. Modify the source code to specify the key pair file from step 1 and to specify a version number of 2.0.0.0. 5. From a command prompt window, navigate to the \Labs\ Lab04\Starter\Compapp directory and build the 2.0.0.0 strong-named version of AReverser.cs. The AReverser.dll should be output into the AReverser_v2.0.0.0 subdirectory. 6. Use the global assembly cache tool (Gacutil.exe) to install this assembly in the global assembly cache. 7. Examine the global assembly cache by using Gacutil.exe or Windows Explorer, and note the presence of AReverser and its properties.
! Build an incompatible strong-named version of AReverser 1. In Notepad, on the File menu, click Open, browse to \ Labs\Lab04\Starter\Compapp\AReverser_v2.0.1.0, and open AReverser.cs. 2. Examine the source code and note that it is different than the code that was used in the preceding exercise. In particular, note that the signature of the Invert method is: public string[] Invert(string[] myString, int myCount)
3. Modify the source code to specify the key pair file OrgVerKey.snk and to specify a version number of 2.0.1.0. 4. From a command prompt window, navigate to the \Labs\ Lab04\Starter\Compapp directory and build the 2.0.1.0 strong-named version of AReverser.cs. The AReverser.dll should be output into the AReverser_v2.0.1.0 subdirectory. 5. Install this assembly in the global assembly cache. 6. Examine the global assembly cache by using Gacutil.exe or Windows Explorer, and note the presence of AReverser and its properties.
Module 4: Deployment and Versioning
41
! Compile, build, and execute the application 1. From a command prompt window, navigate to the Client directory and build the client application. Reference the private assembly MyStringer\Stringer.dll and the strong-named assembly AReverser_v2.0.0.0\AReverser.dll. 2. Execute the Client.exe application, which should generate the following output: Strings from StringComponent C# String 0 C# String 1 C# String 2 C# String 3 Reversed Array Strings C# String 3 C# String 2 C# String 1 C# String 0
! Configure to bind to the latest version of AReverser Tip You can obtain the publicKeyToken tag’s value by examining the global assembly cache using gacutil /l or the cache shell extension. 1. Modify the configuration file to bind to the incompatible later version 2.0.1.0 of AReverser. 2. Execute Client.exe and note the exception. 3. Use the global assembly cache tool to remove the two strong-named versions of AReverser. 4. Confirm the removal of the two strong-named versions of AReverser by viewing the global assembly cache with Gacutil.exe and Windows Explorer.
42
Module 4: Deployment and Versioning
Review Topic Objective
To reinforce module objectives by reviewing key points.
Lead-in
The review questions cover some of the key concepts taught in the module.
!
Introduction to Application Deployment
!
Application Deployment Scenarios
!
Related Topics and Tools
*****************************ILLEGAL FOR NON-TRAINER USE****************************** 1. What part of the assembly identifies its imported and exported types and its version information? The manifest.
2. What software does a computer require to run a .NET application locally? The .NET Framework common language runtime.
3. Name two simple ways to run a .NET Framework application. Copy the executable file and referenced assemblies to the local computer, or access them on a file server.
Module 4: Deployment and Versioning
43
4. Describe how an application can use an assembly that is located in an application’s subdirectory. Create a configuration file in the application’s directory that specifies a PrivatePath as follows: <probing privatePath="MyStringer"/>
5. What kind of assembly can be placed in the global assembly cache and be versioned? A strong-named assembly.
6. What command is used to generate public-private key pairs? The Strong Name (Sn.exe) tool is used to generate a new key pair and place them in a file: sn –k orgKey.snk
7. What command is used to install a strong-named assembly into the global assembly cache? > gacutil -i
THIS PAGE INTENTIONALLY LEFT BLANK
Module 5: Common Type System Contents Overview
1
An Introduction to the Common Type System
2
Elements of the Common Type System
8
Object-Oriented Characteristics
25
Lab 5: Building Simple Types
39
Review
44
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, place or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. 2001-2002 Microsoft Corporation. All rights reserved. Microsoft, ActiveX, BizTalk, IntelliMirror, Jscript, MSDN, MS-DOS, MSN, PowerPoint, Visual Basic, Visual C++, Visual C#, Visual Studio, Win32, Windows, Windows Media, and Window NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
Module 5: Common Type System
iii
Instructor Notes Presentation: 90 Minutes Lab: 45 Minutes
After completing this module, students will be able to: !
Describe the difference between value types and reference types.
!
Explain the purpose of each element in the type system, including values, objects, and interfaces.
!
Explain how object-oriented programming concepts, such as abstraction, encapsulation, inheritance, and polymorphism, are implemented in the Common Type System.
Materials and Preparation This section provides the materials and preparation tasks that you need to teach this module.
Required Materials To teach this module, you need the Microsoft® PowerPoint® file 2349B_05.ppt.
Preparation Tasks To prepare for this module, you should: !
Read all of the materials for this module.
!
Complete the lab.
Module Strategy Use the following strategy to present this module: !
An Introduction to the Common Type System Cover the basic architecture of the Common Type System. Introduce System.Object as the root type for all types in the Microsoft .NET Framework common language runtime. Explain how all types in the Common Type System are either reference types or value types.
!
Elements of the Common Type System In this section, cover the primitive types, objects, properties, and other elements of the Common Type System. The information will not be new to students with a C++ object-oriented background, so you can cover these topics quickly or omit most of the material in this section. This decision will depend on the student’s experience.
!
Object-Oriented Characteristics Discuss how the object-oriented characteristics of the .NET Framework common language runtime are supported. If the class is already experienced in object-oriented techniques, you can omit this section.
Module 5: Common Type System
1
Overview Topic Objective
To provide an overview of the module topics and objectives.
Lead-in
In this module, you will learn about the Common Type System and object-oriented programming.
!
An Introduction to the Common Type System
!
Elements of the Common Type System
!
Object-Oriented Characteristics
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this module, you will learn about the Common Type System. Specifically, you will learn to differentiate between value types and reference types. You will also examine how classes, interfaces, properties, methods, events, and values are represented in the Microsoft® .NET Framework. After completing this module, you will be able to: !
Describe the difference between value types and reference types.
!
Explain the purpose of each element in the type system, including values, objects, and interfaces.
!
Explain how object-oriented programming concepts, such as abstraction, encapsulation, inheritance, and polymorphism, are implemented in the Common Type System.
2
Module 5: Common Type System
" An Introduction to the Common Type System Topic Objective
To provide an overview of the section topics.
Lead-in
In this section, you will learn about the architecture of the Common Type System.
!
Common Type System Architecture
!
Value Types vs. Reference Types
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this section, you will learn about the Common Type System architecture. Specifically, you will learn how types are specified and represented in the .NET Framework common language runtime, and you will learn the difference between value types and reference types.
Module 5: Common Type System
3
Common Type System Architecture Topic Objective
To describe the basic architecture of the Common Type System.
Lead-in
A type defines characteristics for a set of values.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** A type defines characteristics for a set of values. For example, the set of whole numbers between negative 32768 and positive 32767 is called the short type in C#, or System.Int16 in the .NET Framework class library. The short type has a physical representation, which, in this case, is a 16-bit value. The short type also defines operations allowed on the type, such as addition and subtraction.
System.Object System.Object is the root type for all types in the .NET Framework common language runtime. For this reason, any type you use will have System.Object as its base class. System.Object has the following methods. Methods
Description
Public Equals
Overloaded. Determines whether two Object instances are equal.
Public GetHashCode
Serves as a hash function for a particular type; suitable for use in hashing algorithms and data structures, such as a hash table.
Public GetType
Gets the type of the object.
Public ReferenceEquals
Determines whether the specified Object instances are the same instance.
Public ToString
Returns a string that represents the current object.
Protected Finalize
Overridden. Allows an Object to attempt to free resources and perform other cleanup operations before the Object is reclaimed by garbage collection.
Protected MemberwiseClone
Creates a shallow copy of the current Object.
For more information about the methods of System.Object, see Module 6, “Working with Types,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C#™ .NET).
4
Module 5: Common Type System
Value Types Value types are any type that inherits from System.ValueType. Value types are typically simple types, such as integer values or long values. Value types are special because they are allocated on the stack. New value types are defined by using the struct or enum keywords. You cannot create value types by using the class keyword. System.ValueType inherits from System.Object, which means that all value types can behave like System.Object. The type system defines primitive types, which are Byte, Int16, Int32, Int64, Single, Double, Boolean, Char, Decimal, IntPtr, and String. There are also built-in types such as System.Array available.
Reference Types Reference types are any type that inherits from System.Object and not System.ValueType. Reference types are analogous to pointers in C++, but there are subtle differences in the way reference types and pointers work. Reference types are allocated on the heap. Reference types also must be garbage collected. One example of a reference type is the System.IO file class, which represents a file in the system.
Fields, Methods, and Properties The Common Type System defines fields, methods, and properties that are available to all value types and reference types. A field is a data value stored in a class. For example, Name and Age could be fields describing an employee stored in an Employee structure. A method is an operation that can be performed on a class. For example, a Tenure method could calculate how long an employee has been employed. Properties are a convenient way to expose data values to external objects and code. For example, the Name and Age fields could be exposed as properties, which would allow the Employee structure to control how the name and age are stored and retrieved.
Interfaces Interfaces are the only types that do not inherit from System.Object. Interfaces have no implementation; they merely provide a description of methods, properties, and events. Any class that inherits from an interface must implement all of the methods, properties, and events in that interface. Interfaces provide a mechanism for specifying contractual relationships between classes. Interfaces also provide a means of implementing multiple inheritance. For more information about inheritance, see Module 6, “Working with Types,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET).
Module 5: Common Type System
5
Value Types vs. Reference Types Topic Objective
To explain the difference between value types and reference types.
!
Lead-in
With the exception of interfaces, all types in the Common Type System are divided into two categories: value types and reference types.
!
Value Types Are Primitive or User-Defined Structures #
Allocated on stack
#
Assigned as copies
#
Default behavior is pass by value
Reference Types Are Objects That Are: #
Allocated on heap using the new keyword
#
Assigned as references
#
Passed by reference
*****************************ILLEGAL FOR NON-TRAINER USE****************************** With the exception of interfaces, all types in the Common Type System are divided into two categories: value types and reference types.
Value Types Value types represent primitive or user-defined structures. Value types are allocated on the stack and are removed from the stack when the method call in which they were declared returns. When a value type is passed as an argument to a method, it is passed by value, unless the ref keyword is used. All value types must inherit from System.ValueType.
6
Module 5: Common Type System
An integer is an example of a value type. The following example shows an integer value type and its effect on method calls and assignment operations. using System; public class MainClass { public static void Main() { // Create a new integer with value 5 int a = 5; // Create a new uninitialized integer int b; // Initialize b by copying the value 5 into b b = a; //Increase the value of b by 5 b += 5; Console.WriteLine("{0}, {1}", a, b); Add5(b); Console.WriteLine("{0}, {1}", a, b); } public static void Add5(int num) { num += 5; } }
This code generates the following output: 5, 10 5, 10
In the preceding example, the variables a and b represent two separate instances of the integer type. The Assignment operator (=) copies the value from one variable to another. When the Add5 method is called, the = operator updates a copy of the integer, not the original integer, because a copy of the value is pushed onto the stack when passing value types.
Module 5: Common Type System
Reference Types Reference types are a reference to a value. Reference types are allocated on the heap and will remain on the heap until they are garbage collected. Reference types must be allocated by using the new keyword. When you pass reference types as parameters, the reference is pushed onto the stack, and the method call works with the actual value, not with a copy of the value. All reference types inherit from System.Object. The following example shows the effect of assignments and method calls when you use the StringBuilder class, which is a reference type: using System; using System.Text; public class MainClass { public static void Main() { //Create a new StringBuild instance //with the string "Bob" StringBuilder name1 = new StringBuilder("Bob"); StringBuilder name2; //Make name2 reference the same StringBuilder instance name2 = name1; name2.Append("by"); Console.WriteLine("Values are {0}, {1}", name1, name2); Possessive(name2); Console.WriteLine("Values are {0}, {1}", name1, name2); } public static void Possessive(StringBuilder name) { name.Append("'s"); } }
This code generates the following output: Values are Bobby, Bobby Values are Bobby’s, Bobby’s
In this example, name1 and name2 always refer to the same StringBuilder instance on the heap. Thus, whenever a change is made to the underlying instance, both variables are affected. Also, name2 is passed by reference to the Possessive method, so that when the Possessive method makes a change to the instance, both name2 and name1 are affected.
7
8
Module 5: Common Type System
" Elements of the Common Type System Topic Objective
To provide an overview of the section topics and objectives.
Lead-in
In this section, you will learn about primitive types, objects, properties, and other elements of the Common Type System.
!
Primitive Types
!
Objects
!
Constructors
!
Properties
!
Custom Types
!
Enumerations
!
Interfaces
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this section, you will learn about primitive types, objects, properties, and other elements of the Common Type System.
Module 5: Common Type System
9
Primitive Types Topic Objective
To explain how primitive types are used in the common language runtime.
Lead-in
Primitive types in the .NET Framework common language runtime are simple value types, commonly found in most programming languages.
!
Simple Small Types Common in Most Languages
!
Naming Differences
!
C# Support
!
Conversions
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Primitive types in the .NET Framework common language runtime are simple value types, which are commonly found in most programming languages. Primitive types are simple small types, such as Boolean types or character types. The following table identifies all of the primitive types available in the .NET Framework common language runtime. The table lists the Microsoft intermediate language (MSIL) assembler name of the type, which you can see when you view the disassembly of managed code in the MSIL Disassembler, the name of the type in C#, the name of the type in the .NET Framework class library, and the description of the type. Name in MSIL Assembler
Name in C#
Name in class library
Description
bool
bool
System.Boolean
True/false value
char
char
System.Char
Unicode 16-bit char
float32
float
System.Single
IEEE 32-bit float
float64
double
System.Double
IEEE 64-bit float
int8
sbyte
System.SByte
Signed 8-bit integer
int16
short
System.Int16
Signed 16-bit integer
int32
int
System.Int32
Signed 32-bit integer
int64
long
System.Int64
Signed 64-bit integer
unsigned int8
byte
System.Byte
Unsigned 8-bit integer
unsigned int16
ushort
System.UInt16
Unsigned 16-bit integer
unsigned int32
uint
System.UInt32
Unsigned 32-bit integer
unsigned int64
ulong
System.UInt64
Unsigned 64-bit integer
10
Module 5: Common Type System
The following example shows how to create and initialize the primitive System.UInt16 type in C# to store an employee’s age: System.UInt16 age = new System.UInt16(); age = 5;
Primitive types are inherently supported in C#, allowing you to use simpler syntax, as in the following example: ushort age = 5;
You can convert primitive types by using the Convert class. The Convert class defines static methods to convert from one primitive type to another. The following example shows how to convert a long type to an unsigned short type: long longAge = 32; ushort ushortAge; ushortAge = Convert.ToUInt16(longAge);
C# provides a more convenient conversion mechanism: casting. The following example shows how to explicitly cast a long type to an unsigned short type: long longAge = 32; ushort ushortAge; ushortAge = (ushort) longAge;
Casting in C# will automatically compile to conversion methods, such as ToUInt16. Also, you can use casting to avoid memorizing the class library names for primitive types.
Module 5: Common Type System
11
Objects Topic Objective
To explain the role of object types in the Common Type System.
Lead-in
Everything in the Common Type System is an object and inherits from System.Object.
!
Every Class Inherits from System.Object
!
Objects Specify Data and Behavior
!
Fields Define the Data
!
Methods Define the Behavior class class Accumulator Accumulator {{ public public float float Result; Result; public public void void Add(float Add(float amount) amount) {{ Result Result += += amount; amount; }} }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Everything in the Common Type System is an object and inherits from System.Object. An object type declaration specifies data and behavior. The Common Type System defines data in terms of fields and defines behavior in terms of methods. Each field or method in an object is called a member of that object. An object is an instance of a class. The class specifies the types that are contained in a class and the class’s operations; an object is an instance containing a set of data values. In C#, you can use the class keyword to declare an object type that is a reference type. The following example shows how to declare a class called Accumulator. class Accumulator { // Fields and methods defined here }
Fields Fields describe the data in an object. In the preceding example of the Accumulator class, a field is needed to store the current result that has been accumulated. class Accumulator { public float Result; }
12
Module 5: Common Type System
Methods Methods describe the operations in an object. A method can have arguments and can also have a return type. Because the Common Type System requires strong typing, you must define a specific type for every parameter and return value. The following example shows how to specify an Add method for the Accumulator class that takes an amount of type float and adds it to the Result field. class Accumulator { public float Result; public void Add(float amount) { Result += amount; } }
The following code is an example of client code: Accumulator calc = new Accumulator(); calc.Result = 0; calc.Add(5); Console.WriteLine(calc.Result);
This code generates the following output: 5
Module 5: Common Type System
13
Constructors Topic Objective
To explain how to use constructors to initialize classes.
Lead-in
If you design a class that requires special initialization code, you can use a constructor to initialize the class properly when objects are created.
!
Constructors Are Used to Initialize Classes
!
Constructors Can Have Zero or More Parameters
class class Rectangle Rectangle {{ private private int int x1,y1,x2,y2; x1,y1,x2,y2; public public Rectangle(int Rectangle(int x1, x1, int int y1, y1, int int x2,int x2,int y2) y2) {{ this.x1 this.x1 == x1; x1; this.x2 this.x2 == x2; x2; this.y1 this.y1 == y1; y1; this.y2 this.y2 == y2; y2; }} }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** If you design a class that requires special initialization code, you can use a constructor to initialize the class properly when objects are created. A constructor is a method with the same name as the class. The constructor runs when a new instance of the class is created. The following example shows how a constructor is used to initialize a class: public class Rectangle { //Rectangle coordinates private int x1,y1,x2,y2; //Initialize coordinates in constructor public Rectangle() { x1 = 0; x2 = 0; y1 = 0; y2 = 0; } } public class MainClass { public static void Main() { //Creating a new Rectangle //will call constructor Rectangle r = new Rectangle(); } }
14
Module 5: Common Type System
Default Constructor If you do not specify a constructor, the C# compiler will automatically provide a default constructor. The default constructor takes no parameters and calls the base class constructor.
Parameterized Constructor You can also create a constructor with parameters as follows: class Rectangle { //Rectangle coordinates private int x1,y1,x2,y2; //Initialize coordinates in constructor public Rectangle(int x1, int y1, int x2,int y2) { this.x1 = x1; this.x2 = x2; this.y1 = y1; this.y2 = y2; } } public class MainClass { public static void Main() { //Must specify parameters //for this constructor Rectangle r = new Rectangle(2,2,10,10); } }
Module 5: Common Type System
15
Properties Topic Objective
To describe how to use properties.
!
Properties Are Similar to Fields
Lead-in
!
Properties Use get and set Accessor Methods for Managing Data Values
Properties are values that can be stored or retrieved on a class.
public public float float Start Start {{ get get {{ return return start; start; }} set set {{ if if (start (start >= >= 0) 0) start start == value; value; }} }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Properties are values that can be stored or retrieved on a class. Like fields and methods, properties are also considered members of an object. Properties are different than fields because access to properties is controlled through get and set accessor methods. Also, properties are not necessarily stored in a single variable. They may be calculated on the fly or stored in multiple variables.
get and set Accessors Property storage and property retrieval are defined by the get and set accessor methods. You write code in the get accessor method to determine how to retrieve a property value. You write code in the set accessor method to determine how to store a property value. By omitting the get or the set accessor, you can make a property read-only or write-only.
16
Module 5: Common Type System
The following example shows how to implement a Distance class, which can calculate the length of a trip from start to finish. The class contains three properties: Start, Finish, and Length. The Length property is read-only and is calculated on the fly. class Distance { float start, finish; public float Start { get { return start; } set { if (start >= 0) start = value; } } public float Finish { get { return finish; } set { if (finish >= start) finish = value; } } public float Length { get { return (finish - start); } } } public class MainClass { public static void Main() { Distance d = new Distance(); d.Start = 5; d.Finish = 10; float length = d.Length; } }
Module 5: Common Type System
17
In the preceding example, verification code is used in the set statements to control how the properties are set. Negative numbers are not allowed. Additional code could be written to throw an exception if invalid values are used. The client code in MainClass does not have to use method syntax with parentheses to refer to properties. In general, you should always use properties to expose data items to external classes. Properties allow you to encapsulate fields, so that you can change them in the future, if necessary. For more information about encapsulation, see Encapsulation in this module.
18
Module 5: Common Type System
Custom Types Topic Objective
To describe how to build data types that use the C# struct keyword.
Lead-in
You can build your own data type by using the C# struct keyword.
!
Inherit from System.ValueType
!
Are Defined with the struct Keyword in C#
!
Can Have Methods, Properties, and Fields
struct struct Employee Employee {{ public public string string Name; Name; public public ushort ushort Age; Age; public public DateTime DateTime HireDate; HireDate; public public float float Tenure() Tenure() {{ TimeSpan TimeSpan ts ts == DateTime.Now DateTime.Now –– HireDate; HireDate; return ts.Days/(float)365; return ts.Days/(float)365; }} }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** You can build your own data types by using the C# struct keyword. The struct keyword will define a custom type that inherits from System.ValueType, which in turn inherits from System.Object. All value types are sealed; additional classes cannot be derived from them. The following example shows how the C# struct keyword is used to build a simple custom type that describes an individual employee: struct Employee { public string Name; public ushort Age; public DateTime HireDate; }
In C#, structures can also have methods as in the following example: struct Employee { public string Name; public ushort Age; public DateTime HireDate; public float Tenure() { TimeSpan ts = DateTime.Now – HireDate; return ts.Days/(float)365; } }
Structures can also have properties. For example, the Employee structure could be modified so that employee age is a property. Then the set accessor could be used to verify that the age is a valid age.
Module 5: Common Type System
19
Enumerations Topic Objective
To describe how to create enumerations, which allow developers to specify and represent simple types.
!
Lead-in
It is common for a variable to take on only one of a small number of values.
!
.NET Framework Enumerations #
Inherit from System.Enum
#
Use the enum keyword
Bit Flags
enum enum SeatPreference SeatPreference :: {{ Window, Window, //Assigned //Assigned Center, Center, //Assigned //Assigned Aisle //Assigned Aisle //Assigned }}
ushort ushort value value value value value value
00 11 22
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In programming, it is common for a variable to take on only one of a small number of values. For example, the variable for an airline passenger’s seating preference has only three values: Window, Center, or Aisle. It is much easier and safer for a developer to reference such values with text, rather than numbers. The .NET Framework common language runtime supports enumerations, which allow a developer to specify and represent these simple types.
.NET Framework Enumerations In the .NET Framework, enumerations inherit from the class System.Enum, which inherits from System.ValueType. The following example shows how to create an enumeration: enum SeatPreference : ushort { Window, //Assigned value 0 Center, //Assigned value 1 Aisle //Assigned value 2 }
In the preceding example, the base type of the enumeration is defined as ushort. You can use any simple type for a base type, except System.Char. An enumeration will be of type int, unless otherwise specified. The literals are specified in a comma-delimited list. Unless you specify otherwise, the first literal is assigned a value of 0, the second a value of 1, and so on.
20
Module 5: Common Type System
To use enumeration literals, you must fully specify the literal name as in the following example: SeatPreference sp; //Assign enumerated window type seat to sp variable sp = SeatPreference.Window; Console.WriteLine(sp.GetHashCode()); Console.WriteLine(sp.ToString());
This code generates the following output: 0 Window
The GetHashCode method will return the literal value. By using the ToString method, you can obtain the human-readable string name of the literal from a variable. This feature is useful when you need to print or save a human-readable form of the enumeration’s literals. Also, you can use the Parse method to obtain an enumerated value from a string. This feature is useful when receiving input in a text form that requires conversion to the appropriate enumerated value. The following example shows how to convert the string “center” into an enumerated value for the SeatPreference enumeration. sp = (SeatPreference) System.Enum.Parse(typeof(SeatPreference), “Center”);
Module 5: Common Type System
21
Bit Flags Enumerations in the .NET Framework are also useful for storing bit flags that often involve bitwise operations, such as the bitwise AND operator (&), and the bitwise OR operator (|). Use the Flags attribute to create an enumeration of bit flags. The following example refers to a computer-controlled weather display that uses icons to represent weather conditions. The enumeration would allow you to combine the icons in various ways to represent different conditions: [Flags] enum WeatherDisplay : ushort { Sunny = 1, Cloudy = 2, Rain = 4, Snow = 8, Fog = 16 } WeatherDisplay wd; //Assign both Sunny and Cloudy attributes //to create partly sunny forecast wd = WeatherDisplay.Sunny | WeatherDisplay.Cloudy; Console.WriteLine(wd.ToString()); wd = (WeatherDisplay)System.Enum.Parse(typeof(WeatherDisplay), "Rain, Snow"); Console.WriteLine(wd.GetHashCode());
This code generates the following output: Sunny, Cloudy 12
When using the Flags attribute, you must specify the values for the literals manually.
22
Module 5: Common Type System
Interfaces Topic Objective
!
An Interface Is a Contractual Description of Methods and Properties
Lead-in
!
An Interface Has No Implementation
!
Use Casting in Client Code to Use an Interface
To describe the uses of interfaces. An interface is a contractual description of a set of related methods and properties.
interface interface ICDPlayer ICDPlayer {{ void void Play(short Play(short playTrackNum); playTrackNum); void void Pause(); Pause(); void void Skip(short Skip(short numTracks); numTracks); short short CurrentTrack CurrentTrack {{ get; get; set; set; }} }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** An interface is a contractual description of a set of related methods and properties. An interface has a name, and it has methods and properties. For example, an interface named ICDPlayer may have methods, such as Play, Pause, and Skip. It may also have properties, such as CurrentTrack and Time. The following example shows how to define an interface called ICDPlayer that has the methods Play, Pause, and Skip, and has the property CurrentTrack: interface ICDPlayer { void Play(short playTrackNum); void Pause(); void Skip(short numTracks); short CurrentTrack { get; set; } }
An interface has no implementation. Any class can inherit from any interface. To inherit from an interface, a class must implement all methods, properties, and events on that interface. Thus an interface serves as a contract specifying to any user of the class that the class has implemented all methods properties, and events defined in the interface.
Module 5: Common Type System
The following example shows how to implement the ICDPlayer interface in a class called Device. public class Device : ICDPlayer { // Internal property values protected string deviceName; protected short currentTrack; //Constructor public Device() { deviceName = "Default"; currentTrack = 1; } //Properties public string DeviceName { get { return deviceName; } set { deviceName = value; } } public short CurrentTrack { get { return currentTrack; } set { currentTrack = value; } } //Methods public void Play(short playTrackNum) { Console.WriteLine("Now Playing Track: {0}", playTrackNum); currentTrack = playTrackNum; } public void Pause() { Console.WriteLine("Now Paused"); } public void Skip(short numTracks) { Console.WriteLine("Skipped {0} Tracks",numTracks); } }
23
24
Module 5: Common Type System
In the preceding example, all of the methods and properties of ICDPlayer were implemented. A class may implement additional properties and methods, such as the DeviceName property in the Device class. Client code uses an interface by casting to the interface name. The following example shows how a client can create an instance of the Device class and then use the ICDPlayer interface: public class MainClass { public static void Main() { Device Device1 = new Device(); ICDPlayer CD1 = (ICDPlayer) Device1; //Call Play method on ICDPlayer interface CD1.Play(1); //Get CurrentTrack property of ICDPlayer interface Console.WriteLine("Current Track = {0}", CD1.CurrentTrack); //Get DeviceName property of Device object Console.WriteLine("Device Name = {0}", Device1.DeviceName); } }
In the preceding example, the Device1 variable also could have been used to access methods and properties of the ICDPlayer interface. For more information about how to separate interface methods and properties from class methods and properties, see Module 6, “Working with Types,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET).
Module 5: Common Type System
25
" Object-Oriented Characteristics Topic Objective
To provide an overview of the topics covered in this section.
Lead-in
In this section, you will learn how the object-oriented characteristics of the .NET Framework common language runtime are supported.
!
Abstraction
!
Encapsulation
!
Inheritance
!
Polymorphism
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this section, you will learn how the object-oriented characteristics of the .NET Framework common language runtime are supported. Everything in the common language runtime is an object that encompasses fields, methods, properties, and events. Object-oriented characteristics, such as abstraction, encapsulation, inheritance, and polymorphism, make it easier to work with code in the .NET Framework.
26
Module 5: Common Type System
Abstraction Topic Objective
To describe the use and advantages of abstraction in object-oriented programming.
!
Abstraction Works from the Specific to the General
!
Grouping Elements Makes It Easier to Work with Complex Data Types
!
Abstraction Is Supported Through Classes
Lead-in
In object-oriented programming, the term abstraction refers to the process of reducing an object to its essence so that only essential elements are represented.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In object-oriented programming, the term abstraction refers to the process of reducing an object to its essence so that only essential elements are represented. For programmers, abstraction means working from the specific to the general. An example of abstraction would be a phone number, such as (206) 555-1212. At the specific level, this phone number is a sequence of numbers: 2065551212. However, most people do not memorize phone numbers at this specific level. Instead they memorize such information by grouping the numbers into meaningful chunks. In this example, the phone number becomes an area code (206), prefix (555), and number (1212). This abstraction makes it easier for a person to remember the number. The number is reduced to three chunks to memorize, instead of 10 individual numbers. Abstraction is a powerful process that allows people to group and communicate information in simpler and more meaningful ways. In the Common Type System and other object-oriented systems, the fundamental unit of abstraction is the class. Class types allow you to group different types of information, and they provide the functionality to help you operate on that information. From a design perspective, abstraction allows you to treat information and functionality as complete functional units, rather than as disparate pieces of information and operations.
Module 5: Common Type System
27
Encapsulation Topic Objective
To explain the process of encapsulation in objectoriented programming.
!
Encapsulation Is the Process of Hiding Internal Details of a Class
Lead-in
!
Encapsulation Keywords
Encapsulation, or information hiding, is the process of packaging attributes and functionality to prevent other objects from manipulating data or procedures directly.
#
public
#
protected
#
internal
#
private
!
Type-Level Accessibility
!
Nested Classes
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In object-oriented programming, encapsulation, or information hiding, is the process of packaging attributes and functionality to prevent other objects from manipulating data or procedures directly. Encapsulation also allows the object that is requesting service to ignore the details of how the service is provided. By hiding the internal unnecessary data and functionality of a class from other classes, encapsulation provides greater flexibility in the design of classes. For example, when a sorting class performs sorts on data items, the internal data structure of the class will probably store the data items, such as a linked list. The class will also have internal functions that operate on the linked list. If clients that use the sorting class have direct access to the linked list, they will incorporate code that relies on the linked list. In fact, if no accessor functions are written for the linked list, the clients must use the linked list to store or retrieve data items. If the sorting class changes in the future, it may change the linked list to an array for optimization, and the clients will stop functioning correctly. At that time, the clients would require rewriting to use the array syntax. If the sorting class hid the internals of its implementation and provided accessor functions that mapped to the internal data structure, this problem could be avoided. C# provides a set of access modifiers that vary the degree of accessibility to members of any type. The following table lists all of the access modifiers supported in C#. Access modifier
Description
public
Makes a member available to all other classes in all assemblies
protected
Makes a member available only to classes that inherit from this class
internal
Makes a member available only to other classes in the same assembly
private
Makes a member available only to the same class
28
Module 5: Common Type System
If no access modifier is specified, a default modifier is applied. The following table shows which modifiers are applied to different types by default. Members of
Default member accessibility
enum
public
class
private
interface
public
struct
private
Type-Level Accessibility You can specify accessibility at the type level and at the member level. Access modifiers can be applied to enumerations, classes, interfaces, and structs. However, for top-level types, internal and public are the only allowed modifiers. You cannot create a private class or a protected interface at the top level in a namespace. The access modifier applied at the type level will supercede member-level access modifiers if the type-level access modifier is more restrictive. For example, an internal class can have a public method, but the internal modifier will make the public method internal as well. The following example shows the effect of applying access modifiers at the class level and the member level: public class COuter1 { public static short MyPublicShort = 0; internal static short MyInternalShort = 0; private static short MyPrivateShort = 0; } public class MainClass { public static void Main() { COuter1.MyPublicShort = 1; //Success because publicly available COuter1.MyInternalShort = 2; //Success because in same assembly COuter1.MyPrivateShort = 3; //Failure because private only available to COuter1 class } }
Module 5: Common Type System
Nested Classes When classes are nested, the nested classes cannot exceed the accessibility of the containing class. For example, if a nested class is marked as public, but the containing class is marked as internal, the nested class must also be internal. The following example shows the effect of access modifiers on nested classes: internal class COuter1 { public class CInner1 { public static short MyPublicShort = 0; internal static short MyInternalShort = 0; private static short MyPrivateShort = 0; } internal class CInner2 { public static short MyPublicShort = 0; internal static short MyInternalShort = 0; private static short MyPrivateShort = 0; } private class CInner3 { public static short MyPublicShort = 0; internal static short MyInternalShort = 0; private static short MyPrivateShort = 0; } } public class MainClass { public static void Main() { COuter1.CInner1.MyPublicShort = 1; //Success because internal at class level //and in same assembly COuter1.CInner2.MyPublicShort = 2; //Success because internal and in same assembly COuter1.CInner3.MyPublicShort = 3; //Failure because class is private, and all members are private } }
29
30
Module 5: Common Type System
Inheritance Topic Objective
To explain how inheritance works in the Common Type System.
!
Inheritance Is the Reuse of Class Members in Other Classes
Lead-in
!
The Common Type System Only Supports Single Inheritance for Classes
!
Member Hiding
Inheritance is a method of reuse that enables one class to reuse, or inherit, the fields, properties, and methods of another class.
#
Redefine the same method in the derived class
#
Use the new keyword
!
Abstract Members
!
Sealed Classes
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In object-oriented programming, inheritance is a method of reuse that enables one class to reuse, or inherit, the fields, properties, and methods of another class. Inheritance represents an “is a” relationship between classes. For example, if a square class inherits from a shape class, the square “is a” kind of shape. Inheritance is useful when multiple classes share the same methods or data. The common methods and data can be abstracted into a separate class that the other classes inherit from. Inheritance is pervasive in the Common Type System. Every class must inherit from System.Object, so all classes will have the members of System.Object.
Single Inheritance The Common Type System only supports single inheritance of class types. Single inheritance means a class can inherit directly from only one other class. Some object-oriented systems, such as C++, allow multiple inheritance, which means that one class can inherit from many other classes. The Common Type System does support multiple inheritance through interfaces. In that case, one class can inherit from one other class and from zero or more interfaces. For more information about multiple inheritance through interfaces, see Module 6, “Working with Types,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET). In the simplest form of inheritance, a class inherits its members from the base class and gains all the functionality of the base class, and at the same time it adds additional functionality of its own.
Module 5: Common Type System
In the following example, three classes are defined. The base class is called Animal; it implements an Age property and a Move method. The second class is called Dog; it inherits from Animal and implements an additional method called Bark. The third class is called Cat; it also inherits from Animal and implements an additional method called Meow. public class Animal { protected short age = 0; public short Age { get { return age; } set { if (value > 0) age = value; } } public void Move() { Console.WriteLine("Animal is Moving"); } } public class Dog : Animal { public void Bark() { Console.WriteLine("Bark!"); } } public class Cat : Animal { public void Meow() { Console.WriteLine("Meow!"); } } public class MainClass { public static void Main() { Dog d = new Dog(); Cat c = new Cat(); d.Age = 3; d.Move(); d.Bark(); c.Age = 2; c.Move(); c.Meow(); } }
31
32
Module 5: Common Type System
This code generates the following output: Animal is Moving Bark! Animal is Moving Meow!
In the Main method, a Dog object and Cat object are created. Methods and properties of the Animal class are called on the Dog object and the Cat object. In this way, the Animal class functionality for storing the age of an animal and implementing movement in an animal can be written once and reused in multiple classes.
Member Hiding Sometimes you will want a class to inherit from a base class, but you will want to modify some of the base class’s functionality. For example, you may want a new kind of animal class called Slug to inherit from the Animal class, but you will want to use the Move method to make the slug move slowly. You can accomplish this effect by creating a method with the same name and parameter list as the base class method. The following example shows how you can customize the Move method for slugs by making the Slug class replace the Move method of the Animal class: public class Slug : Animal { public new void Move() { Console.WriteLine("Moving very slowly"); } } public class MainClass { public static void Main() { Slug s = new Slug(); s.Move(); } }
This code generates the following output: Moving very slowly
In the preceding example, the new keyword is used to signal that a method was replaced. In this context, the new keyword denotes that a method was hidden. This use of the new keyword should not be confused with the use of the new keyword to allocate a new object. While the new keyword is not required for method hiding, it enhances readability. The C# compiler will issue a warning if the new keyword is not used on replaced methods.
Module 5: Common Type System
33
Abstract Members Methods, fields, and properties can also be abstract. A method, field, or property is abstract when the base class does not implement the member, and the derived class must implement the member. Abstract members are marked with the abstract keyword. When derived classes implement abstract methods, they must use the override keyword to indicate that they are overriding the base-class functionality. The following example shows an abstract Shape class that has one abstract method called Draw. The Shape class implements a Move method, which, when called, will call the derived class Draw method. Thus the Shape class has information about how to move but does not have information about how to draw after it moves. The abstract method forces the derived class to implement the Draw method so that the Shape class can move properly. using System; abstract class Shape { protected int x, y; //Derived class must implement next method public abstract void Draw(); public void Move(int x, int y) { this.x = x; this.y = y; //Call derived class implementation Draw(); } } class Square : Shape { public override void Draw() { Console.WriteLine("Drawing a Square at {0},{1}",x,y); } } class MainClass { public static void Main() { Square s = new Square(); //Call base class Move method, which in turn calls //derived class Draw method s.Move(1,1); } }
34
Module 5: Common Type System
This code generates the following output: Drawing a Square at 1,1
If a class contains any abstract members, the entire class becomes an abstract class. As a result, an instance of the abstract class cannot be created because it is missing functionality for a method. For this reason, the Shape class in the preceding example was marked as abstract.
Sealed Classes You can prevent other classes from deriving from a specific class by sealing that class. Use the sealed modifier to make a sealed class. sealed class SealedClass { public static void Foo() { } }
The sealed modifier is primarily used to prevent unintended derivation, but it also enables certain run time optimizations. In particular, because a sealed class is known to never have any derived classes, it is possible to transform virtual function member invocations on sealed class instances into non-virtual invocations.
Module 5: Common Type System
35
Polymorphism Topic Objective
To explain the use of polymorphism to define a base class.
!
Polymorphism Allows a Reference Variable to Call the Correct Method
!
Virtual Methods Enable Polymorphism in the Common Type System
Lead-in
In object-oriented programming, polymorphism allows you to define a base class that includes routines that perform standard operations on groups of related objects, without regard to the exact type of each object.
!
#
Use the virtual keyword in the base class
#
Use the override keyword in the derived class
Sealed Methods
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Polymorphism derives from the Greek for “many forms.” In object-oriented programming, polymorphism allows you to define a base class that includes routines that perform standard operations on groups of related objects, without regard to the exact type of each object. For example, in the preceding animal scenario, you could add a method called MakeNoise. When this method is called, Animal objects should print “Animal is making noise.” A dog object should print “Bark!”, and a cat object should print “Meow!” In the following example, the code does not work as expected; it prints “Animal is making noise,” instead of the desired specific animal noise, such as “Bark” or “Meow”: public abstract class Animal { //..Other properties and methods public void MakeNoise() { Console.WriteLine("Animal is making noise"); } } public class Dog : Animal { //..Other properties and methods public new void MakeNoise() { Console.WriteLine("Bark!"); } }
(Code continued on the following page.)
36
Module 5: Common Type System public class Cat : Animal { //..Other properties and methods public new void MakeNoise() { Console.WriteLine("Meow!"); } } public class MainClass { public static void Main() { Dog d = new Dog(); Cat c = new Cat(); d.Age = 3; c.Age = 2; WorkWithAnimal(d); WorkWithAnimal(c); } public static void WorkWithAnimal(Animal a) { Console.WriteLine("Working with animal age {0}",a.Age); a.MakeNoise(); } }
This code generates the following output: Working with animal age 3 Animal is making noise Working with animal age 2 Animal is making noise
In the preceding example, because the reference in the WorkWithAnimal method is type Animal, the Animal base class MakeNoise always gets called. The desired behavior would be a dog object making a noise like a dog, not an animal.
Module 5: Common Type System
To resolve this problem, use the virtual keyword to mark a method as virtual. Using the virtual keyword will ensure that the correct method call gets called on the basis of the object type, not the reference type. The derived class must then use the override keyword on the same method, as in the following example: public abstract class Animal { //..Other properties and methods public virtual void MakeNoise() { Console.WriteLine("Animal is making noise"); } } public class Dog : Animal { //..Other properties and methods public override void MakeNoise() { Console.WriteLine("Bark!"); } } public class Cat : Animal { //..Other properties and methods public override void MakeNoise() { Console.WriteLine("Meow!"); } } public class MainClass { public static void Main() { Dog d = new Dog(); Cat c = new Cat(); d.Age = 3; c.Age = 2; WorkWithAnimal(d); WorkWithAnimal(c); } public static void WorkWithAnimal(Animal a) { Console.WriteLine("Working with animal age {0}",a.Age); a.MakeNoise(); } }
37
38
Module 5: Common Type System
This code generates the following output: Working with animal age 3 Bark! Working with animal age 2 Meow!
Sealed Methods You can use the sealed modifier to prevent derived classes from overriding specific methods. Methods marked with the sealed modifier are called sealed methods. The sealed modifier can only be used in conjunction with the override modifier. class A { public virtual void Foo() {/*...*/ } } class B : A { //Any class derived from B will //not be able to override Foo public sealed override void Foo() {/*..*/ } }
Module 5: Common Type System
39
Lab 5: Building Simple Types Topic Objective
To introduce the lab.
Lead-in
In this lab, you will create simple types and enumerations, and work with inheritance.
*****************************ILLEGAL FOR NON-TRAINER USE******************************
Objectives After completing this lab, you will be able to: !
Create enumerations.
!
Create custom value types as structures.
!
Inherit from a base class and override properties and methods.
!
Create constructors.
!
Use appropriate levels of encapsulation through the private and public access modifiers.
Prerequisites Before working on this lab, you must have: !
Knowledge about how to use Microsoft Visual Studio® .NET for creating and working with console applications.
!
Knowledge about the C# language.
!
Knowledge about the System.Console namespace for interacting with the console.
Estimated time to complete this lab: 45 minutes
40
Module 5: Common Type System
Exercise 1 Creating an Enumeration In this exercise, you will create a simple program that uses an enumeration to reference weekdays. You will enumerate the weekdays, Monday through Friday, as a type. Then you will write code to accept a day from the user, match the day to the enumerated type, and write a response back to the user.
! Create an enumeration 1. Open the Days project located in the folder \Labs\Lab05\ Starter\Days. 2. Open the Main.cs file. 3. Create an enumeration called Weekdays. The enumeration should be of type int and should enumerate the literals Monday, Tuesday, Wednesday, Thursday, and Friday.
! Use an enumeration 1. Locate the Main method of the MainClass class. 2. In the Main method, write code to perform the following tasks: a. Write a question to the console that reads, “What day of the week is it?” b. Use the Console.ReadLine method to get the user’s response. c. Determine which day the user selected by parsing the Weekdays enumeration. Store the correct Weekdays literal in a variable of type Weekdays. d. Use a switch statement on the mapped response to determine which day of the week it is. Based on the response, write a string to the user. For example, for Monday, write “Monday is the first weekday.” For Tuesday, write “Tuesday is the second weekday,” and so on. 3. Compile and test the code. Run the program and type in a weekday. You should get an appropriate response based on the day you entered.
Module 5: Common Type System
41
Exercise 2 Creating a Custom Value Type In this exercise, you will create a custom value type or structure to represent a complex number. Complex numbers have a real part and an imaginary part. You will design the structure to store the real and imaginary parts, and you will perform addition on complex numbers.
! Create a structure 1. Open the Complex project located in the folder \Labs\ Lab05\Starter\Complex. 2. Open the Main.cs file. 3. Create a new structure called Complex. a. Create a public member variable called Real of type int. b. Create a public member variable called Imaginary of type int. 4. Create an overloaded Addition operator (+) for the Complex structure. The overloaded operator should look as follows: public static Complex operator +(Complex c1, Complex c2) { }
a. In the Addition operator method, create a variable called result of type Complex to hold the result of the addition. b. Add c1.Real to c2.Real and store the result in result.Real. c. Add c1.Imaginary to c2.Imaginary and store the result in result.Imaginary. d. Return the result.
! Use the structure 1. Locate the Main method in the MainClass class. 2. In the Main method, write code to create a variable called num1 of type Complex. 3. Create a second variable called num2 of type Complex. 4. Initialize num1 and num2 with real and imaginary parts. For example, num1 could have values of 2 and 3 for the real and imaginary parts. num2 could have values of 3 and 4 for the real and imaginary parts. 5. Add the two numbers together, and print both the real and imaginary parts. 6. Compile the program and run it. Verify that the result is correct. Using the values in step 4, you should see the results of 5 and 7 for the real and imaginary parts.
42
Module 5: Common Type System
Exercise 3 Creating Objects In this exercise, you will derive new classes from base classes and then use the derived classes. You will work with a base class called Polygon. The class represents the geometric characteristics of polygons. Polygons have a number of sides. Thus the Polygon class defines a NumSides property but does not implement it. The derived classes implement the NumSides property. For example, a Triangle class could be created that overrides the NumSides property and initializes it to 3. A Pentagon class could be created that overrides the NumSides property and initializes it to 5. The Polygon class also implements a Degrees property that returns the sum of all the angles in the polygon. For example, the angles in a triangle always add up to 180 degrees. This simple calculation is based on the number of sides in the polygon, and so the derived class must implement the NumSides property. The Polygon class also defines an abstract method called Area that will return the area of the polygon. The derived class must override this method to calculate the correct area.
! Derive a new class 1. Open the Shapes project located in the folder \Labs\Lab05\ Starter\Shapes. 2. Open the Polygon.cs file and study the code for the Polygon class. Notice that it is an abstract class that has an abstract property called NumSides, a property called Degrees, and an abstract method called Area that returns the area inside the polygon. 3. Open the Main.cs file. 4. Create a new class called Square that inherits from Polygon. a. Create private member variables called x1, y1, and size of type int to store the Cartesian coordinates of the upper-left corner and the size of the square, which is the length of one side. b. Create a private member variable called numSides of type ushort to store the number of sides that the square has. c. Create a constructor that accepts three integer parameters (x1, y1, size) to construct a new square. Initialize the private member variables to the values passed as parameters. Initialize numSides to 4. d. Override the NumSides property in the base class to return the numSides variable. e. Create read/write properties called X1, and Y1 respectively that map to the private variables x1, and y1. f. Create two read-only properties called X2 and Y2. These represent the lower-right coordinate of the square and are calculated by adding size to x1 or y1 respectively. g. Override the Area method to return the area of the square. The area can be calculated with the following formula: (size)2.
Module 5: Common Type System
43
! Use the derived class 1. Locate the Main method in the MainClass class. 2. In the Main method, write code to create a new Square object. Initialize the square with values for the upper-left corner and size, such as 1, 1, and 4. 3. Write the number of degrees in the square to the console by printing the Degrees property. 4. Write the area to the console by calling the Area method. 5. Write the x1, y1, x2, y2 coordinates to the console by using the properties of the square object. 6. Compile the program and test it. Verify that all the methods and properties work as expected. Given a square with upper-left coordinates of 1, 1, and a size of 4, the number of degrees is 360, the area is 16, and the lower-right coordinates are 5, 5.
44
Module 5: Common Type System
Review Topic Objective
To reinforce module objectives by reviewing key points.
Lead-in
The review questions cover some of the key concepts taught in the module.
!
An Introduction to the Common Type System
!
Elements of the Common Type System
!
Object-Oriented Characteristics
*****************************ILLEGAL FOR NON-TRAINER USE****************************** 1. What are the differences between value types and reference types? Value types are allocated on the stack, assigned as copies, and passed by value. Reference types are allocated on the heap, assigned as references, and passed by reference.
2. What are the differences between fields and properties? A field is a data value in a class that can be directly accessed and manipulated by other classes. A property is a value in a class that is accessed through get and set accessor methods. The actual data value of a property may be stored in the class instance, or calculated when accessed.
3. How do you create an enumeration in C#? Use the enum keyword: enum name : type { …literals… }
Module 5: Common Type System
45
4. What is an interface? An interface is a contractual description of a set of related methods and properties.
5. How is encapsulation supported by the .NET Framework? Encapsulation is supported through access modifiers, such as public, protected, internal, and private.
THIS PAGE INTENTIONALLY LEFT BLANK
Module 6: Working with Types Contents Overview System.Object Class Functionality Specialized Constructors
1 2 12
Type Operations
18
Interfaces
28
Managing External Types
34
Lab 6: Working with Types
38
Review
43
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, place or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. 2001-2002 Microsoft Corporation. All rights reserved. Microsoft, ActiveX, BizTalk, IntelliMirror, Jscript, MSDN, MS-DOS, MSN, PowerPoint, Visual Basic, Visual C++, Visual C#, Visual Studio, Win32, Windows, Windows Media, and Window NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
Module 6: Working with Types
iii
Instructor Notes Presentation: 75 Minutes Lab: 45 Minutes
After completing this module, students will be able to: !
Apply attributes to control visibility and inheritance in classes and interfaces.
!
Create and use interfaces that define methods and properties.
!
Explain how boxing and unboxing works and when it occurs.
!
Use operators to determine types at run time and cast values to different types.
!
Explain what features are available to work with unmanaged types, such as COM types.
Materials and Preparation This section provides the materials and preparation tasks that you need to teach this module.
Required Materials To teach this module, you need the Microsoft® PowerPoint® file 2349B_06.ppt.
Preparation Tasks To prepare for this module, you should: !
Read all of the materials for this module.
!
Complete the lab.
iv
Module 6: Working with Types
Module Strategy Use the following strategy to present this module: !
System.Object Class Functionality In this section, discuss how System.Object provides classes to generate hash functions, represent strings, and compare objects for identity and equality. Explain that this section does not cover all of the classes in System.Object, and that other classes are covered elsewhere in the course. For example, finalization and the Finalize method are covered in detail in Module 9, “Memory and Resource Management,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C#™ .NET).
!
Specialized Constructors This section covers more advanced types of constructors. Explain how static constructors work, when to use them, and when to use private constructors. If the students in your class already have experience in C++, you may not need to spend much time on these topics.
!
Type Operations The Microsoft .NET Framework common language runtime supports a variety of type operations for working with types. Discuss conversions and conversion operators for determining and converting the type of an object. Also cover how to cast types for conversion and for treating a type as a different type. For experienced C++ programmers, you may be able to cover type conversion and casting quickly. C++ programmers may find it useful that the as operator in C# is similar to dynamic_cast in C++. Spend most of this section discussing boxing and unboxing. Students will need to be aware of the performance consequences of boxing. Explain how you can avoid or minimize these consequences if you must use boxing.
!
Interfaces Discuss how multiple inheritance works through interfaces and explain how to explicitly implement interfaces. As with the other topics in this module, you may be able to cover this section quickly if your audience is already familiar with object-oriented programming techniques. For experienced C++ programmers, consider mentioning that explicit interface implementation was not possible in C++.
!
Managing External Types Briefly introduce Platform Invocation Services and COM interoperability. Be aware that more information about these topics is available in Module 15, “Interoperating Between Managed and Unmanaged Code,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET) and “Interoperating with Unmanaged Code” in the .NET Framework SDK documentation.
Module 6: Working with Types
1
Overview Topic Objective
To provide an overview of the module topics and objectives.
Lead-in
In this module, you will learn how to apply your knowledge of the Common Type System to various programming scenarios.
!
System.Object Class Functionality
!
Specialized Constructors
!
Type Operations
!
Interfaces
!
Managing External Types
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this module, you will learn how to apply your knowledge of the Common Type System to various programming scenarios. This module will help you understand how to use types efficiently when developing Microsoft® .NET Framework applications. You should understand that many nuances in the type system can affect program clarity and performance if ignored. This module covers the use of attributes to control visibility and inheritance on types and explains how to work with various type operations, such as boxing and unboxing, and type operators. The module then explores how to work with types programmatically by using operators to coerce, cast, or discover types at run time. In addition, this module discusses how to build an interface that supports methods and properties and how to make interface designs more efficient. The module also highlights features that are designed to help you work with unmanaged types, such as COM types. After completing this module, you will be able to: !
Apply attributes to control visibility and inheritance in classes and interfaces.
!
Create and use interfaces that define methods and properties.
!
Explain how boxing and unboxing works and when it occurs.
!
Use operators to determine types at run time and cast values to different types.
!
Explain what features are available to work with unmanaged types, such as COM types.
2
Module 6: Working with Types
" System.Object Class Functionality Topic Objective
To provide an overview of the topics covered in this section.
Lead-in
In this section, you will learn about the common methods that you need to override on the System.Object class.
!
Hash Codes
!
Identity
!
Equality
!
String Representation
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this section, you will learn about the common methods that you need to override on the System.Object class. This section does not cover finalization and the Finalize method, which are covered in detail in Module 9, “Memory and Resource Management,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C#™ .NET). Also, this section does not cover the MemberwiseClone method.
Module 6: Working with Types
3
Hash Codes Topic Objective
To explain how to use hash codes to perform quick lookups in tables and other types of collections.
Lead-in
A hash function is used to quickly generate a number, or hash code, that corresponds to the value of an object.
!
Hash Code Used to Perform Quick Lookups
!
Override GetHashCode Method on System.Object
!
Should Return Same Hash Code for Objects of Same Value
!
Should Implement Efficient Algorithm
struct struct Student Student {{ string string name; name; int int ID; ID; //Unique //Unique for for each each instance instance public public override override int int GetHashCode() GetHashCode() {{ return return ID; ID; }} }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** A hash function is used to quickly generate a number, or hash code, that corresponds to the value of an object. Hash codes are useful for performing quick lookups in tables, such as the HashTable class, and other kinds of collections. A hash table uses the hash code to drastically limit the number of objects that must be searched to find a specific object in a collection of objects. The hash table does this by getting the hash value of the object and eliminating all objects with a different hash code. This preliminary search leaves only those objects with the same hash code to be searched. Because there are few instances with that hash code, searches are much quicker. System.Object provides a GetHashCode method, which returns an int type. You should override this method to return a hash code on any custom classes or structures that you create. One reason for overriding this method is that when two objects are equal in value, you should get the same hash code for each object if you call GetHashCode. In the case of custom objects, the default implementation of GetHashCode does not give you the same hash code for two objects that are equal in value.
4
Module 6: Working with Types
A good hash code algorithm will support the best performance by generating a random distribution for all input. You should base your hash code algorithm on one of the unique fields in the class. Also, you should never throw an exception from the GetHashCode method because GetHashCode can be called frequently and should always work reliably. The following example shows how to implement GetHashCode for a Student structure that stores a student’s name and ID. struct Student { string name; int ID; //Unique for each instance public override int GetHashCode() { return ID; } }
Module 6: Working with Types
5
Identity Topic Objective
To explain how identity is determined in the .NET Framework common language runtime.
!
Compare to Determine If Two References Are Actually the Same Object
Lead-in
!
Use the Object.ReferenceEquals Method to Test Identity
There are two kinds of comparison for objects: identity and equality. This topic covers object identity.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** There are two kinds of comparison for objects: identity and equality. This topic covers object identity.
Determining Identity Two objects are identical if they are, in fact, the same object. Every object in the .NET Framework common language runtime has an identity that makes it unique in the system. In C++, an object’s identity is determined by its address. Thus, if two pointers are compared and contain the same address, they point to the same object. In COM, an object’s identity is determined by the IUnknown interface. Thus, if two IUnknown interface pointers are compared and contain the same address, they are the same COM object. In the .NET Framework common language runtime, you can use the Object.ReferenceEquals method to compare for identity. Internally, ReferenceEquals compares the addresses of the objects in memory to determine if they are the same object. If they are the same object, ReferenceEquals returns true.
6
Module 6: Working with Types
Using the Object.ReferenceEquals Method In the following example, a value type variable called x is created and passed in two parameters to the Test method. The Test method compares the two parameters to determine if they are identical. class MyObject { public int X; } class MainClass { public static void Main() { MyObject obj1 = new MyObject(); obj1.X = 5; Test(obj1, obj1); MyObject obj2 = new MyObject(); obj2.X = 5; Test(obj1, obj2); } public static void Test(MyObject a, MyObject b) { if (Object.ReferenceEquals(a,b)) Console.WriteLine("Identical"); else Console.WriteLine("Not Identical"); } }
This code generates the following output: Identical Not Identical
Module 6: Working with Types
7
Equality Topic Objective
To explain how to override the Equals method and to introduce guidelines for implementing code to provide equality comparison for types.
Lead-in
Objects can also be compared for equality.
!
Comparing Two Objects to Determine If They Are Equal
!
Override the Equals Method
!
Supply == and != Operators
!
Guidelines #
If overriding Equals, override GetHashCode
#
If overloading ==, override Equals to use same algorithm
#
If implementing IComparable, implement Equals
#
Equals, GetHashCode, and == operator should never throw exceptions
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Objects can also be compared for equality. The Object.Equals method, equality operator (==), or inequality operator (!=) are used to test equality.
Equals Method The Equals method is part of the Object class. You should override this method in your classes and structures to perform appropriate behavior when comparing objects of certain types. The default Object.Equals method calls Object.ReferenceEquals, which results in an identity comparison instead of a value comparison. In general, you should also override the == and != operators to allow easier syntax to compare objects.
8
Module 6: Working with Types
The following example shows how to override the Equals method and the == and != operators to test user-defined Rectangle objects for equality. class Rectangle { //Rectangle coordinates public int x1,y1,x2,y2; public Rectangle(int x1, int y1, int x2, int y2) { this.x1 = x1; this.x2 = x2; this.y1 = y1; this.y2 = y2; } public override int GetHashCode() { return x1; } public override bool Equals (Object obj) { //Check for null and compare run-time types. if (obj == null || GetType() != obj.GetType()) return false; Rectangle r = (Rectangle)obj; return (x1 == r.x1) && (y1 == r.y1) && (x2 == r.x2) && (y2 == r.y2); } static public bool operator == (Rectangle r1, Rectangle r2) { //Check for null parameters //Cast to object to avoid recursive call if ((object)r1 == null) return false; //Let Equals method handle comparison return r1.Equals(r2); } static public bool operator != (Rectangle r1, Rectangle r2) { //Check for null parameters //Cast to object to avoid recursive call if ((object)r1 == null) return true; //Let Equals method handle comparison return !r1.Equals(r2); } } class MainClass { public static void Main() { Rectangle r1 = new Rectangle(5,5,50,55); Rectangle r2 = new Rectangle(5,5,50,55); Console.WriteLine(r1.Equals(r2)); Console.WriteLine(r1 == r2); Console.WriteLine(null == r1); } }
Module 6: Working with Types
9
This code generates the following output: True True False
Guidelines for Equality Comparison Use the following guidelines when implementing code to provide equality comparison for types. !
Anytime you override the Equals method, also override the GetHashCode method. If two objects are equal, they must return the same hash code. The default implementation of GetHashCode does not return the same value.
!
Anytime you overload the == operator, also override the Equals method and the != operator to use the same algorithm. This technique allows infrastructure code, such as HashTable and ArrayList classes, that uses the Equals method, to behave in the same manner as user code that is written with the == operator.
!
Anytime you implement the IComparable interface, also implement the Equals method, and ensure that both elements use the same algorithm for comparisons. You should also consider overloading the comparison operators because any client code that uses the IComparable interface is also likely to use these operators.
!
The Equals method, GetHashCode method, and comparison operators should never throw an exception. Exceptions in comparison operators can cause confusion because most programmers do not anticipate these types of exceptions. Also, these methods are called frequently and need to be efficient and clean to avoid writing additional error-handling code for what are considered simplistic methods.
10
Module 6: Working with Types
String Representation Topic Objective
To explain how the ToString method is used in the .NET Framework common language runtime.
Lead-in
All objects have the ability to represent themselves in a string form.
!
Override ToString to Customize String Form of a Class
struct struct President President {{ public public string string FirstName; FirstName; public string public string LastName; LastName; public public override override string string ToString() ToString() {{
}}
}}
!
return return FirstName FirstName ++ "" "" ++ LastName; LastName;
Use IFormattable Interface and ToString Method for Localized Strings
*****************************ILLEGAL FOR NON-TRAINER USE****************************** One characteristic of all objects is the ability to represent themselves in a string form. The ToString method provides this ability for all objects. Methods in the Microsoft .NET Framework common language runtime, such as Console.WriteLine, frequently use ToString. The ToString method also is useful for debugging. The default behavior of the Object.ToString method is to return the name of the class. The following example shows what happens when ToString is called on a President structure. struct President { public string FirstName; public string LastName; } class MainClass { public static void Main() { President firstPres; firstPres.FirstName = "George"; firstPres.LastName = "Washington"; Console.WriteLine(firstPres.ToString()); } }
This code generates the following output: President
Module 6: Working with Types
You can override the ToString method to provide custom behavior, as shown in the following example: struct President { public string FirstName; public string LastName; public override string ToString() { return FirstName + " " + LastName; } } class MainClass { public static void Main() { President firstPres; firstPres.FirstName = "George"; firstPres.LastName = "Washington"; Console.WriteLine(firstPres.ToString()); } }
This code generates the following output: George Washington
If an object needs to be represented in localized string forms, you should not override Object.ToString. Instead, you should implement the IFormattable interface and its ToString method. The IFormattable interface can take into account different locales.
11
12
Module 6: Working with Types
" Specialized Constructors Topic Objective
To provide an overview of the topics covered in this section.
Lead-in
!
Static Constructors
!
Private Constructors
This section covers more advanced types of constructors. It explains how static constructors work, when to use them, and when to use private constructors.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This section covers more advanced types of constructors. It explains how static constructors work, when to use them, and when to use private constructors.
Module 6: Working with Types
13
Static Constructors Topic Objective
To explain how static constructors work.
Lead-in
Static constructors are used to initialize static fields.
!
Used to Initialize Static Members
class class DeviceConnection DeviceConnection {{ public public static static uint uint ConnectionCount; ConnectionCount; public void OpenConnection(string public void OpenConnection(string connectionName) connectionName) {{ ConnectionCount++; ConnectionCount++; //Other //Other work work to to open open device device }} static static DeviceConnection() DeviceConnection() {{ //Initialize //Initialize static static members members ConnectionCount ConnectionCount == 0; 0; }} }} !
.cctor in Disassembly
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Static constructors are used to initialize static fields. Static constructors are also known as class constructors and type constructors. Static constructors are called after a program begins running but before the first instance of the class is created.
14
Module 6: Working with Types
The following example shows a DeviceConnection class that represents a generic connection to a device. The class maintains a static field that holds the current connection count. The static constructor is created with the same name as the class but has the static attribute, rather than the public or private attribute. class DeviceConnection { public static uint ConnectionCount; public void OpenConnection(string connectionName) { ConnectionCount++; //Other work to open device } static DeviceConnection() { //Initialize static members ConnectionCount = 0; } } class MainClass { public static void Main() { // At some point before next line, // static constructor is called DeviceConnection d = new DeviceConnection(); d.OpenConnection("GameConsole:Joy1/3"); // Next line prints 1 Console.WriteLine(DeviceConnection.ConnectionCount); } }
A static constructor has no access modifiers, such as private or public. Inside a static constructor, only static fields can be used. Instance fields must be initialized in an instance constructor. When the static constructor is viewed in disassembly, it has a different name, .cctor, which stands for class constructor. In disassembly, instance constructors are called .ctor. You can have both types of constructors in the same class.
Module 6: Working with Types
15
If you initialize a static field inline with a value, a static constructor is created automatically. The following example shows how the DeviceConnection class can be rewritten to use an implicit static constructor. The presence of the static constructor can be verified by viewing the disassembly of the code. class DeviceConnection { //Next line automatically creates static constructor public static uint ConnectionCount = 0; public void OpenConnection(string connectionName) { ConnectionCount++; //Other work to open device } }
In the preceding example, the static field ConnectionCount is initialized inline to a value of 0. When you initialize static fields inline, a static constructor is implicitly created in which the initialization occurs. If you also provide an explicit static constructor, the inline initialization is compiled into the explicit static constructors. Inside the static constructor, the code for the inline initializations runs first, and then the code that you wrote in the static constructor runs.
16
Module 6: Working with Types
Private Constructors Topic Objective
To explain when to use private constructors.
!
Prevent a Class from Being Instantiated
Lead-in
!
Use Them on Classes with All Static Members
!
Use a Protected Constructor to Inherit from the Class
A private constructor can never be called. Therefore any class with a private constructor cannot be instantiated.
class class Trig Trig {{ public public static static double double Sin Sin {{ //Calculate and //Calculate and return return public public static static double double Cos Cos {{ //Calculate and //Calculate and return return public public static static double double Tan Tan {{ //Calculate //Calculate and and return return private private Trig(){} Trig(){} }}
(double (double x) x) sin(x) sin(x) }} (double (double x) x) cos(x) cos(x) }} (double (double x) x) tan(x) tan(x) }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** A private constructor can never be called. Therefore any class with a private constructor cannot be instantiated. The only type of class that uses a private constructor is a class with all static members. The following example shows how a private constructor is used to prevent a class from being instantiated. class Trig { public static double Sin (double x) { //Calculate and return sin(x) } public static double Cos (double x) { //Calculate and return cos(x) } public static double Tan (double x) { //Calculate and return tan(x) } private Trig(){} }
Module 6: Working with Types
17
Classes with all static members can be used to maintain global algorithms, as shown in the preceding example. They can also be used as a singleton class, which has only one set of values and methods that is available while a program is running. To derive from a class with static members, you should mark the constructor as protected. Marking the constructor as protected will prevent programmers from creating instances of the class, but allow other classes to derive from it. Note A class with static members is different than an interface. The static members can contain implementations, a class can have static fields, but an interface cannot.
18
Module 6: Working with Types
" Type Operations Topic Objective
To provide an overview of the topics covered in this section.
Lead-in
The .NET Framework common language runtime supports a variety of type operations for working with types.
!
Conversions
!
Casting
!
Boxing
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The .NET Framework common language runtime supports a variety of type operations for working with types. This section discusses conversions and conversion operators for determining and converting the type of an object. This section also discusses how to cast types for conversion and for treating a type as a different type. It also describes boxing and unboxing value types to treat them as reference types.
Module 6: Working with Types
19
Conversions Topic Objective
To explain when and how to use explicit and implicit conversions.
Lead-in
Conversion is the process of changing a type to another type.
!
Explicit Conversions
int int xx == 5; 5; double double yy == (double) (double) x; x; !
Implicit Conversions
int int xx == 5; 5; double double yy == x; x; !
Conversion Operators
public public public public
static static static static
implicit implicit explicit explicit
operator operator operator operator
byte(Digit byte(Digit Digit(byte Digit(byte
d) d) b) b)
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Conversion is the process of changing a type to another type. Conversions are necessary when a value of one type must be assigned to a variable of a different type during assignment or when passing arguments to a method call.
Explicit and Implicit Conversions In explicit conversions, you do not need to specify the name of the type in a cast for assignment conversions or operand conversions. The following example shows how to convert an int value to a double value by using an explicit conversion. public static bool BiggerThanFive(double value) { if (value > 5) return true; else return false; } public static void Main() { int x = 5; //Use an explicit conversion for assignment double y = (double) x; //Use an explicit conversion for operand bool answer = BiggerThanFive((double) x); }
The preceding example is an example of a widening conversion. The bit size of an int is 32 bits, and the bit size of a double is 64 bits. Therefore, the type was widened during the conversion.
20
Module 6: Working with Types
In implicit conversions, you do not need to specify the name of the type during the conversion. Widening conversions can occur implicitly, and so they allow for simpler syntax. The preceding example could be written more simply as follows: public static void Main() { int x = 5; //Use an implicit conversion for assignment double y = x; //Use an implicit conversion for operand bool answer = BiggerThanFive(x); }
The following table shows the allowable implicit conversions for numeric types in the common language runtime. From
To
sbyte
short, int, long, float, double, or decimal
byte
short , ushort, int, uint, long, ulong, float, double, or decimal
short
int, long, float, double, or decimal
ushort
int, uint, long, ulong, float, double, or decimal
int
long, float, double, or decimal
uint
long, ulong, float, double, or decimal
long
float, double, or decimal
char
ushort, int, uint, long, ulong, float, double, or decimal
float
double
ulong
float, double, or decimal
Some conversions can result in a loss of precision. For example, converting an int to a float is an allowable implicit conversion, but a float has only seven digits of precision. Depending on the value of the int, some digits of precision may be lost. The following example shows a narrowing conversion in which a double is converted to an integer. double y = 4.56; int x = (int) y;
In this case, narrowing conversions must be explicit because there is almost always a loss of precision in the value that is being converted. In the preceding example, the value 4.56 will be truncated to 4 when it is assigned to x.
Module 6: Working with Types
21
Conversion Operators You can create user-defined conversions by providing conversion operators in your class or struct. Use the following syntax for specifying a conversion operator: public static [implicit | explicit] operator conv-type-out (conv-type-in operand) in which: !
conv-type-out is the name of the type to convert to.
!
conv-type-in is the name of the type to convert from.
!
operand is the name of the parameter holding the value being converted.
The following example shows how a structure that is called Digit, which represents a value from 0 to 9, can convert implicitly to a byte and explicitly to a Digit. struct Digit { byte value; public Digit(byte value) { if (value < 0 || value > 9) throw new ArgumentException(); this.value = value; } public static implicit operator byte(Digit d) { //Implicitly convert from Digit to short return d.value; } public static explicit operator Digit(byte b) { //Explicitly convert from short to Digit return new Digit(b); } } class MainClass { public static void Main() { Digit dig = new Digit(3); byte b = dig; //Implicit conversion operator invoked Console.WriteLine(b); //Prints 3 Digit dig2 = (Digit) b; //Explicit conversion invoked Console.WriteLine(dig2); //Implicit conversion prints 3 } }
22
Module 6: Working with Types
In the preceding example, the conversion from Digit to byte is implicit, while the conversion from byte to Digit is explicit. You should use the following guidelines to determine whether a conversion operator should be implicit or explicit. !
A conversion operator should be implicit to make code easier to read. Implicit conversions should never throw an error.
!
A conversion operator should be explicit whenever information could be lost in the conversion or if the conversion could throw an exception.
Module 6: Working with Types
23
Casting Topic Objective
To explain how casting is used in the .NET Framework.
Lead-in
Casting is used for explicit conversions. However, casting is also used for changing the type that is used to reference an object.
!
Casting Up from Derived Class to Base Class
!
Casting Down from Base Class to Derived Class
!
Type Operators
!
#
is
#
as
#
typeof
Casting Interfaces
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Casting is used for explicit conversions. However, casting is also used for changing the type that is used to reference an object.
Casting in an Inheritance Hierarchy Casting on objects is frequently used to change the reference type of an object to a base class reference or a derived class reference. When casting a derived class to a base class, you do not need to explicitly cast the object. The following example shows how a Shape type reference can be used to work with a Square object. //Shape is a base class class Shape {...} //Square is a derived class class Square : Shape {...} Square sq = new Square(); Shape sh = sq; sh.ShapeMethod();
When casting from a base class to a derived class, you must explicitly cast the object. If you cast to a derived type that does not match the underlying object, an InvalidCastException error will be thrown.
24
Module 6: Working with Types
The following example shows how to cast from a base class type to a derived class type. Square sq = new Square(); Shape sh = sq; Square sq2 = (Square) sh;//Cast down to Square sh = new Shape(); sq2 = (Square) sh;//InvalidCastException
Type Operators C# provides several operators to help cast object types appropriately. These operators help you avoid InvalidCastException errors. The is operator compares an object to a type. If the type matches the object, true is returned. If the type does not match the object, false is returned. Shape sh = new Shape(); Square sq; if (sh is Square) sq = (Square) sh;
The as operator attempts to convert an object to a type. If the conversion is successful, a reference of the specified type is assigned. Otherwise, null is assigned. Shape sh = new Shape(); Square sq = sh as Square; if (sq == null) Console.WriteLine("Shape was not a square"); else Console.WriteLine("Shape was a square");
You can use the typeof operator for more advanced type operations. The typeof operator will return a System.Type instance that describes the type through reflection. If you have an instance of a class, you can call GetType to obtain the same information. For more information about reflection, the mechanism for obtaining information about loaded assemblies and the types defined within them, including classes, interfaces, and value types, see “Discovering Type Information at Run time,” in the .NET Framework Software Developer’s Kit (SDK) documentation and Module 17, “Attributes,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET).
Casting Interfaces Casting is also used to obtain interface references to an object. The following example shows how the interface ICDPlayer can be obtained through casting from the Device class, which implements the ICDPlayer interface. ICDPlayer player; Device d = new Device(); player = (ICDPlayer) d;//Cast to interface player.Play();
Module 6: Working with Types
25
Boxing Topic Objective
To explain when and how boxing occurs.
!
Lead-in
Boxing and unboxing are conversions that occur automatically when a value type is converted to an object or when an object is converted to a value type.
Boxing Occurs to Convert a Value Type to a Reference Type #
Instance of System.Object is allocated on heap
#
Value type is copied to new object
int int xx == 5; 5; //Value //Value type type Object o = x; Object o = x; //Boxed //Boxed Console.WriteLine("The Console.WriteLine("The answer answer is is :: {0} {0} ", ", x);//Boxed x);//Boxed !
Unboxing Occurs to Retrieve Value Type from Object
int int yy == (int) (int) o; o; //unbox //unbox !
Boxing Can Be Expensive If Inside Loops
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Boxing and unboxing are conversions that occur automatically when a value type is converted to an object or when an object is converted to a value type. Boxing and unboxing occur implicitly and can affect the performance of your application code.
When Boxing Occurs Boxing occurs when a value type must be converted to a reference type. Boxing only occurs when converting to the type System.Object. Any other reference type must have implicit or explicit conversion operators to convert from value types. The following example shows how an integer value type is converted to an object reference type. int x = 5; //Value type Object o = x; //Implicit boxing operation occurs
In the preceding example, a boxing operation occurs when the object o is assigned the value in x. Boxing frequently occurs when calling methods that accept parameters of type Object. For example, Console.WriteLine can accept a string and an object parameter as follows. int x = 5; Console.WriteLine("The answer is: {0}",x);//Boxing occurs
In the preceding example, a boxing operation occurs on the call to Console.WriteLine. Because Console.WriteLine requires an Object type as the second parameter, x is implicitly boxed as an Object type.
26
Module 6: Working with Types
You can determine where boxing operations occur in your code by examining the disassembly. For example, if you use the Microsoft intermediate language (MSIL) Disassembler (Ildasm.exe), you will see an operation code called box, which is followed by the type that is being boxed.
How Boxing Works A value type must be copied from the stack to the heap if the value is going to be treated as a reference type. The boxing operation allocates memory on the heap for the value type and creates a reference to the new memory location. Then it copies the value type into the new memory location.
Unboxing To work with the value inside a boxed type, it must first be unboxed. Unboxing a type will copy it from the heap into a variable on the stack. The following example shows how to unbox an integer. int x = 5; Object o = x; //box int y = (int) o; //unbox
If the underlying value type that is being unboxed is incompatible with the type of the variable that it is assigned to, you will get an InvalidCastException. int x = 5; short s; Object o = x; //box s = (short) o; //Will throw InvalidCastException
Consequences Boxing can cause performance problems if you do not minimize its occurrence. The following example shows the consequences of boxing. int age = 5; int count = 1; for (count = 1;count < 10;count++) { Console.WriteLine("Current Age = {0}",age);//1 Box Console.WriteLine("Age in {0} year(s) will be {1}", count, age+count);//2 box operations }
In the preceding example, three boxing operations will occur for every iteration through the loop. This kind of scenario can be very costly in terms of performance. One solution is to box value types before entering a loop, as in the following example. int age = 5; Object oAge = age; //1 box operation int count = 1; for (count = 1;count < 10;count++) { Console.WriteLine("Current Age = {0}",oAge); Console.WriteLine("Age in {0} year(s) will be {1}", count, age+count);//2 box operations }
Module 6: Working with Types
27
In cases when the need for a higher level of performance is necessary, custom classes can be created to encapsulate values and work with values as reference types. class RefInt32 { public int Value; public RefInt32(int value) { this.Value = value; } public override string ToString() { return Value.ToString(); } public static implicit operator int (RefInt32 refInt) { return refInt.Value; } } int age = 5; RefInt32 refAge = new RefInt32(age); //Convert to ref type int count = 1; RefInt32 refCount = new RefInt32(count); //Convert to ref type RefInt32 refTotal = new RefInt32(0); for (refCount.Value = 1;refCount.Value < 10;refCount.Value++) { refTotal.Value = refAge.Value + refCount.Value; Console.WriteLine("Current Age = {0}",refAge); Console.WriteLine("Age in {0} year(s) will be {1}", refCount,refTotal);//0 box operations }
The preceding example demonstrates the importance of overriding appropriate Object methods to get expected behavior. If the recipient of your reference type calls one of the methods that are expecting appropriate behavior, it may cause problems. In the preceding example, you must override ToString.
28
Module 6: Working with Types
" Interfaces Topic Objective
To provide an overview of the topics covered in this section.
Lead-in
!
Inheritance Considerations
!
Explicit Interface Implementation
When designing and using interfaces, you should consider a number of factors.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** When designing and using interfaces, you should consider a number of factors. This section discusses how multiple inheritance works through interfaces. This section also explains how to explicitly implement interfaces.
Module 6: Working with Types
29
Inheritance Considerations Topic Objective
To explain the use of inheritance and the use of interfaces.
Lead-in
Interfaces are used only through inheritance.
!
Multiple Inheritance
interface interface IFoo IFoo interface interface IBar IBar class class MyObject MyObject !
{void {void DoSomething1();} DoSomething1();} {void {void DoSomething2();} DoSomething2();} :: IFoo, IFoo, IBar IBar {...} {...}
Deriving New Interfaces from Existing Ones
interface interface INewInterface INewInterface :: IFoo, IFoo, IBar IBar {void DoSomething3();} {void DoSomething3();} !
Base Class Interfaces
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Interfaces are used only through inheritance. There are several ways to group or partition functionality when you are using interfaces. You can use multiple inheritance to combine functionality from multiple interfaces. Interfaces also can inherit from other interfaces, allowing functionality to be partitioned into multiple groups.
Multiple Inheritance In the .NET Framework common language runtime, a class must inherit from only one class. However, a class can inherit from zero or more interfaces in addition to inheriting from one class. Interfaces are the only type to allow multiple inheritance in the common language runtime. The following example shows how a class can inherit from two interfaces. interface IFoo { void DoSomething1(); } interface IBar { void DoSomething2(); } class MyObject : IFoo, IBar { public void DoSomething1() { Console.WriteLine("DoSomething1 called"); } public void DoSomething2() { Console.WriteLine("DoSomething2 called"); } }
30
Module 6: Working with Types
Multiple inheritance is an important design consideration when you are choosing between abstract classes and interfaces. If two sets of functionality are designed into two abstract classes, a single class cannot inherit from both classes. However, if interfaces are used, a single class can inherit from both interfaces. If a specification of functions, properties, or events is likely to be used by many different kinds of classes, you should use an interface instead of an abstract class.
Deriving New Interfaces from Existing Ones You can also create new interfaces by deriving from other interfaces. The following example shows how a new interface that is called INewInterface is created from the IFoo and IBar interfaces. interface IFoo { void DoSomething1(); } interface IBar { void DoSomething2(); } interface INewInterface : IFoo, IBar { void DoSomething3(); } class MyObject : INewInterface { public void DoSomething1() { Console.WriteLine("DoSomething1 called"); } public void DoSomething2() { Console.WriteLine("DoSomething2 called"); } public void DoSomething3() { Console.WriteLine("DoSomething3 called"); } }
Module 6: Working with Types
31
Base Class Interfaces If you derive a new class from a base class that implements an interface, you must decide if the derived class will also implement the interface. When an instance of the derived class is converted to the interface, the inheritance hierarchy is searched until a class that implements the interface is found. Then the interface methods are called on that class. The following example shows what happens when interface methods are called on a derived class that does not implement the interface. interface IFoo { void DoSomething1(); } class Base : IFoo { public void DoSomething1() { Console.WriteLine("Base DoSomething1 called"); } } class Derived : Base { public new void DoSomething1() { Console.WriteLine("Derived DoSomething1 called"); } } class MainClass { public static void Main() { Derived d = new Derived(); IFoo i = (IFoo) d; d.DoSomething1(); i.DoSomething1(); } }
This code generates the following output: Derived DoSomething1 called Base DoSomething1 called
In the preceding example, the base class DoSomething1 method is called when an IFoo variable is used. If this behavior is not the desired behavior, then the derived class must be modified to also inherit from the IFoo interface. Then, the derived class DoSomething1 method will be called when the IFoo variable is used.
32
Module 6: Working with Types
Explicit Interface Implementation Topic Objective
To distinguish class accessibility and interface accessibility, and to explain when to use explicit interface implementation.
Lead-in
When you implement an interface in a class, the interface methods are exposed through the interface type, and the class type.
!
Class vs. Interface Accessibility
!
Use Explicit Interface Implementation #
When interface member names that are combined with class names are confusing
#
When inheriting from multiple interfaces with same members
interface interface IFoo IFoo {void {void DoSomething();} DoSomething();} interface IBar {void interface IBar {void DoSomething();} DoSomething();} class class MyObject MyObject :: IFoo, IFoo, IBar IBar {{ void IFoo.DoSomething() void IFoo.DoSomething() {Console.WriteLine("IFoo {Console.WriteLine("IFoo DoSomething DoSomething called");} called");} void void IBar.DoSomething() IBar.DoSomething() {Console.WriteLine("IBar {Console.WriteLine("IBar DoSomething DoSomething called");} called");} }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** When you implement an interface in a class, the interface methods are exposed through the interface type and the class type. The following example shows how an interface method can be called by both the class type and the interface type. interface IFoo { void DoSomething(); } class MyObject : IFoo { public void DoSomething() { Console.WriteLine("DoSomething called"); } } class MainClass { public static void Main() { MyObject o = new MyObject(); //Call method through class type o.DoSomething(); IFoo foo = (IFoo) o; //Call method through interface type foo.DoSomething(); } }
Module 6: Working with Types
33
You can use explicit interface implementation to prevent the class type from accessing the method. This technique is useful if the interface member names are likely to confuse a user of the class. Also, explicit interface implementation is mandatory if you inherit from multiple interfaces that have the same members. To use explicit interface implementation, you prepend the name of the interface to the name of the member in your class definition. You should not use any access modifiers in the member definition. Access modifiers are not allowed because the explicit interface implementation is only accessible through the interface type. The following example shows how explicit interface implementation can avoid conflicts between multiple interfaces with the same members. interface IFoo { void DoSomething(); } interface IBar { void DoSomething(); } class MyObject : IFoo, IBar { void IFoo.DoSomething() { Console.WriteLine("IFoo DoSomething called"); } void IBar.DoSomething() { Console.WriteLine("IBar DoSomething called"); } } class MainClass { public static void Main() { MyObject o = new MyObject(); IFoo foo = (IFoo) o; IBar bar = (IBar) o; foo.DoSomething(); bar.DoSomething(); } }
This code generates the following output: IFoo DoSomething called IBar DoSomething called
34
Module 6: Working with Types
" Managing External Types Topic Objective
To provide an overview of the topics covered in this section.
Lead-in
!
Platform Invocation Services
!
COM Interoperability
The .NET Framework common language runtime is not an isolated system. Most programs will require some level of interaction with outside applications.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The .NET Framework common language runtime is not an isolated system. Most programs will require some level of interaction with outside applications. The common language runtime provides Platform Invocation Services for interacting with code in DLLs. Also, the common language runtime provides integration services to interact with COM objects. Warning Using unmanaged external types is risky because the common language runtime cannot enforce security, manage memory, or provide code type safety verification checking for unmanaged code. This module does not cover all aspects of Platform Invocation Services or COM Integration Services. For more information, see Module 15, “Interoperating Between Managed and Unmanaged Code,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET) and “Interoperating with Unmanaged Code” in the .NET Framework SDK documentation.
Module 6: Working with Types
35
Platform Invocation Services Topic Objective
To introduce the use of Platform Invocation Services to call external functions.
Lead-in
Platform Invocation Services, also known as PInvoke, allow managed code to interact with unmanaged code in existing DLLs.
!
Also Known as PInvoke
!
Use DllImport Attribute to Import an API Function from an External DLL
[DllImport("user32.dll", [DllImport("user32.dll", CharSet=CharSet.Ansi)] CharSet=CharSet.Ansi)] public public static static extern extern int int MessageBox(int MessageBox(int h, h, string string m, m, string string c, c, int int type); type); public public static static void void Main() Main() {{ string pText string pText == "Hello "Hello World!"; World!"; string string pCaption pCaption == "PInvoke "PInvoke Test"; Test"; MessageBox(0, MessageBox(0, pText, pText, pCaption, pCaption, 0); 0); }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Platform Invocation Services, also known as PInvoke, allow managed code to interact with unmanaged code in existing DLLs. PInvoke provides the code that is needed to locate and invoke a function and to marshal parameters to and from the function. To call an external function, you use the DllImport attribute to provide the name of the DLL and any other characteristics, such as the use of ANSI or Unicode strings by the DLL. After the function has been declared, you can call it from managed code. The following example shows how to call the MessageBox API located in User32.dll. using System; using System.Runtime.InteropServices; class MainClass { [DllImport("user32.dll", CharSet=CharSet.Ansi)] public static extern int MessageBox(int h, string m, string c, int type); public static void Main() { string pText = "Hello World!"; string pCaption = "PInvoke Test"; MessageBox(0, pText, pCaption, 0); } }
For more information on PInvoke, see Module 15, “Interoperating Between Managed and Unmanaged Code,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET) and “Interoperating with Unmanaged Code” in the .NET Framework SDK documentation.
36
Module 6: Working with Types
COM Interoperability Topic Objective
To introduce COM interoperability.
Lead-in
COM interoperability allows managed code to create and call COM objects. It also allows COM objects to create and call .NET Framework objects.
!
!
Exposing .NET Framework Classes to COM #
Must create COM Callable Wrapper
#
Create CCWs by using Tlbexp.exe
#
Register by using Regasm.exe
Exposing COM Classes to the .NET Framework #
Must create Runtime Callable Wrapper
#
Create RCWs by using Tlbimp.exe
*****************************ILLEGAL FOR NON-TRAINER USE****************************** COM interoperability allows managed code to create and call COM objects. It also allows COM objects to create and call .NET Framework objects.
Exposing .NET Framework Classes to COM You can expose .NET Framework classes to COM by creating COM callable wrappers (CCW) for each .NET Framework class in your application. The CCW creates and manages standard COM elements, such as IUknown and other standard interfaces, GUIDs, IIDs, and reference counting. These elements allow COM objects to interoperate with your .NET Framework classes as if the .NET Framework classes were real COM classes. The .NET Framework classes that you write do not need to be aware of any COM elements. However, you can directly control some COM elements when necessary, by such means as specifying manual IIDs for your interfaces. You can create CCWs for your .NET Framework classes by running the Type Library Exporter (Tlbexp.exe). You can also register your .NET Framework classes as COM classes by using the Assembly Registration Tool (Regasm.exe).
Module 6: Working with Types
Exposing COM Classes to the .NET Framework Managed code can also create and call existing COM objects. You can accomplish this by creating a runtime callable wrapper (RCW) for each COM class that you want to access from managed code. Similar to the CCW, the RCW handles COM specific semantics, such as reference counting, QueryInterface for different IIDs, and marshaling. Your managed code can use COM interfaces as if they were managed interfaces and COM classes as if they were managed classes. You can create RCWs by using the Type Library Importer (Tlbimp.exe). For more information on COM Interopability, see Module 15, “Interoperating Between Managed and Unmanaged Code,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET) and “Interoperating with Unmanaged Code” in the .NET Framework SDK documentation.
37
38
Module 6: Working with Types
Lab 6: Working with Types Topic Objective
To introduce the lab.
Lead-in
In this lab, you will create a class that overrides methods in System.Object. You will also create an interface that must be implemented explicitly.
*****************************ILLEGAL FOR NON-TRAINER USE******************************
Objectives After completing this lab, you will be able to: !
Create classes that override the GetHashCode, and equality methods and operators.
!
Create classes that provide conversion operators.
!
Implement an explicit interface in a class and use it from a client.
Lab Setup Starter and solution files are associated with this lab. The starter files are in the folder \Labs\Lab06\Starter, and the solution files are in the folder \Labs\Lab06\Solution.
Prerequisites Before working on this lab, you must have: !
Knowledge about how to use Microsoft Visual Studio® .NET for creating and working with console applications.
!
Knowledge about the C# language.
!
Knowledge about the System.Console namespace for interacting with the console.
Estimated time to complete this lab: 45 minutes
Module 6: Working with Types
39
Exercise 1 Overriding System.Object Methods In this exercise, you will create a structure that represents an office in a building. The Office structure stores the size, building number, and room number of an office. You will override appropriate methods and operators to support retrieving hash codes and comparing different offices. You will also create a structure called OfficeUnit that is similar to the Office structure. The only difference between the OfficeUnit structure and the Office structure is that OfficeUnit represents size as an enumerated type with the values small, medium, or large. The Office structure represents size as square footage. You will write an explicit conversion operator to convert values of type Office to type OfficeUnit.
! Implement a hash code 1. Open the Offices project located in the \Labs\ Lab06\Starter\Offices folder. 2. Open the Office.cs file. 3. Locate the Office structure. 4. Override the GetHashCode method. Generate and return a hash code by using the following algorithm: HashCode = Building * 10000 + Room
! Provide a string representation 1. Override the ToString method. 2. Generate and return a string that contains the building number, followed by a forward slash, followed by the room number. You can use the following code to build and return the string. return String.Concat(Building,"/",Room);
! Implement equality 1. Override the Equals method. 2. Cast the object parameter to type Office. 3. Compare the size, room, and building numbers of the parameter to this. If the values are all equal, return true. If they are not equal, return false. 4. Override the == and != operators. Implement them by calling the Equals method.
40
Module 6: Working with Types
! Implement conversion 1. Define an explicit conversion operator to convert from type Office to OfficeUnit. 2. Convert the size from square footage to an OfficeSizes value according to the following table. The OfficeSizes enumeration is already defined in the start code for you. Square Footage
OfficeSizes
<= 64
Small
>64 and <100
Medium
>=100
Large
3. Assign the Building and Room values to the new type and return an instance of the new type.
! Test 1. Open the Main.cs file. 2. Create two offices called o1 and o2. Assign different sizes, room numbers, and building numbers to each office variable. 3. Use the == operator to compare the two offices. Print the results to the console. 4. Call GetHashCode on both offices and print the results. 5. Call ToString on both offices and print the results. 6. Create an OfficeUnit and assign it one of the offices. Use explicit conversion to assign the office. 7. Print all of the values of the OfficeUnit. 8. Compile and run the program. The two offices should not be equal. They should have different hash codes. The ToString method should print the building and room numbers, not the class names. Finally, the printed enumerated size should match the square footage appropriately.
Module 6: Working with Types
41
Exercise 2 Implementing an Explicit Interface In this exercise, you will create an interface called IRange, which provides properties and methods to select and print a range. You will also modify a class called TextEntry to inherit from IRange. This combination allows text in the TextEntry class to be selected as a range and printed. The TextEntry class already implements a Print method. Because the IRange interface also has a Print method, you will need to explicitly implement IRange in the TextEntry class to avoid a name conflict.
! Create an explicit interface 1. Open the Interfaces project located in the \Labs\ Lab06\Starter\Interfaces folder. 2. Open the TextEntry.cs file. 3. Define an interface called IRange that has the following properties. Property Name
Type
RangeBegin
uint
RangeEnd
uint
4. Define the following methods for IRange. Method Name
Return Type
Parameters
Select
void
uint rb, uint re
Print
void
none
5. Modify the TextEntry class to inherit from IRange. 6. In the TextEntry class, create private fields named rangeBegin and rangeEnd to store the RangeBegin and RangeEnd data values. Use the uint type for both fields. 7. Implement the IRange RangeBegin property. a. Implement the get accessor by returning the value of rangeBegin. b. Implement the set accessor by storing the value in rangeBegin. Check to ensure that the new value is less than the length of the text field, and that the value of rangeEnd is not less than rangeBegin. 8. Implement the IRange RangeEnd property. a. Implement the get accessor by returning the value of rangeEnd. b. Implement the set accessor by storing the value in rangeEnd. Check to ensure that the new value does not exceed the length of the text field, and that the value of rangeEnd is not less than rangeBegin. 9. Implement the IRange Select method. Ensure that the re parameter is not less than the rb parameter, and that the new values are not greater than the length of the text field. Assign rb to rangeBegin and re to rangeEnd. 10. Implement the IRange Print method. Using the rangeBegin and rangeEnd fields, create a text range from the text field by using the SubString method. Then print the resulting string to the console.
42
Module 6: Working with Types
! Use the interface 1. Open the Main.cs file. 2. In the MainClass class, create an instance of the TextEntry class called myEntry. 3. Create an interface of type IRange named r and cast myEntry to r. 4. Initialize the Text property on myEntry with a value, such as “The quick brown fox jumped over the gate.” 5. Select a range by using the interface variable r. For example, the values 4 and 10 will select the word “quick.” 6. Call the Print method on myEntry. 7. Call the Print method on r. 8. Compile and run the program. You should see the entire Text property printed to the console, followed by the range of text you selected.
Module 6: Working with Types
43
Review Topic Objective
To reinforce module objectives by reviewing key points.
Lead-in
The review questions cover some of the key concepts taught in the module.
!
System.Object Class Functionality
!
Specialized Constructors
!
Type Operations
!
Interfaces
!
Managing External Types
*****************************ILLEGAL FOR NON-TRAINER USE****************************** 1. What kind of algorithm should you use to implement the GetHashCode method? The algorithm should be efficient and return a unique number.
2. What is the default behavior of the ToString method? The default behavior of the ToString method is to return the name of the class.
3. How can you determine if two separate object references are the same object? Use the ReferenceEquals method to compare the two references.
4. If you override the Equals method, what else should you override? You should override the == and != operators.
5. When should you use a private constructor? Use a private constructor to prevent a class from being instantiated. An example of such a class is a singleton class with static methods and fields.
44
Module 6: Working with Types
6. When should you use implicit conversions, and when should you use explicit conversions? Use implicit conversions for improved readability and use. Use explicit conversions when the conversion could cause a loss of data or throw an exception.
7. When do boxing operations occur? Boxing occurs when a value type is converted to an Object type. Unboxing occurs when the value is retrieved from an Object type.
8. How do you explicitly implement an interface? Do not use the public access modifier. Use the name of the interface in front of the field or method names. For example, void IFoo.DoSomething().
Module 7: Strings, Arrays, and Collections Contents Overview Strings Terminology – Collections
1 2 20
.NET Framework Arrays
21
.NET Framework Collections
39
Lab 7: Working with Strings, Enumerators, and Collections 57 Review
63
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, place or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. 2001-2002 Microsoft Corporation. All rights reserved. Microsoft, ActiveX, BizTalk, IntelliMirror, Jscript, MSDN, MS-DOS, MSN, PowerPoint, Visual Basic, Visual C++, Visual C#, Visual Studio, Win32, Windows, Windows Media, and Window NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
Module 7: Strings, Arrays, and Collections
iii
Instructor Notes Presentation: 120 Minutes Lab: 60 Minutes
After completing this module, students will be able to: !
Parse, format, manipulate, and compare strings.
!
Use the classes in the System.Array and System.Collections namespaces.
!
Improve the type safety and performance of collections by using specialized collections and class-specific code.
Materials and Preparation This section provides the materials and preparation tasks that you need to teach this module.
Required Materials To teach this module, you need the Microsoft® PowerPoint® file 2349B_07.ppt.
Preparation Tasks To prepare for this module, you should: !
Read all of the materials for this module.
!
Practice the demonstrations.
!
Complete the lab.
iv
Module 7: Strings, Arrays, and Collections
Demonstrations This section provides demonstration procedures that will not fit in the margin notes or are not appropriate for the student notes.
Sorting and Enumerating an Array In this demonstration, you will show students how to sort and enumerate an array. The code for this demonstration is contained in one project and is located in \Democode\Mod07\Demo07.1. In addition, the code for the individual demonstration is provided in the student notes.
ArrayList In this demonstration, you will show students how ArrayList implements the IList interface by using an array whose size is dynamically increased as required. The code for this demonstration is contained in one project and is located in \Democode\Mod07\Demo07.2. In addition, the code for the individual demonstration is provided in the student notes.
Hashtable In this demonstration, you will show students how to create a hash table that is used for searches. The code for this demonstration is contained in one project and is located in \Democode\Mod07\Demo07.3. In addition, the code for the individual demonstration is provided in the student notes. In all of the preceding demonstrations, use the debugger to step through the code while you point out features.
Module 7: Strings, Arrays, and Collections
Module Strategy Use the following strategy to present this module: !
Strings Discuss how to work with strings in the Microsoft .NET Framework, including common operations, such as parsing, formatting, manipulating, and comparing strings.
!
Terminology – Collections Define the term collection as it is used in this module and identify where collections are found in the .NET Framework. Be sure that students understand that the term collection is used in its broader sense: to describe a group of items.
!
.NET Framework Arrays Introduce the System.Array class as the base class of all array types that contains methods for creating, manipulating, searching, and sorting arrays. Discuss features of arrays that are specific to C#. Explain the role of the IEnumerable and IEnumerator interfaces in System.Array and System.Collections classes. Use the Sorting and Enumerating an Array demonstration to show how to sort and enumerate an array.
!
.NET Framework Collections Briefly introduce some commonly used classes in the System.Collections namespace. Discuss the IList interface with regards to classes that represent an ordered collection of objects that can be individually indexed. Use the ArrayList demonstration to reinforce this concept. Discuss the IDictionary interface and the classes that it implements. Use the Hashtable demonstration to show how to use the IDictionary interface. Provide guidelines to help students distinguish between collections and arrays, and explain when collections are used. Discuss runtime casting for type safety and the effects of runtime casting, and boxing and unboxing on performance. Discuss techniques for handling boxing and unboxing to optimize performance.
v
Module 7: Strings, Arrays, and Collections
1
Overview Topic Objective
To provide an overview of the module topics and objectives.
Lead-in
In this module, you will learn about some of the key classes in the .NET Framework class library.
!
Strings
!
Terminology – Collections
!
.NET Framework Arrays
!
.NET Framework Collections
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this module, you will learn about some of the key classes in the Microsoft® .NET Framework class library. Specifically, you will learn how to work with strings, arrays, collections, and enumerators. After completing this module, you will be able to: !
Parse, format, manipulate, and compare strings.
!
Use the classes in the System.Array and System.Collections namespaces.
!
Improve the type safety and performance of collections by using specialized collections and class-specific code.
2
Module 7: Strings, Arrays, and Collections
" Strings Topic Objective
To introduce the topics in the section.
!
Parse
Lead-in
!
Format
!
Format Examples
!
Changing Case
!
Compare
!
Trim and Pad
!
Split and Join
!
StringBuilder
!
C# Specifics
!
Regular Expressions
In this section, you will learn how to work with strings in the .NET Framework.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In the C# language, string is an alias for System.String in the .NET Framework. The System.String type represents a string of Unicode characters. Working with strings is an everyday task in software development, and includes operations, such as parsing, formatting, manipulating, and comparing strings. The String object is immutable. Therefore, every time you use one of the methods in the System.String class, you create a new string object. When you want to perform repeated modifications to a string, the overhead that is associated with creating a new String object can be costly. As an alternative, you can use the System.Text.StringBuilder class to modify a string without creating a new object. In this section, you will learn how to work with strings in the .NET Framework.
Module 7: Strings, Arrays, and Collections
3
Parse Topic Objective
!
To explain how the Parse method is used to convert numeric strings to a .NET Framework numeric base type.
Parse Method Converts a Numeric String to a Numeric
string string MyString MyString == "12345"; "12345"; int MyInt int MyInt == int.Parse(MyString); int.Parse(MyString); MyInt++; MyInt++; Console.WriteLine(MyInt); Console.WriteLine(MyInt); // // The The output output to to the the console console is is "12346". "12346".
Lead-in
The Parse method converts a string that represents a .NET Framework numeric base type to an actual .NET Framework numeric base type.
!
To Ignore Commas, Use the NumberStyles.AllowThousands Flag
string string MyString MyString == "123,456"; "123,456"; int MyInt int MyInt == int.Parse(MyString, int.Parse(MyString, System.Globalization.NumberStyles.AllowThousands); System.Globalization.NumberStyles.AllowThousands); Console.WriteLine(MyInt); Console.WriteLine(MyInt); // // The The output output to to the the console console is is "123456". "123456".
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The Parse method converts a string that represents a .NET Framework numeric base type to an actual .NET Framework numeric base type. The Parse method takes a combination of up to three parameters, as follows: !
The string to be converted
!
One or more values from the System.Globalization.NumberStyles enumeration
!
A NumberFormatInfo class
Because the Parse method assumes that all string input represents a base-10 value, non-base-10 values are not parsable. The Parse method also does not parse strings that represent the values NaN (Not A Number), PositiveInfinity, or NegativeInfinity of the Single and Double classes because they are not real numbers. The following code example converts a string to an int value, increments that value, and displays the result: string MyString = "12345"; int MyInt = int.Parse(MyString); MyInt++; Console.WriteLine(MyInt); // The output to the console is "12346".
4
Module 7: Strings, Arrays, and Collections
Handling Nonnumeric Characters The NumberStyles enumeration is useful if you have a string that contains nonnumeric characters that you want to convert into a .NET Framework numeric base type. You must use this enumeration to parse a string with a currency symbol, decimal point, exponent, parentheses, and so on. For example, a string that contains a comma cannot be converted to an int value by using the Parse method unless you pass the System.Globalization.NumberStyles enumeration.
Incorrect Way to Parse a String with Nonnumeric Characters The following code example is invalid and raises an exception. It illustrates the incorrect way to parse a string that contains nonnumeric characters. string MyString = "123,456"; // the following line raises a System.Format exception int MyInt = int.Parse(MyString); Console.WriteLine(MyInt);
Correct Way to Parse a String with Nonnumeric Characters When you apply the System.Globalization.NumberStyles enumeration with the AllowThousands flag, the Parse method ignores the comma that raised the exception in the preceding example. The following code example uses the same string as the preceding example but does not raise an exception. string MyString = "123,456"; int MyInt = int.Parse(MyString, System.Globalization.NumberStyles.AllowThousands); Console.WriteLine(MyInt); // The output to the console is "123456"
Module 7: Strings, Arrays, and Collections
5
Format Topic Objective
To explain how to use format strings, or specifiers, to format the appearance of your application.
Lead-in
The .NET Framework provides several format strings that you can use to format the appearance of strings that derive from other objects.
!
Format Strings Are Used in Methods That Create String Representations of a .NET Framework Data Type #
To display $100.00 to the console on computers on which U.S. English is the current culture
int int MyInt MyInt == 100; 100; string string MyString MyString == MyInt.ToString("C"); MyInt.ToString("C"); Console.WriteLine(MyString); Console.WriteLine(MyString); #
Alternatively
int int MyInt MyInt == 100; 100; Console.WriteLine("{0:C}", Console.WriteLine("{0:C}", MyInt); MyInt);
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The .NET Framework provides several format strings, or specifiers, that you can use to format the appearance of strings that derive from other objects. There are several advantages to converting base data types to strings before displaying them to users. Strings are easily displayed and can be appended to the messages and dialog boxes of your application. You can also use format specifiers to display the same numeric value in scientific format, monetary format, hexadecimal format, and so on.
When to Use Format Strings You can use format specifiers in situations where your application stores information in a format that is designed for use by the application, and not by the user. For example, a business application may keep track of the current date and time in a DateTime object to log when transactions are completed. The DateTime object stores information in which the user is not necessarily interested, such as the number of milliseconds that have elapsed since the creation of the object. You can also use format specifiers to display only information that is of interest to the user, such as the date and hour of the transaction. Additionally, you can dynamically modify strings that are created by using format specifiers to represent monetary, date, and time conventions for the current culture. For example, your application can display the date and time in the notation that is specific to the user’s current culture.
6
Module 7: Strings, Arrays, and Collections
Methods Used with Format Strings Format strings are used with any method that creates a string representation of a .NET Framework data type, such as Int32, Int64, Single, Double, Enumeration, DateTime, and so on. Format strings are also used with Console.Writeline, String.Format, and several methods in the System.IO namespace. Additionally, every base data type contains a ToString method that returns a string representation of the data type’s value and accepts a string format specifier. You can control the layout and design of strings that are created by any of these methods by using one of several format strings defined by the .NET Framework.
Using the ToString Method The ToString method is useful if you want to convert one of the standard .NET Framework data types to a string that represents that type in some other format. For example, if you have an integer value of 100 that you want to represent to the user as a currency value, you can easily use the ToString method and the currency format string ("C") to produce a string of "$100.00". The original value that is contained in the data type is not converted, but a new string is returned that represents the resulting value. This new string cannot be used for calculation until it is converted back to a .NET base data type. The original value, however, can be calculated at any time. Note Computers that do not have U.S. English specified as the current culture will display whatever currency notation is used by the current culture. In the following code example, the ToString method displays the value of 100 as a currency-formatted string in a console window: int MyInt = 100; string MyString = MyInt.ToString("C"); Console.WriteLine(MyString);
The preceding code displays $100.00 to the console on a computer on which U.S. English is the current culture.
Using Console.Writeline The Console.WriteLine method also accepts a format string specifier as an argument and can produce the same value as the preceding example. Console.Writeline accepts string format specifiers in the form, where the characters inside the curly brackets specify the formatting to apply to the variable. The following code example uses the Console.WriteLine method to format the value of MyInt to a currency value. int MyInt = 100; Console.WriteLine("{0:C}", MyInt);
In the preceding example, the 0 character specifies the variable or value on which to apply formatting. In this example, it is the first and only variable. The characters that follow the colon are interpreted as string format specifiers.
Module 7: Strings, Arrays, and Collections
7
Format Examples Topic Objective
To provide examples of format strings that return common numeric string types.
!
Currency Format #
C - $XX,XXX.XX
int int MyInt MyInt == 12345; 12345; string string MyString MyString == MyInt.ToString("C" MyInt.ToString("C" ); ); // // In In the the U.S. U.S. English English culture: culture: "$12,345.00" "$12,345.00"
Lead-in
Let’s look at some examples of format strings that return the value of MyString in the currency and date time formats.
!
Date Time Format #
D - dd MMMM yyyy
#
d - MM/dd/yyyy
DateTime DateTime MyDate MyDate == new new DateTime(2000, DateTime(2000, 1, 1, 10, 10, 0, 0, 0, 0, 0); 0); string string MyString MyString == MyDate.ToString( MyDate.ToString( "d" "d" ); ); // // In In the the U.S. U.S. English English culture: culture: "1/10/2000" "1/10/2000"
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The standard numeric, picture numeric, date and time, and enumeration format strings are described in detail in the .NET Framework Software Development Kit (SDK). This topic provides examples of these format strings. The following examples show the use of the format string that returns the value of MyInt in the currency format: int MyInt = 12345; string MyString = MyInt.ToString( "C" ); // In the U.S. English culture, MyString has the value: // "$12,345.00".
The following table lists some format characters for standard patterns that are used to format DateTime types. Format Character
Associated Property/Description
Example Format Pattern (en-US)
d
ShortDate Pattern
MM/dd/yyyy
D
LongDatePattern
Dd MMMM yyyy
f
Full date and time (long date and short time)
Dd MMMM yyyy
FullDateTimePattern (long date and long time)
Dd MMMM yyyy
F
HH:mm HH:mm:ss
8
Module 7: Strings, Arrays, and Collections
The following example shows the use of the format string that returns the value of MyDate in the short date pattern format: DateTime MyDate = new DateTime(2000, 1, 10, 0, 0, 0); string MyString = MyDate.ToString( "d" ); // In the U.S. English culture, MyString has the value: // "1/10/2000".
Module 7: Strings, Arrays, and Collections
9
Changing Case Topic Objective
To explain how to use the String.ToUpper and String.ToLower methods to change the case of a string.
Lead-in
You can easily change the case of a string by using the String.ToUpper and String.ToLower methods.
!
You Can Easily Change the Case of a String #
String.ToUpper – converts to upper case
string string MyString MyString == "hello "hello world!"; world!"; // // outputs: outputs: HELLO HELLO WORLD! WORLD! Console.WriteLine(MyString.ToUpper()); Console.WriteLine(MyString.ToUpper()); #
String.ToLower – converts to lower case
string string MyString MyString == "HELLO "HELLO WORLD!"; WORLD!"; // outputs: hello world! // outputs: hello world! Console.WriteLine(MyString.ToLower()); Console.WriteLine(MyString.ToLower());
*****************************ILLEGAL FOR NON-TRAINER USE****************************** You can easily change the case of a string by using the two methods that are described in the following table. Method Name
Use
String.ToUpper
Converts all characters in a string to uppercase.
String.ToLower
Converts all characters in a string to lowercase.
String.ToUpper and String.ToLower provide an override that accepts a culture. The String.ToUpper method changes all of the characters in a string to uppercase. The following code example converts the string "hello world!" from lowercase to uppercase: string MyString = "hello world!"; Console.WriteLine(MyString.ToUpper());
The preceding example displays HELLO WORLD! to the console. The String.ToLower method is similar to the String.ToUpper method, but it converts all of the characters in a string to lowercase. The following code example converts the string "HELLO WORLD!" to lowercase. string MyString = "HELLO WORLD!"; Console.WriteLine(MyString.ToLower());
The preceding example displays hello world! to the console.
10
Module 7: Strings, Arrays, and Collections
Compare Topic Objective
To introduce some of the value-comparison methods that are used to compare the values of strings.
!
The .NET Framework Has Methods to Compare Strings #
Lead-in
The .NET Framework provides several valuecomparison methods to compare the values of strings.
For example, the Compare method compares the current string object to another string or object, returning: - Negative if first string is less than second string - 0 if the two strings are equal - Positive if first string is greater than second string
string string MyString MyString == "Hello "Hello World!"; World!"; Console.WriteLine( Console.WriteLine( String.Compare(MyString,"Hello String.Compare(MyString,"Hello World!")); World!")); // // outputs: outputs: 00
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The .NET Framework provides several methods to compare the values of strings. The following table describes some of the value-comparison methods. Method Name
Use
String.Compare
Compares the values of two strings. Returns an integer value.
String.StartsWith
Determines if a string begins with the string passed. Returns a boolean value.
String.IndexOf
Returns the index of the first occurrence of a String, or one or more characters, within this instance.
For more information about value comparison methods and for a complete list of these methods, see “Comparing Strings” in the .NET Framework SDK documentation. For example, the String.Compare method provides a thorough way to compare the current string object to another string or object. You can use this function to compare two strings or substrings of two strings. Additionally, overloads are provided that regard or disregard case and cultural variance.
Module 7: Strings, Arrays, and Collections
The following table shows the three integer values that are returned by the Compare(string strA, string strB) method. Value Type
Condition
A negative integer
strA is less than strB
0
strA equals strB
A positive integer
strA is greater than strB
The following code example uses the Compare method to determine whether two strings are the same. string MyString = "Hello World!"; Console.WriteLine(String.Compare(MyString, "Hello World!"));
The preceding example displays 0 to the console.
11
12
Module 7: Strings, Arrays, and Collections
Trim and Pad Topic Objective
To explain how to use methods of the System.String class to trim and pad strings.
Lead-in
You can remove or extend the spaces around strings by using methods of the System.String class.
!
Trim Methods Remove Spaces
string string MyString MyString == "" Big Big "; "; Console.WriteLine("Hello{0}World!", Console.WriteLine("Hello{0}World!", MyString MyString ); ); string TrimString = MyString.Trim(); string TrimString = MyString.Trim(); Console.WriteLine("Hello{0}World!", Console.WriteLine("Hello{0}World!", TrimString TrimString ); ); // outputs the following lines to the // outputs the following lines to the console: console: //Hello //Hello Big Big World! World! //HelloBigWorld! //HelloBigWorld! !
Pad Methods Expand a Specific Number of Characters
string string MyString MyString == "Hello "Hello World!"; World!"; Console.WriteLine(MyString.PadLeft(20, Console.WriteLine(MyString.PadLeft(20, '-')); '-')); // // outputs outputs the the following following line line to to the the console: console: //--------Hello //--------Hello World! World! to to the the console. console.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** When you want to remove or extend the spaces around strings, the System.String class provides methods for trimming and padding strings.
Trimming When you are parsing a sentence into individual words, you might have white spaces on either end of a word. You can use one of the trim methods in the System.String class to remove any number of spaces from the beginning or end of the string. The following table describes two of the available trim methods. Method Name
Use
String.Trim
Removes white spaces from the beginning and end of a string.
String.Remove
Removes a specified number of characters from a specified index position in a string.
For example, you can easily remove white spaces from both ends of a string by using the String.Trim method, as shown in the following code example. string MyString = " Big "; Console.WriteLine("Hello{0}World!", MyString ); string TrimString = MyString.Trim(); Console.WriteLine("Hello{0}World!", TrimString );
This code outputs the following lines to the console: Hello Big World! HelloBigWorld!
For a complete list of trim methods in the System.String class, see “Trimming and Removing Characters” in the .NET Framework SDK documentation.
Module 7: Strings, Arrays, and Collections
13
Padding System.String also provides methods that you can use to create a new version of an existing string that is expanded by a specific number of characters. The following table describes the available pad methods. Method Name
Use
String.PadLeft
Right aligns and pads a string, so its rightmost character is a specified distance from the beginning of the string.
String.PadRight
Left aligns and pads a string, so its rightmost character is a specified distance from the beginning of the string.
For example, the String.PadLeft method creates a new string that moves an existing string to the right, so its last character is a specified number of spaces from the first index of the string. White spaces are inserted if you do not use an override that allows you to specify your own custom padding character. The following code example uses the PadLeft method to create a new string with a total length of 20 spaces. string MyString = "Hello World!"; Console.WriteLine(MyString.PadLeft(20, '-'));
This example displays the following text to the console. --------Hello World!
14
Module 7: Strings, Arrays, and Collections
Split and Join Topic Objective
To explain how to use the Split and Join methods to break up and concatenate strings.
!
#
Lead-in
The System.String class provides the Split method to break up strings and the Join method to concatenate strings.
Split Method Is Used to Break Up a String Into an Array of Substrings
#
String is broken at positions indicated by the specified separator characters parameter If the separator parameter is null, the whitespace characters are assumed to be the separator
string string Line Line == "Hello "Hello World"; World"; string[] Words string[] Words == Line.Split(null); Line.Split(null); // // Words[0] Words[0] == "Hello" "Hello" and and Words[1] Words[1] == "World" "World" !
Join Method Is Used to Concatenate Strings #
A specified separator string is placed between each element of a string array
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The System.String class provides the Split method to break up strings and the Join method to concatenate strings.
The Split Method For Your Information
You should cover the String.Split method because it is used in the lab.
You use the Split method to break up a string instance into an array of substrings at the positions that are specified by separator characters. If separator characters are omitted, that is to say, if the parameter is null, the whitespace characters are assumed to be the separator. If the separator is a zero-length string, a single-element array that contains the entire expression string is returned. The following example shows how to break up a string into a string array of words: string Line = "Hello World"; string[] Words = Line.Split(null); // Words[0] = "Hello" and Words[1] = "World"
The Join Method You use the Join method to concatenate a specified separator string between each element of a specified String array, which yields a single concatenated string. If the separator is omitted, that is to say null, the space character (" ") is used. If the separator is a zero-length string (""), all of the items in the list are concatenated with no delimiters.
Module 7: Strings, Arrays, and Collections
15
StringBuilder Topic Objective
To explain how to use the StringBuilder method to modify a string without creating a new object.
Lead-in
When you want to perform repeated modifications to a string, use the System.Text.StringBuilder class to modify a string without creating a new object.
!
The String Object is Immutable
!
System.Text.StringBuilder – Allows You to Modify a String Without Creating a New Object
StringBuilder StringBuilder MyStringBuilder MyStringBuilder == new new StringBuilder("Hello"); StringBuilder("Hello"); !
You Can Specify the Maximum Number of Characters
// // MyStringBuilder MyStringBuilder can can hold hold aa maximum maximum of of 25 25 characters characters StringBuilder StringBuilder MyStringBuilder MyStringBuilder == new new StringBuilder("Hello StringBuilder("Hello World!", World!", 25); 25); !
Methods Include: #
Append, AppendFormat, Insert, Remove, and Replace
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The String object is immutable. Therefore, every time you use one of the methods in the System.String class, you create a new string object. When you want to perform repeated modifications to a string, the overhead that is associated with creating a new String object can be costly. As an alternative, you can use the System.Text.StringBuilder class to modify a string without creating a new object.
Creating a StringBuilder Object You can create a new instance of the StringBuilder object by initializing your variable with one of the overloaded constructor methods, as shown in the following code example: StringBuilder MyStringBuilder = new StringBuilder("Hello");
Although the StringBuilder object is a dynamic object that allows you to expand the number of characters in the string that it encapsulates, you can specify a value for the maximum number of characters that it can hold. This value is called the capacity of the object and must not be confused with the length of the string that the current StringBuilder object holds. Any attempt to expand the StringBuilder class beyond the maximum range causes an ArgumentOutOfRangeException to be thrown. The following code example specifies that the MyStringBuilder object can be expanded to a maximum of 25 spaces. StringBuilder MyStringBuilder = new StringBuilder("Hello World!", 25);
16
Module 7: Strings, Arrays, and Collections
StringBuilder Methods The following table describes the methods that you can use to modify the contents of the StringBuilder object. Method Name
Use
StringBuilder.Append
Appends information to the end of the current StringBuilder object.
StringBuilder.AppendFormat
Replaces zero or more format specifications with the appropriately formatted value of an object.
StringBuilder.Insert
Inserts a string or object into the specified index of the current StringBuilder object.
StringBuilder.Remove
Removes a specified number of indexes from the current StringBuilder object.
StringBuilder.Replace
Replaces a specified index or character with the passed character.
Module 7: Strings, Arrays, and Collections
17
C# Specifics Topic Objective
To explain what the string type is in the .NET Framework and to describe the functions of the +, [ ], and != operators, and @quoting.
Lead-in
The string type represents a string of Unicode characters; string is an alias for System.String in the .NET Framework.
!
C# string Type Is a String of Unicode Characters # #
#
Alias for System.String Equality operators (== and !=) compare the values of string objects, not references The + operator concatenates strings
string string aa == "\u0068ello "\u0068ello "; "; string string bb == "world"; "world"; Console.WriteLine( Console.WriteLine( aa ++ bb == == "hello "hello world" world" );//True );//True #
The [ ] operator accesses individual characters of a string
char char xx == "test"[2]; "test"[2]; // // xx == 's'; 's'; #
With @-quoting, escape sequences are not processed
@"c:\Docs\Source\a.txt" @"c:\Docs\Source\a.txt" // // rather rather than than "c:\\Docs\\Source\\a.txt" "c:\\Docs\\Source\\a.txt"
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The string type represents a string of Unicode characters; string is an alias for System.String in the .NET Framework. Although string is a reference type, the equality (==) operator and the inequality (!=) operator are defined to compare the values of string objects, not the references. Comparing the values of string objects makes testing for string equality more intuitive, as in the following example: string a = "hello"; string b = "hello"; Console.WriteLine( a == b );
// output: True -- same value
The + operator concatenates strings, as in the following example: string a = "good " + "morning";
The [ ] operator accesses individual characters of a string, as in the following example: char x = "test"[2];
// x = 's';
String literals are of type string and can be written in two forms: quoted and @-quoted. Quoted string literals are enclosed in quotation marks ("), as in the following example: "good morning"
// a string literal
Quoted string literals can also contain any character literal, including escape sequences, as in the following example: string a = "\\\u0066\n";
// backslash, letter f, new line
18
Module 7: Strings, Arrays, and Collections
@-quoted string literals start with @ and are enclosed in quotation marks, as in the following example: @"good morning"
// a string literal
The advantage of using @-quoted string literals is that escape sequences are not processed. This makes it easy to write a fully qualified file name, as in the following example: @"c:\Docs\Source\a.txt" // rather than "c:\\Docs\\Source\\a.txt"
To include a quoted phrase in an @-quoted string, use two pairs of double quotation marks, as in the following example: @"""Ahoy!"" cried the captain." // "Ahoy!" cried the captain.
The following code example uses the C# features that are discussed in this topic: using System; class test { public static void Main( String[] args ) { string a = "\u0068ello "; string b = "world"; Console.WriteLine( a + b ); Console.WriteLine( a + b == "hello world" ); } }
The preceding code example displays the following output to the console: hello world True
Module 7: Strings, Arrays, and Collections
19
Regular Expressions Topic Objective
To briefly describe how regular expressions can be used in the .NET Framework.
!
Regular Expressions – Powerful Text Processing
!
Pattern-Matching Notation Allows You to:
Lead-in
Regular expressions allow you to quickly parse large amounts of text in order to find specific character patterns; to extract, edit, replace, or delete text substrings; and to add the extracted strings to a collection to generate a report.
!
#
Find specific character patterns
#
Extract, edit, replace, or delete text substrings
#
Add the extracted strings to a collection to generate a report
Designed to be Compatible With Perl 5
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This topic provides a brief summary of regular expressions. Do not spend much time on this slide, but encourage students to refer to the .NET Framework SDK, especially for details about using the regular expression classes.
Regular expressions provide a powerful, flexible, and efficient method to process text. The extensive pattern-matching notation of regular expressions allows you to quickly parse large amounts of text to find specific character patterns; to extract, edit, replace, or delete text substrings; and to add the extracted strings to a collection in order to generate a report. For many applications that deal with strings, such as HTML processing, log file parsing, and HTTP header parsing, regular expressions are an essential tool. The .NET Framework regular expressions incorporate the most popular features of other regular expression implementations, such as those used in Perl and awk. Designed to be compatible with Perl 5 regular expressions, .NET Framework regular expressions include features that are not yet available in other implementations, such as right-to-left matching and dynamic compilation. The .NET Framework regular expression classes are part of the .NET Framework class library and can be used with any language or tool that targets the common language runtime, including ASP.NET and Microsoft Visual Studio® .NET. A detailed explanation of how to use the regular expression classes is beyond the scope of this course. For more information about using regular expression classes, see the .NET Framework SDK documentation.
20
Module 7: Strings, Arrays, and Collections
Terminology – Collections Topic Objective
To define the term collection as it is used in this module and to identify where collections are found in the .NET Framework.
Lead-in
In this module, the term collection is used in its broader sense: to describe a group of items.
!
In This Module, the Term Collection Is Used in Its Broader Sense to Refer to a Group of Items
!
In the .NET Framework, Collections Are Found in the Namespaces #
System.Array
#
System.Collections
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Do not spend much time on this slide. The objective of this slide is to define the term collection clearly, before discussing the collections that are found in the .NET Framework.
In this module, the term collection is used in its broader sense to refer to a group of items. In the .NET Framework, examples of collections are found in the namespaces System.Array, and System.Collections.
Module 7: Strings, Arrays, and Collections
21
" .NET Framework Arrays Topic Objective
To introduce the topics in the section.
Lead-in
The System.Array class is the base class of all array types and contains methods for creating, manipulating, searching, and sorting arrays.
!
System.Array
!
C# Specifics
!
Iterating Over
!
Comparing
!
Sorting
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The System.Array class is the base class of all array types and contains methods for creating, manipulating, searching, and sorting arrays. The Array class is not part of the Collections namespace. However, it is still a collection because it is based on the IList interface. In an array style of collection, an item in the collection is referred to by the term element. Specific elements are identified by their array index. The low bound or lower bound of an Array is the index of its first element. To understand the functionality of the various collection classes, you must understand their key interfaces. In this section, you will learn about the interfaces that are used by the methods of System.Array.
22
Module 7: Strings, Arrays, and Collections
System.Array Topic Objective
To explain how array types are used and defined.
!
System.Array Is the Base Class of All Array Types
Lead-in
!
Arrays Implement the Following Interfaces
The System.Array class is the base class of all array types and contains methods for creating, manipulating, searching, and sorting arrays.
#
!
System.Array Has Methods For #
!
ICloneable, IList, ICollection, and IEnumerable Creating, manipulating, searching, and sorting
Null, Empty String, and Empty (0 item) Arrays Should Be Treated the Same #
Therefore, return an Empty array, instead of a null reference
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The System.Array class is the base class of all array types and contains methods for creating, manipulating, searching, and sorting arrays. Arrays are always allocated on the garbage-collected heap. Arrays can be single dimensional or multidimensional. You can also create arrays of arrays, called jagged arrays. For optimum performance, it is highly recommended that each rank of an array be zero-based. Additionally, each rank of an array must be zero-based when passing arrays between programming languages.
Defining an Array Type You define an array type by specifying the element type of the array, the rank, or number of dimensions, of the array, and the upper and lower bounds of each dimension of the array. All of these details are included in any signature of an array type. The runtime automatically creates exact array types as they are required. No separate definition of the array type is needed. Arrays of a particular type can only hold elements of that type. If you need to manipulate a set of unlike objects or value types, consider using one of the collection types that are defined in the System.Collections namespace.
Module 7: Strings, Arrays, and Collections
23
Methods and Properties of System.Array Some of the methods and properties for the System.Array class are described in this topic. Many of these methods are used to implement the class’s interfaces. System.Array implements the interfaces that are described in the following table. Interface
Use
ICloneable
Supports cloning, which creates a new instance of a class with the same value as an existing instance.
IList
Represents a collection of objects that can be individually indexed.
ICollection
Defines size, enumerators, and synchronization methods for all collections.
IEnumerable
Exposes the enumerator, which supports a simple iteration over a collection.
The interfaces that are listed in the preceding table are supported not only by arrays but also by many of the System.Collections classes. Subsequent topics in this module discuss specific interfaces and the classes that implement them. The following tables describe some of the public members that are available through the System.Array class. Static Method
Use
BinarySearch
Overloaded. Searches a one-dimensional sorted Array for a value, using a binary search algorithm.
CreateInstance
Overloaded. Initializes a new instance of the Array class.
Sort
Overloaded. Sorts the elements in one-dimensional Array objects.
Property
Use
IsFixedSize
Gets a value that indicates whether the Array has a fixed size.
IsReadOnly
Gets a value that indicates whether the Array is read-only.
Length
Gets the total number of elements in all of the dimensions of the Array.
Rank
Gets the rank (number of dimensions) of the Array.
Note The IsFixedSize and IsReadOnly properties are always false unless they are overridden by a derived class.
24
Module 7: Strings, Arrays, and Collections
The following table describes some of the public instance methods that are available through the System.Array class. Instance Method
Use
Clone
Creates a shallow copy of the Array.
GetEnumerator
Returns an IEnumerator for the Array.
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.
GetUpperBound
Gets the upper bound of the specified dimension in the Array.
GetValue
Overloaded. Gets the values of the Array elements at the specified indexes.
SetValue
Overloaded. Sets the specified Array elements to the specified value.
For complete lists of public members of the System.Array class, see “Array Members” in the .NET Framework SDK documentation.
Empty Arrays Nulls should only be returned by reference properties that refer to another object or component. String and Array properties should never return null, because a programmer typically does not expect null in this context. For example, a programmer typically would assume that the following code works: public void DoSomething(…) { string[] sa = SomeOtherFunc(); // The following line assumes sa is never null if (sa.Length > 0) { // do something else } }
Generally, null, empty string, and empty (0 item) arrays should be treated in the same way. Therefore, return an Empty array, instead of a null reference.
Module 7: Strings, Arrays, and Collections
25
C# Specifics Topic Objective
To explain features that are unique to C# arrays.
Lead-in
C# arrays are similar to arrays in most other popular languages, but you should be aware of the differences that are presented in this topic.
!
C# Array Indexes Start at Zero
!
Declaring an Array – Size Is Not Part of Its Type
int[] int[] numbers; numbers; // // declare declare numbers numbers as as // an int array // an int array of of any any size size !
Creating an Array
int[] int[] numbers numbers == new new int[5]; int[5]; // // declare declare and and create create !
Initializing an Array
int[] int[] numbers numbers == new new int[5] int[5] {1, {1, 2, 2, 3, 3, 4, 4, 5}; 5}; !
Using System.Array Members
int[] int[] numbers numbers == {1, {1, int LengthOfNumbers int LengthOfNumbers
2, 2, 3, 3, 4, 4, 5}; 5}; == numbers.Length; numbers.Length;
*****************************ILLEGAL FOR NON-TRAINER USE****************************** C# arrays are zero-indexed. That means the array indexes start at zero. Arrays in C# and arrays in most other popular languages work similarly, but there are a few differences that you should be aware of. When declaring an array, the brackets ([]) must come after the type, and not after the identifier. Placing the brackets after the identifier is not legal syntax in C#, as shown in the following example: int[] table; // not int table[];
In addition, and unlike the C language, the size of the array is not part of its type. This allows you to declare and assign to an array any array of int objects, regardless of the array’s length, as in the following examples. int[] numbers; // declare numbers as an int array of any size numbers = new int[10];
// numbers is a 10-element array
numbers = new int[20];
// now it's a 20-element array
26
Module 7: Strings, Arrays, and Collections
Declaring an Array C# supports single-dimensional arrays and multidimensional arrays, which are also know as rectangular arrays, and jagged arrays. The following code examples show how to declare each of these arrays: int[] numbers; // single-dimensional array string[,] names; // multidimensional array byte[][] scores; //Array-of-arrays
Instantiating an Array Declaring arrays, as shown in the preceding examples, does not actually create the arrays. As discussed later in this topic, arrays in C# are objects and must be instantiated. The following code examples show the syntax to create an array for each of the arrays already discussed: int[] numbers = new int[5]; // Single-dimensional array string[,] names = new string[5,4]; // Multi-dimensional array byte[][] scores = new byte[5][]; // Array-of-arrays for (int x = 0; x < scores.Length; x++) { scores[x] = new byte[4]; }
Module 7: Strings, Arrays, and Collections
The following example shows a complete C# program that declares and instantiates arrays as discussed in this topic. using System; class DeclareArraysSample { public static void Main() { // Single-dimensional array int[] numbers = new int[5]; // Multidimensional array string[,] names = new string[5,4]; // Array-of-arrays (jagged array) byte[][] scores = new byte[5][]; // Create the jagged array for (int i = 0; i < scores.Length; i++) { scores[i] = new byte[i+3]; } // Print length of each row for (int i = 0; i < scores.Length; i++) { Console.WriteLine("Length of row {0} is {1}", i, scores[i].Length); } } }
The preceding program displays the following output: Length Length Length Length Length
of of of of of
row row row row row
0 1 2 3 4
is is is is is
3 4 5 6 7
Initializing Arrays C# provides simple and straightforward ways to initialize arrays when they are declared, by enclosing the initial values in curly braces ( {} ). Important If an array is not initialized when it is declared, array members are automatically initialized to the default initial value for the array type.
27
28
Module 7: Strings, Arrays, and Collections
Single-Dimensional Array The following examples show different ways to initialize single-dimensional arrays: int[] numbers = new int[5] {1, 2, 3, 4, 5}; string[] names = new string[3] {"Matt", "Joanne", "Robert"};
You can omit the size of the array, as in the following examples: int[] numbers = new int[] {1, 2, 3, 4, 5}; string[] names = new string[] {"Matt", "Joanne", "Robert"};
If an initializer is provided, you can also omit the new statement, as in the following examples: int[] numbers = {1, 2, 3, 4, 5}; string[] names = {"Matt", "Joanne", "Robert"};
Multidimensional Array The following examples show different ways to initialize multidimensional arrays: int[,] numbers = new int[3, 2] { {1, 2}, {3, 4}, {5, 6} }; string[,] siblings = new string[2, 2] { {"Mike","Amy"}, {"Mary","Albert"} };
You can omit the size of the array, as in the following examples: int[,] numbers = new int[,] { {1, 2}, {3, 4}, {5, 6} }; string[,] siblings = new string[,] { {"Mike","Amy"}, {"Mary","Ray"} };
If an initializer is provided, you can also omit the new statement, as in the following examples: int[,] numbers = { {1, 2}, {3, 4}, {5, 6} }; string[,] siblings = { {"Mike", "Amy"}, {"Mary", "Albert"} };
Module 7: Strings, Arrays, and Collections
29
Jagged Array (Array-of-Arrays) The following examples show different ways to initialize jagged arrays. You can initialize jagged arrays by using the following style: int[][] numbers = new int[2][] { new int[] {2,3,4}, new int[] {5,6,7,8,9} };
You can omit the size of the first array, as in the following example: int[][] numbers = new int[][] { new int[] {2,3,4}, new int[] {5,6,7,8,9} };
Accessing Array Members Accessing array members in C# is straightforward and similar to how you access array members in C and C++. For example, the following code creates an array called numbers and then assigns 5 to the fifth element of the array: int[] numbers = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; numbers[4] = 5;
The following code example declares a multidimensional array and assigns 5 to the member located at [1, 1]: int[,] numbers = { {1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10} }; numbers[1, 1] = 5;
The following code example shows how to access a member of a jagged array: int[][] numbers { new int[] {1, new int[] {3, }; numbers[1][1] =
= new int[][] 2}, 4} 5;
Arrays Are Objects Arrays in C# are actually objects. System.Array is the abstract base type of all array types. Therefore, you can use the properties and other class members of System.Array. For example, you can use the Length property to get the length of an array. The following code example assigns the length of the numbers array, which is 5, to a variable called LengthOfNumbers: int[] numbers = {1, 2, 3, 4, 5}; int LengthOfNumbers = numbers.Length;
The System.Array class provides many other useful methods and properties, such as methods for sorting, searching, and copying arrays.
30
Module 7: Strings, Arrays, and Collections
Iterating Over Topic Objective
To explain the role of the IEnumerable and IEnumerator interfaces in collections.
Lead-in
A .NET Framework interface provides a way to group a set of related members that can be used to perform a particular action.
!
System.Array and System.Collections Classes Implement IEnumerable Interface and its GetEnumerator Method
!
Enumerator Classes Implement IEnumerator #
Members: MoveNext, Reset, and Current
int[] int[] numbers numbers == new new int[5] int[5] {1, {1, 2, 2, 3, 3, 4, 4, 5}; 5}; IEnumerator e = numbers.GetEnumerator(); IEnumerator e = numbers.GetEnumerator(); while while (e.MoveNext()) (e.MoveNext()) {{ Console.WriteLine("Number: Console.WriteLine("Number: {0}", {0}", (int)e.Current); (int)e.Current); }} // // alternatively alternatively foreach foreach (int (int ii in in numbers) numbers) {{ Console.WriteLine("Number: Console.WriteLine("Number: {0}", {0}", i); i); }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Emphasize the difference between enumeration and an enumerator.
To understand the functionality of the various collection classes, you must understand the key interfaces of the collection classes. A .NET Framework interface provides a way to group a set of related members that can be used to perform a particular action. For example, a client of an object of any class that implements the IEnumerable interface can obtain an enumerator object for that class. The System.Array class and all of the System.Collections classes support the IEnumerable interface. Note It is easy to confuse the term enumerator with enumeration. However, enumerator and enumeration are very different concepts. Enumeration, an enum type in C#, is a distinct type with named constants, as in the following example: enum Color {Red,Green,Blue}
Support for IEnumerable and the GetEnumerator Method These classes inherit from IEnumerable and implement its single method GetEnumerator. The GetEnumerator method returns an enumerator that implements the IEnumerator interface. Enumerators are intended to be used only to read data in the collection. You cannot use enumerators to modify the underlying collection. The enumerator is required to be safe. In other words, enumerators must have a fixed view of the items in a collection that remain the same, even if the collection is modified. For example, if you call GetEnumerator on a collection at the point in time when the collection contains the elements 1, 2, and 3, the enumerator object that is returned by this method must always produce 1, 2, and 3 when it is iterated over, even if the collection is later changed.
Module 7: Strings, Arrays, and Collections
31
Using Enumerators with Collections When working with collections, you typically implement an enumerator in one of the following ways: !
The enumerator makes a copy of all of the items in the collection. Making a copy of a large collection guarantees safety but imposes a severe penalty in performance and memory utilization for large collections and therefore is usually not done.
!
The enumerator has a reference to the collection. If the enumerator implementation uses a reference to the collection, the copy penalty is avoided. However, safety must be guaranteed by another means. If the collection is static or unchanging, you need take no additional action to ensure safety. The .NET Framework collections are typically not static. Instead, they implement a versioning mechanism. Every time a .NET Framework collection changes, the collection’s version number is incremented. An enumerator that detects that the collection’s version number has changed after the enumerator was created throws an InvalidOperationException that should be caught and handled by the enumerator’s client.
The IEnumerator interface has the Current public instance property. The Current property gets the current element in the collection. An enumerator maintains a reference to the item in the collection that is currently being enumerated. The enumerator is in an invalid state if it is positioned before the first element in the collection or after the last element in the collection. When the enumerator is in an invalid state, calling Current throws an exception. The IEnumerator interface also requires the following public instance methods. Method
Use
MoveNext
Advances the enumerator to the next element of the collection.
Reset
Sets the enumerator to its initial position, which is before the first element in the collection.
Initially, the enumerator is positioned before the first element in the collection. Reset also brings the enumerator back to this position. Therefore, after an enumerator is created or after a Reset, you must call MoveNext to advance the enumerator to the first element of the collection before reading the value of Current. Current returns the same object until MoveNext or Reset is called. After the end of the collection is passed, the enumerator returns to an invalid state. At this time, calling MoveNext returns false. Calling Current throws an exception if the last call to MoveNext returned false.
32
Module 7: Strings, Arrays, and Collections
The following code example shows how to iterate over a collection. In this example, the collection is an instance of System.Array. int[] numbers = new int[5] {1, 2, 3, 4, 5}; IEnumerator e = numbers.GetEnumerator(); while (e.MoveNext()) { Console.WriteLine("Number: {0}", (int)e.Current); }
This code outputs: Number: Number: Number: Number: Number:
1 2 3 4 5
Using foreach to Iterate Through an Array C# provides the foreach statement, which is a less verbose way to iterate through the elements of an array. For example, the following code example creates an array called numbers and iterates through it with the foreach statement: int[] numbers = {4, 5, 6, 1, 2, 3, -2, -1, 0}; foreach (int i in numbers) { System.Console.WriteLine("Number: {0}", i); }
Module 7: Strings, Arrays, and Collections
33
Comparing Topic Objective
To explain how the IComparer and IComparable interfaces are used to sort and order a collection’s items.
!
To Sort and Search, Collections Must Be Able to Compare Items
!
IComparer’s Compare Method Compares Two Objects of Any Type
int int Compare( Compare( object object x, x, object object yy ); ); #
Lead-in
!
To sort a collection, you must be able to compare and order the items of the collection.
Comparer class is the default implementation of IComparer Its Compare method uses IComparable.CompareTo
IComparable’s CompareTo Method Compares the Current Instance to an Object of the Same Type
int int CompareTo( CompareTo( object object anObject anObject ); ); ! CompareTo Returns
Value Less than zero
Meaning Instance is less than object
Zero Greater than zero
Instance is equal to object Instance is greater than object
*****************************ILLEGAL FOR NON-TRAINER USE****************************** To sort a collection, you must be able to compare and order the items of the collection. The IComparer and IComparable interfaces are used to sort and order a collection’s items. The IComparer interface’s Compare method compares two objects of any type and returns a value that indicates whether one object is less than, equal to, or greater than the other. The Compare method provides the sort order of a collection and is also used in conjunction with the Array.BinarySearch method.
Default Implementation of IComparer The Comparer class provides the default implementation of the IComparer interface. The Comparer class’s Compare(object a, object b) method is implemented as follows: !
If a implements IComparable, then a.CompareTo(b) is returned.
!
Otherwise, if b implements IComparable, then b.CompareTo(a) is returned.
The IComparable interface has a single CompareTo method that compares the current instance with another object of the same type.
34
Module 7: Strings, Arrays, and Collections
Return Values of CompareTo The value returned by the CompareTo method is a 32-bit signed integer that indicates the relative order of the instance and the object that is passed as a parameter, as in the following example: int CompareTo(object anObject);
The following table describes the possible meanings of the return value. Value
Meaning
Less than zero
This instance is less than anObject.
Zero
This instance is equal to anObject.
Greater than zero
This instance is greater than anObject.
By definition, any object compares greater than a null reference, and two null references compare equal to each other.
Issues with Using IComparable.CompareTo The parameter, anObject, must be the same type as the class or value type that implements this interface. Otherwise, ArgumentException is thrown. The default comparison procedures use the Thread.CurrentCulture of the current thread unless it is otherwise specified. String comparisons may have different results depending on the culture. To perform case-insensitive comparisons on strings, you can use the CaseInsensitiveComparer class’s implementation of the IComparer interface.
Module 7: Strings, Arrays, and Collections
35
Sorting Topic Objective
To explain how to compare and order the items of a collection by using the IComparer and IComparable interfaces. Knowledge of this topic is required for the lab.
Lead-in
Some collections, such as Array, sort their items when their Sort method is called.
!
Sort Method Using Element’s IComparable.CompareTo Array.Sort( Array.Sort( anArray anArray ); );
!
IComparable.CompareTo Design Pattern
public public int int CompareTo(Object CompareTo(Object anObject) anObject) {{ if if (( anObject anObject == == null) null) return return 1; 1; if if (( !(anObject !(anObject is is ) ) )) {{ throw throw new new ArgumentException(); ArgumentException(); }} // Do comparison // Do comparison and and return return aa // negative integer if // negative integer if instance instance << anObject anObject // 0 if instance == anObject // 0 if instance == anObject // // positive positive integer integer if if instance instance >> anObject anObject }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Some collections, such as Array, sort their items when their Sort method is called. Overloaded versions of the Sort method allow you to supply an IComparer implementation in the method call that is used to perform the ordering of the elements. These overloaded versions of the Sort method provide you with the flexibility to resort the same collection of items by using different IComparer implementations. If you use the array’s public static void Sort(Array) method, the default Comparer class implementation of IComparer is used, and the array’s elements are sorted by using the elements’ implementation of the CompareTo method of the IComparable interface. Other collection classes, such as SortedList, have constructors whose parameters determine the sort order. These classes are less flexible than classes that implement a sort method because after they are constructed their sort ordering cannot be changed.
36
Module 7: Strings, Arrays, and Collections
Demonstration: Sorting and Enumerating an Array Topic Objective
To demonstrate how to sort and enumerate an array.
Lead-in
In this demonstration, we will sort an array by using the CompareTo method when the value type or class of the items in the collection implements the IComparable interface.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** As stated in Sorting in this module, you can sort an array by using the CompareTo method when the value type or class of the items in the collection implements the IComparable interface. For Your Information
You should carefully cover the CompareTo method because it is used in the lab.
In this demonstration, an Employee class contains an employee’s name, level number, and hiring date. To sort an array of Employees based on increasing level numbers where equal level numbers are then ordered by increasing hiring date, implement the following: using System; namespace ArraySorting { public class Employee : IComparable { public string name; public int level; public DateTime hiringDate; public Employee( string name,int level,DateTime hiringDate) { this.name = name; this.level=level; this.hiringDate=hiringDate; } public int CompareTo(Object anObject) { if (anObject == null) return 1; if ( !(anObject is Employee) ) { throw new ArgumentException(); }
(Code continued on the following page.)
Module 7: Strings, Arrays, and Collections Employee anEmployee = (Employee)anObject; if ( level < anEmployee.level ) return -1; else { if ( level == anEmployee.level ) { if (hiringDate < anEmployee.hiringDate) return -1; else { if ( hiringDate == anEmployee.hiringDate) return 0; else return 1; } } else return 1; } } } public class ArraySort
{
public static void Main()
{
// Create and initialize a new Array instance. Employee[] myEmployees = new Employee[10]; myEmployees[0] = new Employee( "a",2,new DateTime(1990,1,1)); myEmployees[1] = new Employee( "b",2,new DateTime(2000,1,1)); myEmployees[2] = new Employee( "c",2,new DateTime(1990,1,1)); myEmployees[3] = new Employee( "d",4,new DateTime(2000,1,1)); myEmployees[4] = new Employee( "e",4,new DateTime(1990,1,1)); myEmployees[5] = new Employee( "f",4,new DateTime(2000,1,1)); myEmployees[6] = new Employee( "g",1,new DateTime(1990,2,5)); myEmployees[7] = new Employee( "h",1,new DateTime(2000,1,1)); myEmployees[8] = new Employee( "i",1,new DateTime(1990,1,1)); myEmployees[9] = new Employee( "j",0,new DateTime(2001,1,1)); // Display the values of the Array. Console.WriteLine( "The Array instance initially contains values:" ); PrintIndexAndValues( myEmployees ); // Sort the values of the Array. Array.Sort( myEmployees );
(Code continued the following page.)
37
38
Module 7: Strings, Arrays, and Collections // Display the values of the Array. Console.WriteLine( "After sorting:" ); PrintIndexAndValues( myEmployees ); } public static void PrintIndexAndValues( Array myEmployees ) { foreach ( Employee e in myEmployees ) { Console.WriteLine( "name: {0} \tlevel: {1} \tdate:{2:d}", e.name, e.level, e.hiringDate); } } } }
The preceding code displays the following output to the console: The Array instance initially contains values: name: a level: 2 date:1/1/1990 name: b level: 2 date:1/1/2000 name: c level: 2 date:1/1/1990 name: d level: 4 date:1/1/2000 name: e level: 4 date:1/1/1990 name: f level: 4 date:1/1/2000 name: g level: 1 date:2/5/1990 name: h level: 1 date:1/1/2000 name: i level: 1 date:1/1/1990 name: j level: 0 date:1/1/2001 After sorting: name: j level: 0 date:1/1/2001 name: i level: 1 date:1/1/1990 name: g level: 1 date:2/5/1990 name: h level: 1 date:1/1/2000 name: c level: 2 date:1/1/1990 name: a level: 2 date:1/1/1990 name: b level: 2 date:1/1/2000 name: e level: 4 date:1/1/1990 name: d level: 4 date:1/1/2000 name: f level: 4 date:1/1/2000
Module 7: Strings, Arrays, and Collections
39
" .NET Framework Collections Topic Objective
To introduce the topics in the section.
Lead-in
The System.Collections namespace contains interfaces and classes that define various collections of objects, such as lists, queues, arrays, hashtables, and dictionaries.
!
Examples of System.Collections Classes
!
Lists
!
Dictionaries
!
SortedList
!
Collection Usage Guidelines
!
Type Safety and Performance
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The System.Collections namespace contains interfaces and classes that define various collections of objects, such as lists, queues, arrays, hashtables, and dictionaries.
40
Module 7: Strings, Arrays, and Collections
Examples of System.Collections Classes Topic Objective
!
To show some of the collection classes in the System.Collections namespace.
ArrayList #
!
DictionaryBase #
Lead-in
Let’s look briefly at some of the collection classes in the System.Collections namespace.
!
Represents a collection of keys and values that are organized around the key’s hash code
SortedList #
!
Provides abstract base class for strongly-typed collection of associated keys and values
Hashtable #
!
Implements IList by using a dynamically-sized array
Represents the collection of keys and values, sorted by keys and accessible by key and index
BitArray, Queue, Stack, CollectionBase, ReadOnlyCollectionBase
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Do not spend a lot of time on this slide. Just introduce students to some commonly used collection classes and encourage them to refer to the .NET Framework SDK for more information.
The following table shows some of the collection classes in the System.Collections namespace. Class
Description
ArrayList
Implements the IList interface by using an array whose size is dynamically increased as required.
BitArray
Manages a compact array of bit values, which are represented as Booleans, where true indicates that the bit is on (1) and false indicates the bit is off (0).
CollectionBase
Provides the abstract base class (MustInherit in Microsoft Visual Basic®) for a strongly-typed collection.
DictionaryBase
Provides the abstract base class (MustInherit in Visual Basic) for a strongly-typed collection of associated keys and values.
Hashtable
Represents a collection of associated keys and values that are organized around the hash code of the key.
Queue
Represents a first-in, first-out collection of objects.
ReadOnlyCollectionBase
Provides the abstract base class (MustInherit in Visual Basic) for a strongly-typed read-only collection.
SortedList
Represents a collection of associated keys and values that are sorted by the keys and are accessible by key and by index.
Stack
Represents a simple last-in-first-out collection of type Object.
Module 7: Strings, Arrays, and Collections
41
Lists Topic Objective
To describe the public instance members that the IList interface implements.
!
IList – Interface for Classes That Represent an Ordered Collection of Objects That Can Be Individually Indexed
!
Some Classes That Implement IList
Lead-in
IList is an interface for classes that represent an ordered collection of objects that can be individually indexed. Array, ArrayList, StringCollection, and TreeNodeCollection are some of the classes that implement IList.
#
!
Array, ArrayList, StringCollection, and TreeNodeCollection
Methods Include: #
Add, Clear, Contains, Insert, IndexOf, Remove, and RemoveAt
*****************************ILLEGAL FOR NON-TRAINER USE****************************** IList is an interface for classes that represent an ordered collection of objects that can be individually indexed. Array, ArrayList, StringCollection, and TreeNodeCollection are some of the classes that implement IList. The following tables describe the public instance properties that the IList interface implements. Property
Use
IsFixedSize
When implemented by a class, gets a value indicating whether the IList has a fixed size.
IsReadOnly
When implemented by a class, gets a value indicating whether the IList is read-only.
Item
When implemented by a class, gets or sets the element at the specified index. In C#, this property is the indexer for the IList class.
42
Module 7: Strings, Arrays, and Collections
The following table describes the public instance methods that the IList interface defines. Method
Use
Add
When implemented by a class, adds an item to the IList.
Clear
When implemented by a class, removes all items from the IList.
Contains
When implemented by a class, determines whether the IList contains a specific value.
IndexOf
When implemented by a class, determines the index of a specific item in the IList.
Insert
When implemented by a class, inserts an item to the IList at the specified position.
Remove
When implemented by a class, removes the first occurrence of a specific object from the IList.
RemoveAt
When implemented by a class, removes the IList item at the specified index.
Module 7: Strings, Arrays, and Collections
43
Demonstration: ArrayList Topic Objective
To demonstrate how ArrayList implements the IList interface by using an array whose size is dynamically increased as required.
Lead-in
In this demonstration, ArrayList implements the IList interface by using an array whose size is dynamically increased as required.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this demonstration, ArrayList implements the IList interface by using an array whose size is dynamically increased as required. using System; using System.Collections; class listSample { public static void Main(string[] args) { ArrayList fruit = new ArrayList(); fruit.Add("Apple"); fruit.Add("Pear"); fruit.Add("Orange"); fruit.Add("Banana"); Console.WriteLine ("\nList Contains:"); foreach (string item in fruit) { Console.WriteLine(item); } Console.WriteLine ( "\nResult of Contains method for Kiwi: {0}", fruit.Contains("Kiwi")); Console.WriteLine ( "\nAdding Kiwi at Orange:"); fruit.Insert(fruit.IndexOf("Orange"),"Kiwi"); Console.WriteLine ("\nList Contains:"); foreach (string item in fruit) { Console.WriteLine(item); }
(Code continued the following page.)
44
Module 7: Strings, Arrays, and Collections Console.WriteLine ( "\nResult of Contains method for Kiwi: {0}", fruit.Contains("Kiwi")); Console.WriteLine ( "\r\nPress Return to exit."); Console.Read(); } }
The preceding code example displays the following output to the console: List Contains: Apple Pear Orange Banana Result of Contains method for Kiwi: False Adding Kiwi at Orange: List Contains: Apple Pear Kiwi Orange Banana Result of Contains method for Kiwi: True Press Return to exit.
Module 7: Strings, Arrays, and Collections
45
Dictionaries Topic Objective
To describe the IDictionary interface and the classes that it implements.
!
Lead-in
IDictionary is an Interface for Collections of Associated Keys and Values #
IDictionary is an interface for collections of associated keys and values. !
Collection Classes That Implement IDictionary Include #
!
Each association must have a unique non-null key, but the value of an association can be any object reference, including a null reference Hashtable, DictionaryBase, and SortedList
Methods Include: #
Add, Clear, Contains, GetEnumerator, and Remove
*****************************ILLEGAL FOR NON-TRAINER USE****************************** IDictionary is an interface for collections of associated keys and values. Each association must have a unique non-null key, but the value of an association can be any object reference, including a null reference. Hashtable, DictionaryBase, and SortedList are examples of collection classes that implement IDictionary. The IDictionary interface allows the contained keys and values in the items in a collection to be enumerated, but it does not imply any particular sort order. The IDictionaryEnumerator interface inherits from IEnumerator and adds members to return the object’s Key and Value fields individually or in a DictionaryEntry structure. IDictionary implementations can be divided into the following categories: !
Read-only You cannot modify a read-only IDictionary.
!
Fixed-size You cannot add or remove elements from a fixed-size IDictionary, but you can modify existing elements.
!
Variable-size You can add, remove, or modify elements in a variable-size IDictionary.
46
Module 7: Strings, Arrays, and Collections
The following table describes some of the public instance properties that the IDictionary interface implements. Property
Use
IsFixedSize
When implemented by a class, gets a value indicating whether the IDictionary has a fixed size.
IsReadOnly
When implemented by a class, gets a value indicating whether the IDictionary is read-only.
Item
When implemented by a class, gets or sets the element at the specified index. In C#, this property is the indexer for the class.
Keys
When implemented by a class, gets an ICollection containing the keys of the IDictionary.
Values
When implemented by a class, gets an ICollection containing the values in the IDictionary.
The following table describes some of the public instance methods that the IDictionary interface implements. Method
Use
Add
When implemented by a class, adds an entry with the provided key and value to the IDictionary.
Clear
When implemented by a class, removes all elements from the IDictionary.
Contains
When implemented by a class, determines whether the IDictionary contains an entry with the specified key.
GetEnumerator
When implemented by a class, returns an IDictionaryEnumerator for the IDictionary.
Remove
When implemented by a class, removes the entry with the specified key from the IDictionary.
For a complete list of the members of the IDictionary interface, see “IDictionary Members” in the .NET Framework SDK documentation.
Module 7: Strings, Arrays, and Collections
47
Demonstration: Hashtable Topic Objective
To demonstrate how to create a hash table that is used for searches.
Lead-in
The Hashtable class represents a collection of associated keys and values that are organized on the basis of the keys’ hash code.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The Hashtable class represents a collection of associated keys and values that are organized on the basis of the keys’ hash code. The objects that are used as keys in a Hashtable must implement or inherit the Object.GetHashCode and Object.Equals methods. If key equality were simply reference equality, the inherited implementation of these methods would suffice. Furthermore, these methods must produce the same results when they are called with the same parameters while the key exists in the Hashtable. Key objects must be immutable as long as they are used as keys in the Hashtable. The foreach statement of the C# language requires the type of the elements in the collection. Because each element of the Hashtable is a key-and-value pair, the element type is not the type of the key or the type of the value. Instead, the element type is DictionaryEntry, as in the following example: foreach (DictionaryEntry myEntry in myHashtable) {...}
48
Module 7: Strings, Arrays, and Collections
The code in this demonstration creates a hash table of employee numbers and names, and searches the table for an employee by number and by name. using System; using System.Collections; class HashTableSample { public static void Main(String[] args) { //create hash table of employee numbers and names Hashtable table = new Hashtable(); table.Add("0123","Jay"); table.Add("0569","Brad"); table.Add("1254","Brian"); table.Add("6839","Seth"); table.Add("3948","Rajesh"); table.Add("1930","Lakshan"); table.Add("9341","Kristian"); printTable(table); //now we'll look to see if an employee is in the table //by key Console.Write( "Search for employee by key, enter ID ==-> "); string input = Console.ReadLine(); if (table.Contains(input)) { Console.WriteLine("Found {0} in the list.",input); } else { Console.WriteLine("Employee {0} not found.",input); } //by value Console.Write( "Search for employee by value, enter name ==-> "); input = Console.ReadLine(); if (table.ContainsValue(input)) { Console.WriteLine("Found {0} in the list.",input); } else { Console.WriteLine("Employee {0} not found.",input); } printTable(table); // remove an employee by key Console.Write("Remove employee by key, enter ID ==-> "); input = Console.ReadLine(); table.Remove(input); printTable(table); Console.WriteLine ("\r\nPress Return to exit."); Console.Read(); }
(Code continued on the following page.)
Module 7: Strings, Arrays, and Collections public static void printTable(Hashtable table) { Console.WriteLine ("Current list of employees:\n"); Console.WriteLine ("ID\tName"); Console.WriteLine ("--\t----"); foreach (DictionaryEntry d in table) { Console.WriteLine ("{0}\t{1}", d.Key, d.Value); } } }
The preceding code example displays the following output or similar output to the console: Current list of employees: ID Name ----1254 Brian 6839 Seth 3948 Rajesh 1930 Lakshan 0123 Jay 0569 Brad 9341 Kristian Search for employee by key, enter ID ==-> 111 Employee 111 not found. Search for employee by value, enter name ==-> Jay Found Jay in the list. Current list of employees: ID Name ----1254 Brian 6839 Seth 3948 Rajesh 1930 Lakshan 0123 Jay 0569 Brad 9341 Kristian Remove employee by key, enter ID ==-> 9341 Current list of employees: ID -1254 6839 3948 1930 0123 0569
Name ---Brian Seth Rajesh Lakshan Jay Brad
Press Return to exit.
49
50
Module 7: Strings, Arrays, and Collections
SortedList Topic Objective
To explain how arrays are used in a SortedList and how to create, initialize, and access a SortedList. This topic is very important because its contents are required for the lab.
Lead-in
A SortedList maintains two arrays internally to store entries to the list: one array for the keys, and another array for the associated values.
!
SortedList Maintains Two Arrays for Entries #
One array for the keys and another array for the associated values
SortedList SortedList mySL mySL == new new SortedList(); SortedList(); // Add an entry with // Add an entry with aa key key == "First" "First" and and aa value value == 11 mySL.Add("First", 1); mySL.Add("First", 1); // // Increment Increment the the value value of of the the entry entry whose whose key key == "First" "First" mySL["First"] = (Int32)mySL["First"] + 1; mySL["First"] = (Int32)mySL["First"] + 1; !
Count Property – Number of Elements in the SortedList
!
Sorted Using a Specific IComparer Implementation or According to the Key's IComparable Implementation
!
Printing the Keys and Values of a SortedList
for for (( int int ii == 0; 0; ii << myList.Count; myList.Count; i++ i++ )) {{ Console.WriteLine( "\t{0}:\t{1}", Console.WriteLine( "\t{0}:\t{1}", myList.GetKey(i), myList.GetKey(i), myList.GetByIndex(i) myList.GetByIndex(i) ); ); }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** A SortedList maintains two arrays internally to store entries to the list: one array for the keys, and another array for the associated values. An entry is a key-and-value pair. A SortedList implements the IDictionary, IEnumerable, ICollection, and ICloneable interfaces. The Count property gets the number of elements that are contained in the SortedList. The Add method is used to add an entry to the SortedList. The [ ] Operator is used to modify the value of an entry with the specified key. For Your Information
You should carefully cover the SortedList class, the Add method, and the [ ] Operator because they are used in the lab.
You can sort the keys of a SortedList according to an IComparer implementation that is specified when the SortedList is instantiated or according to the IComparable implementation that is provided by the keys themselves. In either case, a SortedList does not allow duplicate keys. Operations on a SortedList tend to be slower than operations on a Hashtable because of the sorting. However, the SortedList offers more flexibility by allowing access to the values through the associated keys or through the indexes. A key cannot be a null reference, but a value can be a null reference. Indexes in the SortedList collection are zero-based.
Module 7: Strings, Arrays, and Collections
51
The following example shows how to create a SortedList, add an entry, modify an entry’s value, and print out the SortedList’s keys and values. using System; using System.Collections; public class SamplesSortedList public static void Main()
{
{
// Create and initialize a new SortedList. SortedList mySL = new SortedList(); mySL.Add("First", 1); mySL.Add("Second", 2); mySL.Add("Third", 3); // Display the properties and values of the SortedList. Console.WriteLine( "mySL" ); Console.WriteLine( " Count: {0}", mySL.Count ); Console.WriteLine( " Capacity: {0}", mySL.Capacity ); Console.WriteLine( " Keys and Values:" ); PrintKeysAndValues( mySL ); // increment the value of the entry whose key is "Third" mySL["Third"] = (Int32)mySL["Third"] + 1; PrintKeysAndValues( mySL ); }
public static void PrintKeysAndValues( SortedList myList ){ Console.WriteLine( "\t-KEY-\t-VALUE-" ); for ( int i = 0; i < myList.Count; i++ ) { Console.WriteLine( "\t{0}:\t{1}", myList.GetKey(i), myList.GetByIndex(i) ); } Console.WriteLine(); } }
The preceding code example displays the following output to the console: mySL Count: 3 Capacity: 16 Keys and Values: -KEY-VALUEFirst: 1 Second: 2 Third: 3 -KEYFirst: Second: Third:
-VALUE1 2 4
52
Module 7: Strings, Arrays, and Collections
Collection Usage Guidelines Topic Objective
To distinguish between collections and arrays and explain when collections are used.
!
Use a Collection instead of an Array: #
Lead-in
Arrays and collections are used similarly, but they perform differently.
#
!
When Add, Remove, or other methods for manipulating the set of objects are supported. This scopes all related methods to the collection When you want to provide a read-only set of objects. System.Array objects are always writable - You can add read-only wrappers around internal arrays
Use Collections to Avoid Inefficiencies #
In the following code, each call to the myObj property creates a copy of the array. As a result, 2n+1 copies of the array are created:
for for (int (int ii == 0; 0; ii << obj.myObj.Count; obj.myObj.Count; i++) i++) DoSomething(obj.myObj[i]); DoSomething(obj.myObj[i]);
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Class library designers sometimes need to decide when to use an array and when to use a collection. Arrays and collections have similar usage models, but they differ somewhat in performance.
Collections vs. Arrays When Add, Remove, or other methods for manipulating the collection are supported, use a collection, instead of an array. Using a collection scopes all related methods to the collection. Also, use collections to add read-only wrappers around internal arrays. System.Array objects are always writable.
Array Valued Properties Use collections to avoid code inefficiencies. In the following code example, each call to the myObj property creates a copy of the array. As a result, 2n+1 copies of the array will be created in the following loop: for (int i = 0; i < obj.myObj.Count; i++) DoSomething(obj.myObj[i]);
Module 7: Strings, Arrays, and Collections
53
Choosing A Collection Class Choosing the right Collections class must be done carefully. Using the wrong collection can unnecessarily restrict how you use it. Consider the following questions: !
Do you need temporary storage? • If yes, consider Queue or Stack. • If no, consider the other collections.
!
Do you need to access the elements in a certain order, such as first-in-firstout, last-in-first-out, or randomly? • The Queue offers first-in, first-out access. • The Stack offers last-in, first-out access. • The rest of the collections offer random access.
!
Do you need to access each element by index? • ArrayList and StringCollection offer access to their elements by the zero-based index. • Hashtable, SortedList, ListDictionary, and StringDictionary offer access to their elements by the key of the element. • NameObjectCollectionBase and NameValueCollection offer access to their elements either by the zero-based index or by the key of the element.
!
Will each element contain just one value or a key-singlevalue pair or a keymultiplevalues combination? • One value: Use any of the collections based on IList. • Key-singlevalue pair: Use any of the collections based on IDictionary. • Key-multiplevalues combination: Consider using or deriving from the NameValueCollection class in the Collections.Specialized namespace.
!
Do you need to sort the elements differently from how they were entered? • Hashtable sorts the elements by the hash code of the key. • SortedList sorts the elements by the key, based on an IComparer implementation. • ArrayList provides a Sort method that takes an IComparer implementation as a parameter.
!
Do you need fast searches and retrieval of information? • ListDictionary is faster than Hashtable for small collections of ten items or less.
!
Do you need collections that accept only strings? • StringCollection (based on IList) and StringDictionary (based on IDictionary) are in the Collections.Specialized namespace.
54
Module 7: Strings, Arrays, and Collections
Type Safety and Performance Topic Objective
To discuss runtime casting for type safety and the effects of runtime casting and boxing and unboxing on performance.
Lead-in
!
System.Collections Items Require a Runtime Object Cast
!
Drawbacks of Runtime Casting
!
The System.Collections classes are generic collections, which ignore their items’ true types. Generic collections allow classes to be used for items of any type.
#
Type errors found at runtime, rather than at compile time
#
Runtime overhead of casting
#
Runtime overhead of boxing/unboxing operations for value types
Type-Specific Collection Classes Can Eliminate Runtime Casting #
#
!
System.Collections.Specialized namespace contains stringspecific collections Create your own type-specific collection class by inheriting from a System.Collections class and adding type-specific members
Ways to Reduce Boxing and Unboxing Operations of Value Types #
Encapsulate value types in a class
#
Manipulate value types through interfaces
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The System.Collections classes are generic collections, which ignore their items’ true types. Generic collections allow classes to be used for items of any type.
Drawbacks of Runtime Casting The System.Collections classes have properties and methods that manipulate the collection’s items through the item’s System.Object base type. The class’s members take and return parameters of type System.Object. For example, IEnumerator.Current returns a System.Object instance. This use of an object’s base class requires casting a runtime object to obtain the true type of the object. Casting System.Object instances at run time has two drawbacks: !
Collection operations with invalid parameter or return types are not flagged at compile time. If the cast is invalid, the problem is only detected at run time when an InvalidCastException is thrown.
!
Such casting hurts runtime performance because the .NET runtime must perform type-checking.
In addition, in the case of a collection of value types, such as an int, casting to and from System.Object results in boxing and unboxing operations that further impair performance. The ideal solution to these two drawbacks would be a language mechanism that automatically generates a type-specific version of the System.Collections classes that is similar to the C++ template class mechanism. For example, if you declared an ArrayList, this object would store int values directly, without casting and without boxing. This feature is not implemented in the first version of C#; however, it may be implemented in a later version.
Module 7: Strings, Arrays, and Collections
55
The System.Collections classes provide a simple and powerful way to implement collections. Carefully evaluate whether the use of these classes in your programs imposes a significant enough performance penalty to warrant taking the further action that is described in the following sections.
Strongly-Typed Collections One way to provide compile-time type-checking and improve the performance of collection code is to use strongly-typed collections. The System.Collections.Specialized namespace contains string-specific collection classes, as described in the following table. Class
Description
NameValueCollection
Represents a sorted collection of associated String keys and String values that can be accessed with the hash code of the key or with the index.
StringCollection
Represents a collection of strings.
StringDictionary
Implements a hash table with the key strongly-typed to be a string, rather than an object.
StringEnumerator
Supports a simple iteration over a StringCollections.
Creating Custom Collection Classes You can also create your own strongly-typed collection by creating your own custom collection class. Your class can reuse System.Collections functionality by inheriting from the appropriate System.Collections class. You create the type-specific class with methods and properties whose parameters and return types are type-specific. By using C#’s explicit interface implementation mechanism, which is discussed in Module 6, “Working with Types,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C#™ .NET), a class can implement both non-interface and interface members of the same name. Explicit interface method implementations are frequently used when you implement such interfaces as IEnumerable, IEnumerator, ICloneable, IComparable, ICollection, IList, and IDictionary. By having both sets of members, clients that expect type-specific or System.Object parameters and return values can use the custom class.
Techniques for Handling Boxing and Unboxing Instead of creating a type-specific collection class, you can remove the overhead of boxing and unboxing by using a reference type, rather than a value type, for the items in a collection. To accomplish this, you can wrap a value type in a class, as shown in Module 6, Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET). In some cases, you can reduce boxing and unboxing by using interfaces. Value types can implement interfaces, but because interfaces are referenced types, you can only have an interface reference to a boxed value type. You can define an interface for your value type that has the methods and properties that are needed to manipulate the value type. The value type instance must still be boxed when it is inserted into a generic System.Collections object, but your code will be able to manipulate the item’s value through its interface without unboxing the item.
56
Module 7: Strings, Arrays, and Collections
For example, in the case of a collection of integer values where you want to be able to add an integer to an element’s value, you can have your value type implement an interface, as in the following code: interface IAdd { void Add(int amount); }
struct IntStruct: IAdd { int number; public IntStruct(int number) { this.number = number; } public int Number { get { return(number);} } public void Add(int amount) { number += amount; } public override string ToString() { return(number.ToString()); } }
Module 7: Strings, Arrays, and Collections
57
Lab 7: Working with Strings, Enumerators, and Collections
*****************************ILLEGAL FOR NON-TRAINER USE******************************
Objectives After completing this lab, you will be able to: !
Create an application that parses and formats strings.
!
Create an application that uses SortedList collection objects.
Lab Setup Starter and solution files are associated with this lab. The starter files are in the folder \Labs\Lab07\Starter, and the solution files are in the folder \Labs\Lab07\Solution.
Scenario In this lab, you are provided with a Visual Studio .NET console application as a starting point. The application, named WordCount, parses a test string into words and then determines the number of unique words and the number of times each word occurs. The results of this analysis are formatted and displayed on the console. The application is a modified version of the .NET Framework SDK sample, Word Count.
Estimated time to complete this lab: 60 minutes
58
Module 7: Strings, Arrays, and Collections
Exercise 1 Sorting Words Alphabetically In this exercise, you will create a class that breaks up the test string into words and then stores each word and the number of its occurrences in a SortedList. By default, the sort order will be based on the alphabetical ordering of the word keys.
! Modify the WordCount class to sort words alphabetically 1. Open the WordCount project in the starter directory by using Visual Studio .NET. 2. Open the WordCount.cs file. 3. In the WordCounter class, create a member named wordCounter that is an instance of the SortedList class. 4. Create a read-only property named UniqueWords that returns the number of items in wordCounter. 5. Create a public method named GetWordsAlphabeticallyEnumerator that takes no arguments and returns a wordCounter enumerator object of type IDictionaryEnumerator. 6. Create a public method named CountStats that takes two out parameters of type Int64 named numWords and numChars and returns a Boolean. 7. Implement the CountStats method to break up the test string, create the alphabetically sorted list, and return the word count statistics. a. Initialize the CountStats method’s out parameters to 0. b. Use the String.Split method to break up testString into individual words that are stored in an array of type string named Words. c. Assign to the out parameter numWords the number of words that are obtained in step 7.b. d. For each non-empty string in the array Words: i. Add the number of characters in the word string to numChars. ii. If wordCounter does not already contain the string, add a new entry whose key is the string and whose value is 1. The value represents the number of occurrences of the word. Otherwise, increment by one the value of the existing entry. Tip For information about and examples of the IDictionary methods named Add and Contains, see Dictionaries and Demonstration: Hashtable in this module. For an example of how to modify the value of an entry by using the [] Operator, see SortedList in this module. iii. Return true.
Module 7: Strings, Arrays, and Collections
59
! Modify the Application class 1. In the Application class's Main method, create an instance of WordCounter named wc. 2. Create two variables of type Int64 named NumWords and NumChars. 3. Call the wc object’s CountStats method to assign the word and character counts to NumWord and NumChars respectively. 4. Output to the console the test string WordCounter.testString. 5. Output to the console a two-column header labeled Words and Chars. Use a tab to separate the columns. 6. Output to the console the number of words, and format this output to occupy five characters, followed by a tab and the number of characters.
! Test the WordCount application • Build and run the application. You should see output that is similar to the following: For string Hello world hello here Words 11
Chars 41
i am where are you hello you
60
Module 7: Strings, Arrays, and Collections
Exercise 2 Sorting Words by Number of Occurrences In this exercise, you will create a nested class that implements the IComparable interface and whose CompareTo method will result in a SortedList that is ordered on the basis of the number of occurrences of a word.
! Modify the WordCount class to sort words by occurrences 1. Add a nested class named WordOccurrence that inherits from IComparable. 2. Add a private member of type int named occurrences. 3. Add a private member of type String named word. 4. Add a constructor that takes two parameters. The first parameter is of type int, and the second parameter is of type String. The constructor assigns the first value to occurrences and the second value to word. 5. Add a public method CompareTo that takes an Object parameter and returns an int. The code should follow the CompareTo design pattern and return a value that results in a sort of words that is ordered by the occurrences of each word in ascending order. Less frequently occurring words should be followed by more frequently occurring words. In the case in which the number of occurrences of the instance and the Object parameter are the same, the sort should be alphabetical by using the String.Compare method. Tip For an example of implementing the CompareTo method, see Demonstration: Sorting and Enumerating an Array in this module. 6. Add two public read-only properties named Occurrences and Word that return the fields occurrences and word respectively. End the nested class definition. 7. Back in the WordCounter class itself, add a public method named GetWordsByOccurrenceEnumerator that returns an object of type IDictionaryEnumerator. Implement GetWordsByOccurrenceEnumerator as follows: a. Create a new SortedList named sl. b. Iterate through the alphabetically-sorted list named wordCounter by using an enumerator that is obtained by calling the GetWordsAlphabeticallyEnumerator method. i. For each alphabetical list entry, create a new object of type WordOccurence that is initialized with the alphabetically sorted list entry’s Value and Key. ii. Add to sl a new entry whose key field is the new WordOccurence object and whose value field is set to null. c. Return an enumerator of sl of type IDictionaryEnumerator.
Module 7: Strings, Arrays, and Collections
61
! Modify the Application class 1. Output the words to be sorted alphabetically by adding code to the Application class’s Main method to: a. Obtain an IDictionaryEnumerator object named de by calling the wc object’s GetWordsAlphabeticallyEnumerator method. b. Display a message to inform the user that the output that is generated will display word usage sorted alphabetically. Also inform the user of the number of unique words. c. Iterate over the collection and output each entry’s value and key, formatting them so that the value is displayed in the first column and the key is displayed in the second column. 2. Output the words sorted by occurrence by adding code to: a. Assign to de an IDictionaryEnumerator object by calling the wc object’s GetWordsByOccurrenceEnumerator method. b. Display a message to inform the user that the output that is generated will display word usage sorted by occurrence. Also inform the user of the number of unique words. c. Iterate over the collection. For each entry, obtain the key field’s WordCounter.WordOccurrence object and output to the console the Occurrences property of the WordCounter.WordOccurrence object in the first column and the object’s Word property in the second column.
62
Module 7: Strings, Arrays, and Collections
! Test the WordCount application • Build and run the application. You should see output that is similar to the following: For string Hello world hello here
i am where are you hello you
Words Chars 11 41 Word usage sorted alphabetically (9 unique words) 1: "am" 1: "are" 2: "hello" 1: "Hello" 1: "here" 1: "i" 1: "where" 1: "world" 2: "you" Word usage sorted by occurrence (9 unique words) 1: am 1: are 1: Hello 1: here 1: i 1: where 1: world 2: hello 2: you
Module 7: Strings, Arrays, and Collections
63
Review Topic Objective
To reinforce module objectives by reviewing key points.
Lead-in
The review questions cover some of the key concepts taught in the module.
!
Strings
!
Terminology – Collections
!
.NET Framework Arrays
!
.NET Framework Collections
*****************************ILLEGAL FOR NON-TRAINER USE****************************** 1. Enter the code to read an integer from the console and assign it to a variable named aNumber. int MyInt = int.Parse(Console.ReadLine());
2. What class should you use to improve performance when you want to perform repeated modifications to a string? System.Text.StringBuilder
3. Name and briefly describe the interfaces implemented by System.Array. ICloneable: Supports cloning, which creates a new instance of a class with the same value as an existing instance. IList: Represents a collection of objects that can be individually indexed. ICollection: Defines size, enumerators, and synchronization methods for all collections. IEnumerable: Exposes the enumerator, which supports a simple iteration over a collection.
4. What does it mean to say that an enumerator is required to be safe? The enumerator must have a fixed view of the items in a collection that remains the same, even if the collection is modified.
64
Module 7: Strings, Arrays, and Collections
5. Create an array that contains the integers 1, 2, and 3. Then use the C# foreach statement to iterate over the array and output the numbers to the console. int[ ] numbers = {1, 2, 3}; foreach (int i in numbers) { System.Console.WriteLine("Number: {0}", i); }
6. What is the name of the interface that is implemented by classes that contain an ordered collection of objects that can be individually indexed? Name the System.Collections classes that implement this interface. The IList interface is implemented by Array, ArrayList, StringCollection, and TreeNodeCollection.
7. What is the name of the interface for collections of associated keys and values? Name the System.Collections classes that implement this interface. The IDictionary interface is implemented by Hashtable, DictionaryBase, and SortedList.
8. Generic collection classes require runtime type-casting of their items to obtain the true type of the items in the collection classes. Name the issues raised by runtime casting. Type-checking cannot be done at compile time. Performance overhead of casting. In the case of collection of value types, boxing and unboxing operations.
Module 8: Delegates and Events Contents Overview
1
Delegates
2
Multicast Delegates
12
Events
21
When to Use Delegates, Events, and Interfaces
31
Lab 8: Creating a Simple Chat Server
32
Review
42
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, place or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. 2001-2002 Microsoft Corporation. All rights reserved. Microsoft, ActiveX, BizTalk, IntelliMirror, Jscript, MSDN, MS-DOS, MSN, PowerPoint, Visual Basic, Visual C++, Visual C#, Visual Studio, Win32, Windows, Windows Media, and Window NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
Module 8: Delegates and Events
Instructor Notes Presentation: 75 Minutes Lab: 75 Minutes
After completing this module, students will be able to: !
Use the delegate class to create type-safe callback functions and eventhandling methods.
!
Use the event keyword to simplify and improve the implementation of a class that raises events.
!
Implement events that conform to the Microsoft® .NET Framework guidelines.
Materials and Preparation This section provides the materials and preparation tasks that you need to teach this module.
Required Materials To teach this module, you need the Microsoft PowerPoint® file 2349B_08.ppt.
Preparation Tasks To prepare for this module, you should: !
Read all of the materials for this module.
!
Practice the demonstrations.
!
Complete the lab.
iii
iv
Module 8: Delegates and Events
Demonstrations This section provides demonstration procedures that will not fit in the margin notes or are not appropriate for the student notes.
Using Delegates In this demonstration, you will use Microsoft Visual Studio® .NET to run code in which a delegate to a light object’s method is passed to a switch object. When the switch object’s state changes, the switch object calls the light object’s method and passes its new state. The code for this demonstration is provided in the student notes. The demonstration files are located in \Democode\Mod08\ Demo08.1\Using Delegates.
Multicast Delegates In this demonstration, you will show students how to add and remove methods from the invocation list of multicast delegates by using the plus operator (+) and the minus operator (-). The code for this demonstration is provided in the student notes. The demonstration files are located in \Democode\Mod08\ Demo08.2\MULTICAST DELEGATES.
Module Strategy Use the following strategy to present this module: !
Delegates Use the Delegate Scenario to help students to visualize and comprehend the concept of delegates. Some students may question the validity of this scenario. In the real world, a house would never be rewired dynamically, but you can explain that software programs often need to rebind dynamically.
!
Multicast Delegates Contrast typical multicast delegate use with that of the single delegate example by using the common scenario of a switch that controls two light bulbs. Explain how to create and invoke multicast delegates and show how to use the + and – operators in C# to add and remove methods from the invocation list of multicast delegates.
!
Events Use the Event Scenario to help students to visualize and comprehend the concept of events. Emphasize that events are an important building block for creating classes that can be reused in many different programs, and that delegates are particularly suited for event handling. Explain how to declare, connect to, and raise an event. Introduce the .NET Framework guidelines for delegates and events.
!
When to Use Delegates, Events, and Interfaces Contrast and compare the specific usage characteristics of delegates, interfaces, and events for providing callback functionality in particular situations.
Module 8: Delegates and Events
1
Overview Topic Objective
To provide an overview of the module topics and objectives.
Lead-in
Delegates are the objectoriented equivalents of function pointers.
!
Delegates
!
Multicast Delegates
!
Events
!
When to Use Delegates, Events, and Interfaces
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In the Microsoft® .NET Framework, delegates are the object-oriented equivalents of function pointers. However, unlike function pointers, delegates are type-safe and secure. The common language runtime supports the use of delegates in callback and event-handling scenarios. An event is raised by an object or event source in response to an action performed by a user or some sort of program logic. The event receiver must then respond to the raised event and perform an action. The event keyword lets you specify delegates that will be called upon the occurrence of an event. After completing this module, you will be able to: !
Use the delegate class to create type-safe callback functions and eventhandling methods.
!
Use the event keyword to simplify and improve the implementation of a class that raises events.
!
Implement events that conform to the .NET Framework guidelines.
2
Module 8: Delegates and Events
" Delegates Topic Objective
To provide an overview of the topics in this section.
Lead-in
In this section, you will learn about how delegates are used in the .NET Framework.
!
Delegate Scenario
!
Declaring a Delegate
!
Instantiating a Delegate
!
Calling a Delegate
*****************************ILLEGAL FOR NON-TRAINER USE****************************** You can use delegates to encapsulate a reference to a method inside a delegate object. Because delegates are type-safe, secure, managed objects, they offer all of the advantages of pointers without any of the disadvantages of pointers. For example, delegates will always point to a valid object and cannot corrupt the memory of other objects.
Module 8: Delegates and Events
3
Delegate Scenario Topic Objective
To illustrate one use of delegates through the scenario of a switch and a light bulb.
1 - Change in switch position invokes switch’s OnFlip method
Switch Object
Lead-in
This scenario of a switch that controls a light illustrates one use of delegates.
Light Object OnFlip method 2 - OnFlip Method invokes delegate
OnFlipCallback method
Delegate Delegate object object 3 - Delegate invokes light’s OnFlipCallback method
4 - OnFlipCallback method changes light’s state
*****************************ILLEGAL FOR NON-TRAINER USE****************************** To run the build slide, click through the lower-left button on the slide.
This scenario of a switch that controls a light illustrates one use of delegates. The switch object models an electric switch, and the light object models an electric light. The delegate object encapsulates a reference to a light object’s OnFlipCallback method. The switch object code could be written without a delegate by referring directly to the specific light object’s method. However, this approach does not offer the flexibility to dynamically connect, disconnect, and reconnect various light object methods to the switch object. You can use a delegate object that connects the switch object and the light object to achieve this flexibility.
For Your Information
Stress that this scenario reflects a common scenario to which everyone can relate. While you may not use the same light switch to turn different lights on and off at different times, this scenario helps you to visualize and comprehend the concepts behind delegates.
Delivery Tip
You should briefly preview the Demonstration: Using Delegates to provide students with an overview of the code that can be used to implement this scenario. Defer discussion of delegate specific code until after the following topics are covered.
In real life, the preceding scenario would probably not occur as described. While a house would never be rewired dynamically, software programs often need to dynamically rebind. When the switch is flipped in the light switch scenario shown in the slide: 1. The switch object’s OnFlip method is invoked. 2. The OnFlip method invokes the delegate object. 3. The delegate object invokes the light object’s OnFlipCallback method. 4. The OnFlipCallback method changes the state of the light.
4
Module 8: Delegates and Events
Declaring a Delegate Topic Objective
To explain how to declare a delegate.
Lead-in
A delegate declaration defines a type that encapsulates a method with a particular set of arguments and return type.
!
A Delegate Declaration Defines a Type That Encapsulates a Method with a Particular Set of Arguments and Return Type
// // declares declares aa delegate delegate for for aa method method that that takes takes aa single single // // argument argument of of type type string string and and has has aa void void return return type type delegate delegate void void MyDelegate1(string MyDelegate1(string s); s);
*****************************ILLEGAL FOR NON-TRAINER USE****************************** A delegate declaration defines a type that encapsulates a method with a particular set of arguments and return type. The delegate declaration is sufficient to define a delegate class whose actual implementation is provided by the common language runtime. For Your Information
The details of how the common language runtime creates delegates are presented later in this module.
The following example shows how to declare a delegate for a method that takes a single argument of type string and has a void return type: delegate void MyDelegate1(string s);
Module 8: Delegates and Events
5
Instantiating a Delegate Topic Objective
To explain how to instantiate delegates.
Lead-in
After you have declared a delegate type, you can create a delegate object and associate it with a particular method.
!
A Delegate Object Is Created with the new Operator
!
Delegate Objects Are Immutable
// // instantiating instantiating aa delegate delegate to to aa static static method method Hello Hello // in // in the the class class MyClass MyClass MyDelegate1 MyDelegate1 aa == new new MyDelegate1(MyClass.Hello); MyDelegate1(MyClass.Hello); // // instantiating instantiating aa delegate delegate to to an an instance instance method method // AMethod in object p // AMethod in object p MyClass MyClass pp == new new MyClass(); MyClass(); MyDelegate1 MyDelegate1 bb == new new MyDelegate1(p.AMethod); MyDelegate1(p.AMethod);
*****************************ILLEGAL FOR NON-TRAINER USE****************************** After you have declared a delegate type, you can create a delegate object and associate it with a particular method. This topic explains the code used to instantiate delegates.
Creating a New Delegate Object Like all objects, a new delegate object is created with the new operator. When creating a delegate, however, the argument passed to the new operator is special: it is written like a method call, but without the arguments to the method. After a delegate is created, the method to which it is associated never changes: delegate objects are immutable. Delivery Tip
To place the delegate instantiation code in a fuller context, refer to the following code example.
When referencing an object, an interesting and useful feature of a delegate is that the delegate does not know or care about the class of the object that it references. It can reference any object as long as the method’s signature matches the delegate’s signature. A delegate can reference static or instance methods. When referencing instance methods, the delegate stores not only a reference to the method’s entry point, but also a reference to the object instance on which the method is invoked.
6
Module 8: Delegates and Events
The following example shows how to declare a delegate named MyDelegate1. The code illustrates how this delegate can be instantiated to a static or an instance method. The signature of the method and MyDelegate1 must match; the method must have a void return and take a single argument of type string. delegate void MyDelegate1(string s); ... public class MyClass { public static void Hello(string s) { //... } public void AMethod(string s) { //... } } ... // instantiating a delegate to a static method MyDelegate1 a = new MyDelegate1(MyClass.Hello); // instantiating a delegate to object p's AMethod method MyClass p = new MyClass(); MyDelegate1 b = new MyDelegate1(p.AMethod);
Module 8: Delegates and Events
7
Calling a Delegate Topic Objective
To explain how to call delegates.
!
Lead-in
After a delegate is created, it can be passed to other code that will call the delegate. // // // // // //
Use a Statement Containing: #
The name of the delegate object
#
Followed by the parenthesized arguments to be passed to the delegate
given given the the previous previous delegate delegate declaration declaration and and instantiation, instantiation, the the following following invokes invokes MyClass' MyClass' static static method method Hello Hello with with the the parameter parameter "World" "World"
a("World"); a("World");
*****************************ILLEGAL FOR NON-TRAINER USE****************************** After a delegate object is created, it can be passed to other code that will call the delegate. For example, a server object may provide a method that a client object calls to register a callback method for a specific event. The server will invoke this callback method when that event occurs. Typically, the client instantiates a delegate that refers to its callback method. The client passes the callback delegate object as a parameter. For Your Information
Refer to the previous slide on delegate declaration and instantiations.
You can call a delegate object by using the name of the delegate, followed by the parenthesized arguments to be passed to the delegate. For example, using the declaration and instantiations of delegates a and b shown in the previous slide, the following lines invoke the static Hello method of the class MyClass and the AMethod of the MyClass object p with the argument "World": a("World"); b("World");
8
Module 8: Delegates and Events
Demonstration: Using Delegates Topic Objective
To demonstrate using a callback delegate.
Lead-in
In this demonstration, you will use Microsoft Visual Studio .NET to run code in which a delegate to a light object’s method is passed to a switch object. When the switch object’s state changes, the switch object calls the light object’s method and passes its new state.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Delivery Tip
Use Visual Studio .NET to run the following code. Set breakpoints or single step to illustrate the sequence of events.
In this demonstration, you will use Microsoft Visual Studio® .NET to run code in which a delegate to a light object’s method is passed to a switch object. When the switch object’s state changes, the switch object calls the light object’s method and passes its new state.
Module 8: Delegates and Events
For Your Information
As noted in C# LanguageSpecific Syntax in this module, delegate types with a void return value in C# cause the compiler to derive the delegate class from the MulticastDelegate class. Therefore, the IL for this delegate demonstration code will show the SwitchFlipped delegate inheriting from the System.MulticastDelegate class.
using System; namespace SwitchAndLight { public enum SwitchPosition {Up, Down}; delegate void SwitchFlipped(SwitchPosition switchState); class Light { private string name; public Light(string s) { name = s; } public void OnFlipCallback(SwitchPosition switchState) { if (switchState == SwitchPosition.Up) { Console.WriteLine("... {0} light is on",name); } else { Console.WriteLine("... {0} light is off",name); } } } class Switch { private SwitchPosition switchState = SwitchPosition.Down; private SwitchFlipped switchFlippedHandler = null; public void ConnectToLight(SwitchFlipped lightHandler) { switchFlippedHandler = lightHandler; } public SwitchPosition SwitchState { get {return switchState;} }
(Code continued on the following page.)
9
10
Module 8: Delegates and Events public void OnFlip() { if (switchState == SwitchPosition.Down) { switchState = SwitchPosition.Up; } else { switchState = SwitchPosition.Down; } if (switchFlippedHandler != null) { switchFlippedHandler(switchState); } } } class TheApp { static void OnFlip(Switch aSwitch) { Console.WriteLine( "Before flipping, the switch is: {0}", aSwitch.SwitchState); Console.WriteLine("Flipping switch ... "); aSwitch.OnFlip(); Console.WriteLine( "After flipping, the switch is: {0}\n\n", aSwitch.SwitchState); } static void Main(string[] args) { Switch s = new Switch(); Light light1 = new Light("bathroom"); Light light2 = new Light("bedroom"); // connect switch and bathroom light by passing a // delegate to the bathroom light's // OnFlipCallback method to s s.ConnectToLight(new SwitchFlipped(light1.OnFlipCallback)); OnFlip(s); OnFlip(s); // connect switch and bedroom light by passing a // delegate to the bedroom's light's // OnFlipCallback method to s s.ConnectToLight(new SwitchFlipped(light2.OnFlipCallback)); OnFlip(s); OnFlip(s); } } }
Module 8: Delegates and Events
This code generates the following output: Before flipping, the switch is: Down Flipping switch ... ... bathroom light is on After flipping, the switch is: Up
Before flipping, the switch is: Up Flipping switch ... ... bathroom light is off After flipping, the switch is: Down
Before flipping, the switch is: Down Flipping switch ... ... bedroom light is on After flipping, the switch is: Up
Before flipping, the switch is: Up Flipping switch ... ... bedroom light is off After flipping, the switch is: Down
11
12
Module 8: Delegates and Events
" Multicast Delegates Topic Objective
To provide an overview of the topics in this section.
Lead-in
If you change the state in one object, you may need to broadcast that change to multiple objects.
!
Multicast Delegate Scenario
!
Single vs. Multicast Delegates
!
Creating and Invoking Multicast Delegates
!
C# Language-Specific Syntax
!
Delegate Details
*****************************ILLEGAL FOR NON-TRAINER USE****************************** If you change the state in one object, you may need to broadcast that change to multiple objects. You can use multicast delegate objects to provide this functionality; they can be composed together so that a single invocation invokes all of their methods, just as a single light switch can turn on or off all lights connected to that switch. For Your Information
As noted in the C# Language-Specific Syntax in this module, delegate types with a void return value in C# cause the compiler to derive the delegate class from the MulticastDelegate class.
Module 8: Delegates and Events
13
Multicast Delegate Scenario Topic Objective
To illustrate the typical multicast delegate use by using the common scenario of a switch that controls two light bulbs.
1 - Change in switch position invokes switch’s OnFlip method
Light1 Object Light2 Object
Lead-in
The following scenario of a switch that controls two lights illustrates the use of multicast delegates.
Switch Object
4 - OnFlipCallback method changes light1’s state
OnFlip method 2 - OnFlip method invokes multicast delegate1
OnFlipCallback method
3 - delegate1 invokes light1’s OnFlipCallback Invocation list Multicast Multicastdelegate1 delegate1object object
5 - delegate2 is invoked
Multicast Multicastdelegate2 delegate2object object
OnFlipCallback method 7 - OnFlipCallback method changes light2’s state 6 - delegate2 invokes light2’s OnFlipCallback
*****************************ILLEGAL FOR NON-TRAINER USE****************************** To run the build slide, click through the lower-left button on the slide.
The following scenario of a switch that controls two lights illustrates the use of multicast delegates. The switch object models the light switch, and the two light objects model the two electric lights. To provide the capability to dynamically connect, disconnect, and reconnect light object methods to the switch object, the switch object is dynamically connected to the light objects by instantiating and composing two multicast delegate objects.
In the light scenario, multicast delegate1 refers to the light1 object’s OnFlipCallback method, and multicast delegate2 refers to the light2 object’s OnFlipCallback method. Their composition logically represents an invocation list, a list of methods that are executed when the delegate is invoked. In this case, the invocation list consists of these two methods. In the light switch scenario shown in the slide, the multicast delegate objects encapsulate references to two objects and two methods. When the switch is flipped: 1. The switch object’s OnFlip method is invoked. 2. The OnFlip method invokes the multicast delegate1 object. 3. The multicast delegate1 object first invokes the light1 object’s OnFlipCallback method. 4. The light1 object’s OnFlipCallback method changes the state of the light1 object. 5. The multicast delegate1 object next invokes the multicast delegate2 object. 6. The multicast delegate2 object invokes the light2 object’s OnFlipCallback method. 7. The light2 object’s OnFlipCallback method changes the state of the light2 object. You can compose additional multicast delegates to allow the switch to control additional lights.
14
Module 8: Delegates and Events
Single vs. Multicast Delegates Topic Objective
To explain the difference between single-cast and multicast delegates.
!
All Delegates Have an Invocation List of Methods That Are Executed When Their Invoke Method is Called
!
Single-Cast Delegates: Derived Directly From System.MulticastDelegate
!
Multicast Delegates: Derived from System.MulticastDelegate
Lead-in
When delegate declarations are compiled, the compiler generates a new class, which derives from two delegate classes provided by the .NET Framework.
#
# #
Invocation list contains only one method Invocation list may contain multiple methods Multicast delegates contain two static methods to add and remove references from invocation list: Combine and Remove
!
Use GetInvocationList to Obtain an Invocation List as an Array of Delegate References
!
Use a Delegate’s Target and Method Properties to Determine: # #
Which object will receive the callback Which method will be called
*****************************ILLEGAL FOR NON-TRAINER USE****************************** When delegate declarations are compiled, the compiler, in effect, generates a new class. The new delegate class derives from the System.MulticastDelegate class that is provided by the .NET Framework. All delegates have an invocation list, or linked list of delegates that are executed when the invoke method is called. A delegate that derives from the MulticastDelegate class may contain an invocation list with multiple delegates. The MulticastDelegate class contains two static methods to add and remove method references from an invocation list: Combine and Remove. Combine is declared as follows: public static Delegate Combine( Delegate a, Delegate b);
The method returns a new multicast Delegate object with an invocation list that concatenates the invocation lists of a and b in that order. Remove is declared as follows: public static Delegate Remove( Delegate source, Delegate value);
The method returns a new Delegate object with an invocation list formed by taking the invocation list of source and removing the last occurrence of value, if value is found in the invocation list of source. If value is null or if value is not found, then source is returned. You can use the method GetInvocationList to obtain the invocation list as an array of delegate references. You can also use the delegate’s Target and Method properties to determine which object is to receive the callback and which method is to be called. In the case of a static method, Target is null.
Module 8: Delegates and Events
15
Creating and Invoking Multicast Delegates Topic Objective
To provide specific examples of how to create and invoke multicast delegates, and how to iterate though an invocation list to invoke specific delegates.
// // assign assign to to cc the the composition composition of of delegates delegates aa and and bb cc == (MyDelegate2)Delegate.Combine(a, (MyDelegate2)Delegate.Combine(a, b); b); // // assign assign to to dd the the result result of of removing removing aa from from cc dd == (MyDelegate2)Delegate.Remove(c, a); (MyDelegate2)Delegate.Remove(c, a); // // Iterate Iterate through through c's c's invocation invocation list list // // and and invoke invoke all all delegates delegates except except aa Delegate[] Delegate[] DelegateList DelegateList == c.GetInvocationList(); c.GetInvocationList(); for (int i = for (int i = 0; 0; ii << DelegateList.Length; DelegateList.Length; i++) i++) {{ if (DelegateList[i].Target != aFoo1) if (DelegateList[i].Target != aFoo1) {{ ((MyDelegate2) ((MyDelegate2) DelegateList[i])(); DelegateList[i])(); }} }}
Lead-in
This sample code shows how to create and invoke multicast delegates, and how to iterate through an invocation list to invoke specific delegates.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The following sample code shows: !
How to create multicast delegate objects.
!
How to write code to invoke delegates’ Combine and Remove methods.
!
How to write code to iterate through an invocation list invoking all delegates, except those delegates whose target is a specific object.
16
Module 8: Delegates and Events using System; public delegate void MyDelegate2(); public class Foo { public void Bar() { Console.WriteLine("Bar invoked"); } } class Application { public static void Main() { Foo aFoo1 = new Foo(); Foo aFoo2 = new Foo(); MyDelegate2 a, b, c, d; a = new MyDelegate2(aFoo1.Bar); b = new MyDelegate2(aFoo2.Bar); // assign to delegate c the composition of delegates a and b c = (MyDelegate2)Delegate.Combine(a , b); // assign to d the result of removing a from c d = (MyDelegate2)Delegate.Remove(c , a); // iterate through c's invocation list // and invoke all delegates except those that target aFoo1 Delegate[] DelegateList = c.GetInvocationList(); for (int i = 0; i < DelegateList.Length; i++) { if (DelegateList[i].Target != aFoo1) { ((MyDelegate2) DelegateList[i])(); } } } }
The preceding code invokes the delegate that invokes object aFoo2’s Bar method and outputs: Bar invoked
Module 8: Delegates and Events
17
C# Language-Specific Syntax Topic Objective
To present helpful alternative C# syntax.
Lead-in
Delegate types with a void return value in C# cause the compiler to derive the delegate class from the MulticastDelegate class.
!
C# Delegates That Return Void Are Multicast Delegates
!
In C#, Use the + and - Operators to Add and Remove Invocation List Entries #
Less verbose than Combine and Remove methods
MyDelegate MyDelegate a, a, b, b, c, c, d; d; aa == new MyDelegate(Foo); new MyDelegate(Foo); bb == new new MyDelegate(Bar); MyDelegate(Bar); cc == aa ++ b; b; // // Compose Compose two two delegates delegates to to make make another another dd == cc -- a; a; // // Remove Remove aa from from the the composed composed delegate delegate aa += // += b; b; // Add Add delegate delegate bb to to a's a's invocation invocation list list aa -= // -= b; b; // Remove Remove delegate delegate bb from from a's a's list list
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Delegate types with a void return value in C# cause the compiler to derive the delegate class from the MulticastDelegate class. You can also use these delegates’ addition operator (+) and the subtraction operator (-) to invoke the Combine and Remove methods for delegate composition and decomposition.
18
Module 8: Delegates and Events
Demonstration: Multicast Delegates Topic Objective
To demonstrate how to use the + and – operators to add and remove methods.
Lead-in
You can add and remove methods from the invocation list of multicast delegates by using the + and – operators.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this demonstration, you will learn how to add and remove methods from the invocation list of multicast delegates by using the + and – operators. Delivery Tip
Use Visual Studio .NET to run this code. Set breakpoints or single-step to illustrate the sequence of events.
using System; namespace Multicast_Delegates { delegate void MyDelegate3(string s); class MyClass { public static void Hello(string s) { Console.WriteLine(" Hello, {0}!", s); } public static void Goodbye(string s) { Console.WriteLine(" Goodbye, {0}!", s); }
(Code continued on the following page.)
Module 8: Delegates and Events public static void Main() { MyDelegate3 a, b, c, d; a = new MyDelegate3(MyClass.Hello); b = new MyDelegate3(MyClass.Goodbye); c = a + b; // Compose two delegates to make another d = c - a; // Remove a from c to make another Console.WriteLine("Invoking a("A"); Console.WriteLine("Invoking b("B"); Console.WriteLine("Invoking c("C"); Console.WriteLine("Invoking d("D");
delegate a:"); delegate b:"); delegate c:"); delegate d:");
Console.WriteLine("Adding b to a and invoking:"); a += b; // Add the b delegate to a a("E"); Console.WriteLine("Removing b from a and invoking:"); a -= b; // Remove the b delegate from a a("F"); } } }
This code generates the following output: Invoking delegate Hello, A! Invoking delegate Goodbye, B! Invoking delegate Hello, C! Goodbye, C! Invoking delegate Goodbye, D! Adding b to a and Hello, E! Goodbye, E! Removing b from a Hello, F!
a: b: c:
d: invoking:
and invoking:
19
20
Module 8: Delegates and Events
Delegate Details Topic Objective
To show how a delegate declaration is handled by the compiler.
Lead-in
When delegate declarations are compiled, the compiler generates a new class that derives from System.Delegate.
!
A Delegate Declaration Causes the Compiler to Generate a New Class
// // delegate delegate void void MyDelegate3(string MyDelegate3(string val); val); class class MyDelegate3 MyDelegate3 :: System.MulticastDelegate System.MulticastDelegate public public MyDelegate3(object MyDelegate3(object obj, obj, methodref methodref :: base (obj, mref) { //... base (obj, mref) { //... }} public public void void virtual virtual Invoke(string Invoke(string val) val) {{ }} }; };
{{ mref) mref) //... //...
*****************************ILLEGAL FOR NON-TRAINER USE****************************** As previously noted in this module, when delegate declarations are compiled, the compiler, in effect, generates a new class that derives from System.Delegate. The new delegate class has two members: a constructor and an Invoke method. The following class declaration resembles what the compiler actually does: // delegate void MyDelegate3(string val);
For Your Information
There is no actual type named methodref, but having an instance of a type that encapsulates a reference to a class method resembles what the compiler actually does.
class MyDelegate3 : System.MulticastDelegate { public MyDelegate3(object obj, methodref mref) : base (obj, mref) {//... } public void virtual Invoke(string val) {//... } }
The first member is a constructor: its first parameter is the delegate’s target object, and its second parameter is a reference to a method. When a delegate refers to a static method, the target object is null. The Invoke method for the class MyDelegate3 indicates that delegate instances of this class encapsulate methods that have a void return and a single string parameter. When a delegate is invoked, the Invoke method is called with the specified parameters.
Module 8: Delegates and Events
21
" Events Topic Objective
To provide an overview of the topics in this section.
Lead-in
An event is a way for a class to notify clients of that class of a change in an object.
!
Event Scenario
!
Declaring an Event
!
Connecting to an Event
!
Raising an Event
!
.NET Framework Guidelines
*****************************ILLEGAL FOR NON-TRAINER USE****************************** An event is a way for a class to notify clients of that class when some interesting thing happens to an object. The most familiar use for events is in graphical user interfaces. Typically the classes that represent controls in the graphical user interface have events that are notified when a user manipulates the control, as when a user clicks a button. However, the use of events is not limited to graphical user interfaces. Events also allow an object to signal state changes that may be useful to clients of that object. Events are an important building block for creating classes that can be reused in many different programs. Delegates are particularly suited for implementing events. You can use the .NET event mechanism to easily provide an object with type safe methods that clients can call to register and deregister delegates to event handler methods. When an event is raised, the event handler methods are called.
22
Module 8: Delegates and Events
Event Scenario Topic Objective
To illustrate the .NET event mechanism through the scenario of a mouse clicked event.
Lead-in
This scenario of a mouse object that notifies interested objects when a mouse clicked event occurs illustrates one use of delegates and events.
Mouse Object MouseClicked field Invocation List:
SoundMaker Object MouseClicked MouseClicked method method
multicast delegate object multicast delegate object
stopButton object MouseClicked MouseClicked method method
add_MouseClicked method remove_MouseClicked method OnMouseClicked method
*****************************ILLEGAL FOR NON-TRAINER USE****************************** To run the build slide, click through the lower-left button on the slide.
This scenario of a mouse object that notifies interested client objects when a mouse click occurs illustrates one use of the .NET event mechanism. Client objects add and remove multicast delegate objects to the mouse object’s invocation list. These delegates specify the event handler methods to be called when a mouse click occurs. The mouse object code could be written to call a predefined list of event handler methods. However, this approach does not offer the flexibility to dynamically connect, disconnect, and reconnect methods to be called when the mouse click event occurs. By using multicast delegate objects, you can provide this flexibility. In the mouse clicked event scenario that is shown in the slide, a mouse object encapsulates code to monitor a hardware mouse: 1. The mouse object’s class uses the Microsoft Visual C#™ compiler event keyword to have the compiler automatically create a private field named MouseClicked that is used to store a reference to the invocation list of delegates, and two public methods named add_MouseClicked and remove_MouseClicked that are used to add and remove delegates to this list. You can call these methods in Visual C# by using the += and -= operators. 2. The mouse object’s class declares an OnMousedClicked method that is called when a mouse click is detected. The OnMouseClicked method raises the MouseClicked event by invoking the invocation list’s first delegate object. 3. A user issues a command that causes sound to be generated whenever the mouse is clicked. This command causes the mouse object’s add_MouseClicked method to be called with a delegate object that points to the soundMaker object’s MouseClicked method. This delegate is added to the mouse object’s MouseClicked invocation list. 4. The mouse is clicked and the mouse object’s OnMouseClicked method is called to raise the mouse clicked event.
Module 8: Delegates and Events
23
5. The OnMouseClicked method invokes the MouseClicked invocation list’s delegate. 6. The delegate calls the SoundMaker object’s MouseClicked method and a sound is generated. 7. A user then starts an application that operates a piece of machinery. The application displays a Stop button on the screen that the user clicks to stop the operation of the machinery. The application then instantiates a stopButton object and calls the mouse object’s add_MouseClicked method, passing a delegate object that refers to the stopButton object’s MouseClicked method. This delegate is added to the mouse object’s MouseClicked invocation list. The application then starts the machinery. 8. The mouse is clicked and the mouse object’s OnMouseClicked method is called to raise the MouseClicked event. 9. The OnMouseClicked method invokes the MouseClicked invocation list’s first delegate object. The delegate calls the SoundMaker object’s MouseClicked method and a sound is generated. 10. The first delegate object invokes the second delegate object. The second delegate object calls the stopButton object’s MouseClicked method. This method checks the location of the mouse cursor and after determining that it is located over the Stop button, halts the machinery. 11. The user then issues a command to terminate the application. The application calls the mouse object’s remove_MouseClicked method, passing a delegate object that refers to the stopButton object’s MouseClicked method. This delegate is removed from the mouse object’s MouseClicked invocation list.
24
Module 8: Delegates and Events
Declaring an Event Topic Objective
To explain how to declare events.
!
Declare the Delegate Type for the Event
Lead-in
!
Declare the Event
To declare an event inside a class, you first must declare a delegate type for the event.
#
Like the field of delegate type preceded by an event keyword
// // MouseClicked MouseClicked delegate delegate declared declared public public delegate delegate void void MouseClickedEventHandler(); MouseClickedEventHandler(); public public class class Mouse Mouse {{ // // MouseClicked MouseClicked event event declared declared public public static static event event MouseClickedEventHandler MouseClickedEventHandler MouseClicked; MouseClicked; //... //... }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** To declare an event inside a class, you first must declare a delegate type for the event. The delegate type defines the set of arguments that are passed to the method that handles the event. Multiple events can share a delegate type, so you need only declare a delegate type for an event if no suitable delegate type has already been declared. For example, in the case of a MouseClicked event in which the method that handles the event takes no arguments and returns void, the delegate is declared as follows: public delegate void MouseClickedEventHandler();
Next, the event itself is declared. You declare an event as you would declare a field of delegate type, except that the keyword event follows the modifiers and precedes the delegate type. Events usually are declared public, but any accessibility modifier is allowed. The following code declares a class Mouse with an event named MouseClicked: public class Mouse { public static event MouseClickedEventHandler MouseClicked; //... }
Module 8: Delegates and Events
When you declare an event, the compiler generates a private field that references the end of a delegate invocation list. In the preceding example, a private field named MouseClicked, which refers to delegates of type MouseClickedEventHandler, is created. The compiler also generates two public methods for clients to call to compose and remove their delegate objects. In the preceding example, these public methods would be named add_MouseClicked and remove_MouseClicked. You can call these methods in C# by using the += and -= operators. Because client access to the delegate invocation list is restricted to these methods, the use of the event keyword not only makes the implementation of events easier, it also prevents clients from accessing or raising the delegates of other clients.
25
26
Module 8: Delegates and Events
Connecting to an Event Topic Objective
To describe how to connect to and disconnect from an event.
Lead-in
From outside the class that declared it, an event looks like a field, but access to that field is restricted.
!
Connect by Combining Delegates
!
Disconnect by Removing Delegates
// // Client’s Client’s method method to to handle handle the the MouseClick MouseClick event event private void MouseClicked() { //... private void MouseClicked() { //... }} //... //... // // Client Client code code to to connect connect to to MouseClicked MouseClicked event event Mouse.MouseClicked += new Mouse.MouseClicked += new MouseClickedEventHandler(MouseClicked); MouseClickedEventHandler(MouseClicked); // // Client Client code code to to break break connection connection to to MouseClick MouseClick event event Mouse.MouseClicked Mouse.MouseClicked -= -= new new MouseClickedEventHandler(MouseClicked); MouseClickedEventHandler(MouseClicked);
*****************************ILLEGAL FOR NON-TRAINER USE****************************** From outside the class that declared it, an event looks like a field, but access to that field is restricted. You can only add a delegate to an event or remove a delegate from an event. In C#, you add and remove delegates by using the += and -= operators. To begin receiving event invocations, client code first creates a delegate of the event type that refers to the method that should be invoked from the event. Then, the client code uses the + and – operators to compose that delegate with any other delegates to which the event might be connected. When the client code is finished receiving event invocations, it removes its delegate from the event by using the -= operator. The following example shows how to connect to and disconnect from a MouseClicked event: public class aClient { // Client's method to handle the MouseClick event private void MouseClicked() { //... } //... // code to connect to Mouse class' MouseClicked event Mouse.MouseClicked += new MouseClickedEventHandler(MouseClicked); // code to break the connection to MouseClicked event Mouse.MouseClicked -= new MouseClickedEventHandler(MouseClicked); //... }
Module 8: Delegates and Events
27
Raising an Event Topic Objective
To show when and how to raise an event.
Lead-in
After a class has declared an event, it can treat that event like a field of the indicated delegate type.
!
Check Whether Any Clients Have Connected to This Event #
!
If the event field is null, there are no clients
Raise the Event by Invoking the Event’s Delegate
if if (MouseClicked (MouseClicked != != null) null) MouseClicked(); MouseClicked();
*****************************ILLEGAL FOR NON-TRAINER USE****************************** After a class has declared an event, it can treat that event like a field of the indicated delegate type. The field will be null if no client has connected a delegate to the event, or it will refer to a delegate that should be called when the event is raised. Therefore, when an event is raised, you should first check for null and then call the event. You can only raise an event from within the class that declared the event. if (MouseClicked != null) MouseClicked();
28
Module 8: Delegates and Events
.NET Framework Guidelines Topic Objective
To introduce .NET Framework guidelines for delegates and events.
Lead-in
You should follow .NET Framework guidelines if you intend to use your component in the .NET Framework.
!
Name Events with a Verb and Use Pascal Casing
!
Use “Raise” for Events, Instead of “Fire”
!
Event Argument Classes Extend System.EventArgs
!
Event Delegates Return Void and Have Two Arguments
!
Use a Protected Virtual Method to Raise Each Event
public public class class SwitchFlippedEventArgs SwitchFlippedEventArgs :: EventArgs EventArgs {{ //... //... }} public public delegate delegate void void SwitchFlippedEventHandler( SwitchFlippedEventHandler( object object sender, sender, SwitchFlippedEventArgs SwitchFlippedEventArgs e); e); public public event event SwitchFlippedEventHandler SwitchFlippedEventHandler SwitchFlipped; SwitchFlipped;
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The .NET Framework includes guidelines on the delegate types that should be used for events. If you are creating a component for use with the .NET Framework, you should follow these guidelines: For Your Information
!
The Pascal Casing convention capitalizes the first character of each word. For example: BackColor The camelCasing convention capitalizes the first character of each word, except the first word. For example: backColor
Consider naming events with a verb and use Pascal Casing. The Pascal Casing convention capitalizes the first character of each word. For example: SwitchFlipped Prefix pre- and post-event names by using the present and past tense. Do not use the BeforeXxx\AfterXxx pattern. For example, a close event that could be canceled should have a Closing and Closed event. An event that indicates that a control has been added should be declared as follows: public event ControlEventHandler ControlAdded;
!
Use “raise” for events, rather than “fired.” When referring to events in documentation, use “an event was raised,” instead of “an event was fired.”
!
For the event’s delegate declaration, use a return type of void, two parameters, an object source parameter named sender, and an e parameter that encapsulates any additional information about the event. Delegates should return void and have two arguments: the object that raised the event and the event data object. Use names ending in EventHandler for the event’s delegate, as in the following example: public delegate void SwitchFlippedEventHandler( object sender, SwitchFlippedEventArgs e);
The sender parameter represents the object that raised the event and called the delegate. The sender parameter is always a parameter of type object, even if you can employ a more specific type. The state associated with the event is encapsulated in an instance of an event class named e. Use an appropriate and specific event class for its type.
Module 8: Delegates and Events !
29
When necessary, event classes extend System.EventArgs and end in EventArgs, as in the following example: public class SwitchFlippedEventArgs : EventArgs {//... }
For events that do not use any additional information, the .NET Framework has already defined an appropriate delegate type: EventHandler, whose argument is of the event base type EventArgs, as in the following example: public delegate void EventHandler( object sender, EventArgs e); !
Create an invoking method for the event. Because events can only be raised from within the class that declared them, derived classes cannot directly raise events that are declared within the base class. Often it is appropriate to let the derived class raise the event. You can do this by creating a protected invoking method for the event. By calling this invoking method, derived classes can raise the event. protected virtual void OnSwitchFlipped (//...) { //... }
For even more flexibility, you can declare the invoking method as virtual, which allows the derived class to override it. This approach allows the derived class to intercept the events that the base class is raising, possibly doing its own processing of them. For this reason, the .NET Framework guideline suggests that you create an invoking method to raise an event that is a protected (family) virtual method. However, if the class is sealed, the method should not be virtual because the class cannot be derived from. Name the method OnEventName, where EventName is the event being raised.
30
Module 8: Delegates and Events
The switch and light delegate scenarios in the preceding topics help to put these guidelines in context. When a switch is flipped, the switch object raises a SwitchFlipped event for connected light objects. In this case, the additional SwitchFlipped event data indicates whether the switch is in the up or down position: public enum SwitchPosition {Up, Down}; // class to encapsulate switch data public class SwitchFlippedEventArgs : EventArgs { private SwitchPosition position; public SwitchFlippedEventArgs(SwitchPosition position) this.position = position; }
{
public SwitchPosition Position { get { return position; } } } //delegate declaration public delegate void SwitchFlippedEventHandler( object sender, SwitchFlippedEventArgs e); public class Switch { //event declaration public event SwitchFlippedEventHandler SwitchFlipped; // ProcessSwitchFlippedUp – called when switch flipped up // to create an event argument object and raise the event public void ProcessSwitchFlippedUp() { SwitchFlippedEventArgs e = new SwitchFlippedEventArgs(SwitchPosition.Up); OnSwitchFlipped(e); } // ProcessSwitchFlippedDown – when switch flipped down public void ProcessSwitchFlippedDown() { SwitchFlippedEventArgs e = new SwitchFlippedEventArgs(SwitchPosition.Down); OnSwitchFlipped(e); } protected virtual void OnSwitchFlipped ( SwitchFlippedEventArgs e) { if (SwitchFlipped != null) { // call the delegate if non-null SwitchFlipped(this, e); } } //... }
Module 8: Delegates and Events
31
When to Use Delegates, Events, and Interfaces Topic Objective
To explain when to use delegates, events, and interfaces.
!
Lead-in
Delegates, interfaces, and events all provide callback functionality, but each has specific usage characteristics that make it better suited to particular situations.
Use a Delegate If: #
You basically want a C-style function pointer
#
You want single callback invocation
#
!
Use Events If: #
Client signs up for the callback function through an add method call
#
More than one object will care
#
!
The callback should be registered in the call or at construction time, not through an add method call
You want end users to be able to easily add a listener to the notification in the visual designer
Use an Interface If: #
The callback function entails complex behavior, such as multiple methods
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Delegates, interfaces, and events all provide callback functionality, but each has specific usage characteristics that make it better suited to particular situations. Use a delegate if: !
You basically want a C-style function pointer.
!
You want single callback invocation.
!
You want the callback function registered in the call or at construction time, not through an add method call.
Use events if: !
Client code signs up for the callback prior to the occurrence of events, typically through an add method call.
!
More than one client object will care.
!
You want end users to be able to easily add a listener to the notification in the visual designer.
Use an interface if: • The callback function entails complex behavior, as when more than one callback method is invoked.
32
Module 8: Delegates and Events
Lab 8: Creating a Simple Chat Server Topic Objective
To introduce the lab.
Lead-in
In this lab, you will create use delegates and events with a simple chat server application.
*****************************ILLEGAL FOR NON-TRAINER USE******************************
Objectives After completing this lab, you will be able to: !
Write code that uses the delegate class to create type-safe callbacks.
!
Write code that uses the event keyword to simplify and improve the implementation of a class that raises events.
!
Write event code that conforms to the .NET Framework guidelines.
Lab Setup Only solution files are associated with this lab. The solution files for this lab are in the folder \Labs\Lab08\Solution.
Scenario In this lab, you will create a simple chat-style server to which multiple clients can connect. When one client sends a string message to the server, the server forwards the message to all registered clients that have not been specifically excluded. You should note that a real chat application would be based on a more scalable and flexible design to connect client and servers, such as a publish-andsubscribe design.
Estimated time to complete this lab: 60 minutes
Module 8: Delegates and Events
33
Exercise 1 Creating a Simple Chat Server Using Delegates In this exercise, you will implement a simple chat server and clients by using delegates.
Scenario Each client will register a delegate with the server. Then, when a client sends a message to the server, the server forwards the message to all specified clients. This implementation uses delegates as a callback mechanism.
! Create a Visual Studio .NET project • Open Visual Studio .NET and create a new C# Console Application project named chat1 in \Labs\Lab08.
! Create the chat server class 1. Create the chat server class and name it DChatServer. 2. Declare within the DChatServer class a delegate that will be used to invoke the callback function in the client when a chat message arrives. a. Name the delegate OnMsgArrived. b. Declare this delegate public and reflect the callback function’s signature. The callback’s signature is a void return value with a single argument of type string. 3. Define a field to store the connected clients’ delegates by performing the following steps. a. Name the field onMsgArrived. b. Make the field a private static reference to the type of the delegate, OnMsgArrived. 4. Define the method that will be called by a client to connect that client to the server by performing the following steps. a. Name the method ClientConnect. b. Declare the method public static void. c. Include an argument of type OnMsgArrived. d. Create the method’s implementation by using the delegate’s Combine method to add this new delegate to the delegate stored in the onMsgArrived invocation list. As an alternative to the Combine method, you can use the += operator. 5. Define a method that will be called by a client to disconnect that client from the server by performing the following steps. a. Name the method ClientDisconnect. b. Declare the method public static void. c. Declare the method so that it has the same signature as ClientConnect. d. Create the method’s implementation by using the delegate’s Remove method to remove the delegate from the onMsgArrived invocation list. As an alternative to the Remove method, you can use the -= operator.
34
Module 8: Delegates and Events
6. Define a method that will send a specified message to the connected clients, with the possible exception of one client, by performing the following steps. a. Name the method SendMsg. b. Declare the method public static void. c. Declare the method so that it takes two arguments: a string argument for the message and an object argument for the excluded client. d. Create the method’s implementation. i. If the excluded client argument is null, invoke the multicast delegate to send the message to all the clients. ii. If the excluded client argument is not null, iterate through the onMsgArrived delegate’s invocation list and invoke only those delegates that do not match the excluded client argument.
! Create the chat client class 1. Create the chat client class and name it DChatClient. 2. Implement within the DChatClient class a callback method that the server will invoke by a delegate when the server receives a chat message. a. Name the method onMsgArrived. b. Declare the method private void. c. Declare the method so that it takes a single string argument. d. Create the method’s implementation so that it prints the name of the client along with the string argument to the console. 3. Add a constructor that will receive the name of the client as a parameter and connect the client with the server. a. Declare the constructor so that it takes a string argument that represents the client’s name. b. Create the constructor’s implementation. i. Store the client’s name in a private field. ii. Connect the client to the server by calling the server’s ClientConnect method. The call’s argument should be an instance of the delegate type DChatServer.OnMsgArrived that was instantiated with the client’s callback method: onMsgArrived.
Module 8: Delegates and Events
35
! Create the program’s main entry point 1. Name the class that contains the program’s standard Main entry point, Application. Typically, a Visual Studio .NET project creates the class with the Main entry point for you. You may only need to rename this class. 2. Write to the console a line of text to indicate that the program is starting. 3. Instantiate three DChatClient objects, passing the client names “1”, “2”, and “3” to the constructor. 4. Send a message to the server by invoking the static method SendMsg of the DChatServer. The message should be sent to all clients. 5. Send a second message to all clients, except Client 2. 6. Write to the console a line of text to indicate that the program is finished.
! Compile and test the program • Build the program and run it. The output should resemble the following: Demo start: Delegate Chat Server. Msg arrived (Client 1): Hi to all Msg arrived (Client 2): Hi to all Msg arrived (Client 3): Hi to all Msg arrived (Client 1): Hi to all Msg arrived (Client 3): Hi to all Demo stop: Delegate Chat Server.
clients clients clients clients except client 2 clients except client 2
36
Module 8: Delegates and Events
Exercise 2 Creating a Simple Chat Server Using Events In this exercise, you will implement a simple chat server and clients by using events and delegates. This implementation uses the event keyword, which hides some of the low-level code details associated with delegates and prevents clients from accessing or invoking the delegates of other clients.
Scenario Each client will connect with the server’s “on message arrived” event. Then, when one client sends a string message to the server, the server forwards the message to all specified connected clients.
! Access the chat1 Visual Studio .NET project • Open Visual Studio .NET and open the project named chat1, which you created in Exercise 1, in \Labs\Lab08. Open the previously created C# source file and perform the following procedures.
! Create the event-based chat server class 1. Create a new chat server class and name it EChatServer. 2. Declare within the EChatServer class a delegate that will be used to invoke the callback function in the client when a chat message arrives. a. Name the delegate OnMsgArrived. b. This delegate should be declared public and reflect the callback function’s signature. The callback’s signature is a void return value with a single argument of type string. 3. Declare an event for the OnMsgArrived delegate type. a. Name the event onMsgArrived. b. Declare the event public static. 4. Define a method that will send a specified message to the connected clients, with the possible exception of one client. a. Name the method SendMsg. b. Declare the method public static void. c. Declare the method so that it takes two arguments: a string argument for the message and an object argument for the excluded client. d. Create the method’s implementation. i. If the excluded client argument is null, invoke the multicast delegate to send the message to all of the clients. ii. If the excluded client argument is not null, iterate through the onMsgArrived delegate’s invocation list and invoke only those delegates that do not match the excluded client argument.
Module 8: Delegates and Events
37
! Create the event-based chat client class 1. Create a new chat client class and name it EChatClient. 2. Implement within the EChatClient class a callback method that the server will invoke by a delegate when the server receives a chat message. a. Name the method onMsgArrived. b. Declare the method private void. c. Declare the method so that it takes a single string argument. d. Implement the method so that it prints the name of the client, together with the string argument, to the console. 3. Add a constructor that will receive the name of the client as a parameter and connect the client with the server. a. Declare the constructor so that it takes a string argument that represents the client’s name. b. Create the constructor’s implementation. i. Store the client’s name in a private field. ii. Connect the client to the server by using the += operator to add a new delegate instance to the EChatServer’s event. The += operator’s right operand should be an instance of the delegate type EChatServer.OnMsgArrived that was instantiated with the client’s callback method, onMsgArrived.
! Extend the program’s main entry point by performing the following procedures
1. Write to the console a line of text to indicate that the event chat portion of the program is starting. 2. Instantiate three EChatClient objects, passing the client names “1”, “2”, and “3” to the constructor. 3. Send a message to the server by invoking the static method SendMsg of the EchatServer. You should send the message to all clients. 4. Send a second message to all clients except client 2. 5. Write to the console a line of text to indicate that event chat is finished.
! Compile and test the program • Build the program and run it. The output should resemble the following: ... Demo start: Event Chat Server. Msg arrived (Client 1): Hi to all Msg arrived (Client 2): Hi to all Msg arrived (Client 3): Hi to all Msg arrived (Client 1): Hi to all Msg arrived (Client 3): Hi to all Demo stop: Event Chat Server.
clients clients clients clients except client 2 clients except client 2
38
Module 8: Delegates and Events
Exercise 3 (As Time Permits) Conforming to the .NET Framework Guidelines In this exercise, you will implement the simple chat program with events that conform to the .NET Framework guidelines. Although the C# language allows events to use any delegate type, the .NET Framework has stricter guidelines on the delegate types that should be used for events. If you intend for your component to be used with the .NET Framework, follow these guidelines.
Scenario As in Exercise 2, each client connects with the server’s event. Then, when one client sends a string message to the server, the server forwards the message to all specified clients. In this exercise, the delegate type will conform to the .NET Framework guidelines. The delegate type will take two parameters, an object source parameter, which indicates the source of the event, and an e parameter, which encapsulates information about the event and, in this case, includes the string message that generated the event. In this exercise, you will also instantiate the chat server as an object. Instantiating the chat server will allow you to invoke the connected client’s delegates with a non-null object source parameter.
! Create the event class that will encapsulate the event’s state 1. Open Visual Studio .NET and open the project named chat1, which you created in Exercise 1, in location \Labs\Lab08. Open the previously created C# source file and add the following code. 2. Create a new public class named MsgArrivedEventArgs that inherits from EventArgs. 3. Declare within MsgArrivedEventArgs a private readonly field of type string named message. 4. Declare a public constructor that takes a single string argument and stores it in the field named message. 5. Add a property named Message that returns the value of message.
Module 8: Delegates and Events
39
! Create the guidelines-based chat server class 1. Create a new chat server class and name it GChatServer. 2. Declare within the GChatServer class a delegate that will be used to invoke the function in the client when a chat message arrives. a. Name the delegate MsgArrivedEventHandler. b. This delegate should be declared public and reflect the guideline’s signature of a void return value and two arguments of type object and MsgArrivedEventArgs. 3. Declare an event for the MsgArrivedEventHandler delegate type. a. Name the event MsgArrivedHandler. b. Make the event public. Do not make it static. 4. Define a method that will send a specified message to the connected clients, with the possible exception of one client. a. Name the method SendMsg. b. Declare the method public void. c. Declare the method so that it takes two arguments: a string argument for the message and an object argument for the excluded client. d. Create the method’s implementation. i. Instantiate an instance of the event class MsgArrivedEventArgs with the message string. ii. Invoke the OnMsgArrived method, which is implemented in the next step. 5. Define the OnEvent method to raise the event. a. Name the method OnMsgArrived. In accordance with the .NET Framework guidelines declare the method protected virtual void with two arguments of type MsgArrivedEventArgs and object. b. Create the method’s implementation. i. If the event’s field contains a non-null delegate list and the excluded client argument is null, invoke the multicast delegate with the appropriate arguments. ii. If the event’s field contains a non-null delegate list and the excluded client argument is not null, iterate through the MsgArrivedHandler delegate’s invocation list and invoke only those delegates that do not match the excluded client argument. iii. If the event’s field contains a null delegate list, do nothing.
40
Module 8: Delegates and Events
! Create the guidelines-based chat client class 1. Create a new chat client class and name it GChatClient. 2. Implement within the GChatClient class a callback method that the server will invoke by a delegate when the server receives a chat message. a. Name the method onMsgArrived. b. Declare the method private void. c. Declare the method so that it takes an object and MsgArrivedEventArgs arguments. d. Create the method’s implementation so that it prints the name of the client, the Message property of MsgArrivedEventArgs, and the server object as a string to the console. 3. Add a constructor that will receive the name of the client and a reference to the server, and will connect the client with the server. a. Declare the constructor so that it takes a string argument that represents the client’s name and a GChatServer argument that specifies the server object. b. Create the constructor’s implementation. i. Store the client’s name in a private field. ii. Store the server’s reference in a private field. iii. Connect the client to the server by using the += operator to add a new delegate instance to the GChatServer’s event. The += operator’s right operand should be an instance of the delegate type GChatServer.MsgArrivedEventHandler that was instantiated with the client’s callback method: onMsgArrived.
! Extend the program’s main entry point by performing the following procedures
1. Create an instance of the GChatServer object. 2. Write to the console a line of text to indicate that the guidelines-based chat server class portion of the program is starting. 3. Instantiate three GChatClient objects, passing the client names “1”, “2”, and “3” to the constructor. 4. Send a message to the server by invoking the SendMsg method of GChatServer. The message should be sent to all clients. 5. Send a second message to all clients except client 2. 6. Write to the console a line of text to indicate that the guidelines-based event chat is finished.
Module 8: Delegates and Events
! Compile and test the program • Build the program and run it. The guidelines-based event chat output should resemble the following: ... Demo start: Guidelines Based Event Chat Server. Server: GChatServer Msg arrived (Client 1): Hi to all clients Server: GChatServer Msg arrived (Client 2): Hi to all clients Server: GChatServer Msg arrived (Client 3): Hi to all clients Server: GChatServer Msg arrived (Client 1): Hi to all clients except client 2 Server: GChatServer Msg arrived (Client 3): Hi to all clients except client 2 Server: GChatServer Demo stop: Guidelines Based Event Chat Server.
41
42
Module 8: Delegates and Events
Review Topic Objective
To reinforce module objectives by reviewing key points.
Lead-in
The review questions cover some of the key concepts taught in this module.
!
Delegates
!
Multicast Delegates
!
Events
!
When to Use Delegates, Events, and Interfaces
*****************************ILLEGAL FOR NON-TRAINER USE****************************** 1. Write the code to declare a delegate that is named ProcessOrderCallback for the following method: static public bool ProcessOrder(int Quantity, string Item) { //... } public delegate bool ProcessOrderCallback (int Quantity, string, string Item);
2. Write the code to call the following EnterOrder method, instantiating and passing an instance of the ProcessOrderCallback delegate that refers to a public static method that is named Foo in the public class Bar. static public void EnterOrder( ProcessOrderCallback processOrderCallback) { //... }; EnterOrder( new ProcessOrderCallback(Bar.Foo) );
Module 8: Delegates and Events
3. Write the body of the static method EnterOrder that takes as an argument an instance of the delegate that is declared in question 1. EnterOrder outputs to the console strings to prompt for an item name and quantity and reads in the user’s input. EnterOrder should invoke the callback delegate with this information. static public void EnterOrder( ProcessOrderCallback
processOrderCallback)
{
Console.WriteLine("Enter Item Name:"); string name = Console.ReadLine(); Console.WriteLine("Enter Item Quanity:"); int quantity = Int32.Parse( Console.ReadLine() ); processOrderCallback(quantity, name); }
4. Using the following declarations, write the code to add delegate b to a’s invocation list. delegate void MyDelegate(); MyDelegate a, b; a = new MyDelegate(Bar1.Foo1); b = new MyDelegate(Bar2.Foo2);
a += b;
5. Use the event keyword to write the code to declare a public static event for a delegate type ProcessOrderEventHandler. public static event ProcessOrderEventHandler processOrderHandler;
6. Describe when you should use a delegate and when you should use an event. Use a delegate when: • You want a C-style function pointer. • You want single callback invocation. • You want the callback function to be registered in the call or at construction time, not in a separate add method. Use an event when: • Client code signs up for the callback prior to the occurrence of events, typically through a separate add method. • More than one client object will be affected.
43
THIS PAGE INTENTIONALLY LEFT BLANK
Module 9: Memory and Resource Management Contents Overview Memory Management Basics Non-Memory Resource Management
1 2 12
Implicit Resource Management
13
Explicit Resource Management
26
Optimizing Garbage Collection
36
Lab 9: Memory and Resource Management 48 Review
55
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, place or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. 2001-2002 Microsoft Corporation. All rights reserved. Microsoft, ActiveX, BizTalk, IntelliMirror, Jscript, MSDN, MS-DOS, MSN, PowerPoint, Visual Basic, Visual C++, Visual C#, Visual Studio, Win32, Windows, Windows Media, and Window NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
Module 9: Memory and Resource Management
iii
Instructor Notes Presentation: 125 Minutes Lab: 60 Minutes
After completing this module, students will be able to: !
Describe how garbage collection manages object memory.
!
Implicitly manage non-memory resources by using a destructor’s finalize code.
!
Explicitly manage non-memory resources by using client-controlled deterministic release of resources.
!
Write code by using the temporary resource usage design pattern.
!
Programmatically control the behavior of the garbage collection.
!
Describe advanced garbage collection features.
Materials and Preparation This section provides the materials and preparation tasks that you need to teach this module.
Required Materials To teach this module, you need the Microsoft® PowerPoint® file 2349B_09.ppt.
Preparation Tasks To prepare for this module, you should: !
Read all of the materials for this module.
!
Review the animation.
!
Practice the demonstrations.
!
Complete the lab.
iv
Module 9: Memory and Resource Management
Demonstrations This section provides demonstration procedures that will not fit in the margin notes or are not appropriate for the student notes. The code for each of the following demonstrations is contained in one project and is located in \Democode\Mod09\ GARBAGE COLLECTION. In addition, the code for the individual demonstrations is provided in the student notes. Use the debugger to step through the code while you point out features and ask students what they think will happen next.
Finalization In this demonstration, you will show students how garbage collection handles finalization and resurrection. In this demonstration, run the Introduction and ResurrectionDemo methods.
The IDisposable Interface In this demonstration, you will show students how to perform explicit resource management by using the IDisposable interface. In this demonstration, run the DisposeDemo method.
Weak References In this demonstration, you will show students how garbage collection handles weak references. In this demonstration, run the WeakRefDemo method.
Generations In this demonstration, you will show students how garbage collection handles generations. In this demonstration, run the GenerationDemo method.
Multimedia This section lists the multimedia items that are part of this module. Instructions for launching and playing the multimedia are included with the relevant slides.
Simple Garbage Collection This animation will show students the Microsoft .NET Framework common language runtime’s garbage collection process without finalization.
Garbage Collection This animation will show students the .NET Framework common language runtime garbage collection process, including finalization.
Module 9: Memory and Resource Management
v
Module Strategy Use the following strategy to present this module: !
Memory Management Basics Students in your classes will probably use different approaches to memory management. Begin with a brief review of different memory management techniques that you or the students may have learned from experience. Because students will need to adapt their programming practices to the automatic memory management that is provided by the common language runtime, it is important to mention other memory management techniques. Compare and contrast manual memory management with the automatic memory management that is provided by the common language runtime. Outline the simple garbage collection process without the finalization details and use the Simple Garbage Collection animation to help the students understand the concept of the garbage collection process more easily. Instructions for running the animations in this module are included in Instructor Margin Notes.
!
Non-Memory Resource Management This topic is an introduction to handling non-memory resources implicitly and explicitly. Tell students that the next two sections cover these areas in detail. You should not spend much time on this slide.
!
Implicit Resource Management Introduce the finalization phase of the garbage collection process. Emphasize that in C#, a destructor must be used for the finalization code. The second animation, Garbage Collection, is more complex than the first. It shows the garbage collection process with the finalization details. Spend time discussing the drawbacks that are associated with finalization and what to do if finalization is required. Show students how to deal with an object that has been resurrected. Use the Finalization demonstration to highlight how garbage collection deals with finalization and resurrection.
!
Explicit Resource Management Show students how to perform explicit resource management by using the IDisposable interface and Dispose method. Discuss the temporary resource usage design pattern as an example of how to allocate resources for temporary use.
!
Optimizing Garbage Collection Use the demonstrations that are provided to show how to optimize garbage collection through weak references and generations. In addition to discussing the programmatic optimizations that can be made to the garbage collection process, briefly mention the use of performance counters to monitor memory activity and the use of a multiprocessor system to scale applications where there are garbage collection bottlenecks.
Module 9: Memory and Resource Management
1
Overview Topic Objective
To provide an overview of the module topics and objectives.
Lead-in
Objects in the Microsoft .NET Framework use memory resources and may use other resources, such as file handles. For software to run properly, these resources must be well managed.
!
Memory Management Basics
!
Non-Memory Resource Management
!
Implicit Resource Management
!
Explicit Resource Management
!
Optimizing Garbage Collection
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Objects in the Microsoft® .NET Framework use memory resources and may use other resources, such as file handles. For software to run properly, these resources must be well managed. In other words, they must be properly allocated and released. After completing this module, you will be able to: !
Describe how garbage collection manages object memory.
!
Implicitly manage non-memory resources by using a destructor’s finalize code.
!
Explicitly manage non-memory resources by using client-controlled deterministic release of resources.
!
Write code by using the temporary resource usage design pattern.
!
Programmatically control the behavior of the garbage collection.
!
Describe advanced garbage collection features.
2
Module 9: Memory and Resource Management
" Memory Management Basics Topic Objective
To provide an overview of the section topics.
Lead-in
A major feature of the .NET Framework common language runtime is that the runtime automatically handles the allocation and release of an object’s memory resources.
!
Developer Backgrounds
!
Manual vs. Automatic Memory Management
!
Memory Management of .NET Framework Types
!
Simple Garbage Collection
*****************************ILLEGAL FOR NON-TRAINER USE****************************** A major feature of the .NET Framework common language runtime is that the runtime automatically handles the allocation and release of an object’s memory resources. In most cases, automatic memory management enhances code quality and developer productivity without negatively impacting expressiveness or performance. Understanding how the .NET Framework facilitates resource management is essential for writing correct and efficient code. In this section, you will learn about memory management in the .NET Framework, including simple garbage collection.
Module 9: Memory and Resource Management
3
Developer Backgrounds Topic Objective
To discuss various developer backgrounds with regard to memory management.
!
COM #
Lead-in
Your experience with memory management will vary depending upon your development background.
!
C++ #
!
Manually implement reference counting and handle circular references Manually use the new operator and delete operator
Visual Basic #
Accustomed to automatic memory management
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Your experience with memory management will vary depending upon your development background. In certain situations, you will need to adapt your programming practices to the automatic memory management that is provided by the common language runtime.
COM Developers COM developers are accustomed to implementing reference counting as a manual memory management technique. Each time an object is referenced, a counter is incremented. When a reference to an object goes out of scope, the counter is decremented. When an object’s reference count reaches zero, the object is terminated and its memory is freed. The reference counting scheme is the source of many bugs. If the reference counting rules are not followed precisely, objects may be freed prematurely or unreferenced objects may accumulate in memory. Circular references are also a common source of bugs. A circular reference occurs when a child object has a reference to a parent object, and the parent object has a reference to the child object. Circular references prevent either object from being released or destroyed. The only solution is for the parent and child objects to agree on a fixed pattern of usage and destruction, such as where the parent always deletes the child first. When you develop applications in a managed language, the runtime’s garbage collector eliminates the need for reference counting and, as a result, the bugs that can arise from this manual memory management scheme.
4
Module 9: Memory and Resource Management
C++ Developers C++ developers are accustomed to the tasks that are related to manual memory management. In C++, when you allocate memory for an object by using the new operator, you must release the object’s memory by using the delete operator. This can lead to errors such as forgetting to release an object and causing a memory leak, or attempting to access memory for an object that has already been released. When you develop applications by using the Managed Extensions for C++, or another managed language, you do not have to use the delete operator to release an object. The garbage collector does this for you automatically when the object is no longer being used by the application. C++ developers may be accustomed to avoiding the use of short-term objects because of the associated cost of manually managing the memory for these objects. For managed short-term objects that are created and then go out of scope between collections, the cost of allocating and releasing memory is extremely low. In the .NET Framework, the garbage collector is actually optimized to manage objects with short lifetimes. When you develop managed applications, it is appropriate to use short-term objects in situations where they simplify your code.
Visual Basic Developers Microsoft Visual Basic® developers are accustomed to automatic memory management. If you are a Visual Basic developer, the programming practices with which you are familiar apply to the majority of the managed objects that you create in the .NET Framework. However, you should take special note of the suggested design pattern for a Dispose method to use when you create or use objects that encapsulate unmanaged resources.
Module 9: Memory and Resource Management
5
Manual vs. Automatic Memory Management Topic Objective
To introduce the advantages of automatic memory management in the .NET Framework.
!
Manual Memory Management #
Lead-in
!
Manual memory management requires that you manage the allocation and deallocation of blocks of memory.
!
Programmer manages memory
Common Problems #
Failure to release memory
#
Invalid references to freed memory
.NET Runtime Provides Automatic Memory Management #
Eases programming task
#
Eliminates a potential source of bugs
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Manual memory management requires that you manage the allocation and deallocation of blocks of memory. The NET Framework common language runtime provides automatic memory management so that you are freed from this time-consuming and difficult task.
Manual Memory Management The following table provides examples of manual memory management in different programming languages, C and C++, and in COM. Language or environment
Example of manual memory management
C
malloc and free functions
C++
new and delete operators
COM
AddRef and Release reference counting methods
Automatic Memory Management in the .NET Framework The .NET Framework common language runtime automatically handles managed object memory and manages references to these objects, releasing managed objects when they are no longer being used. This automatic memory management eliminates the possibility of programming errors that can cause memory leaks and the use of memory that has already been freed. With automatic memory management, you no longer have to deal with complex bugs that are associated with reference counting, circular reference leaks, or dangling references.
6
Module 9: Memory and Resource Management
Memory Management of .NET Framework Types Topic Objective
To explain how a value’s type affects how the value is managed in memory.
!
Lead-in
Instances of Value Types Use Stack Memory #
In .the NET Framework, all values have a type, which may be a value type or a reference type.
!
Allocation and deallocation are automatic and safe
Managed Objects Are Reference Types and Use Heap Memory #
Created by calls to the new operator
#
Freed by garbage collection
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In the .NET Framework, all values have a type, which may be a value type or a reference type. Each value’s type affects how that value is managed in memory.
Instances of Value Types Instances of value types are stored in memory that is allocated on the stack. Allocation and deallocation of memory occur automatically as follows: !
Allocation Memory for an instance of a value type is created when the activation record for its scope is pushed on to the stack.
!
Deallocation Memory is deallocated when the scope’s activation record, which contains the value type instance, is popped from the stack.
Value types are always accessed directly. You cannot create a reference to a value type, and therefore you cannot refer to a value instance that has been deallocated. As a result, there is no danger of creating a dangling reference to a value type.
Module 9: Memory and Resource Management
7
Instances of Reference Types Managed objects are reference types, which you create by calls to the new operator. The memory of these objects is allocated in the common language runtime managed heap. You can access reference types only through a reference to that storage. The use of references enables garbage collection to track outstanding references to a particular instance and to free that object’s heap memory when appropriate. A managed object’s heap memory is only released through garbage collection when there are no reachable references to that object. This mechanism ensures that there will be no invalid references to the object’s freed memory and thus no dangling references.
8
Module 9: Memory and Resource Management
Simple Garbage Collection Topic Objective
To explain how simple garbage collection works in the .NET Framework.
!
Lead-in
Garbage collection is triggered when an application creates an object, and there is not enough space left in the heap to provide memory for the object. !
Simple Garbage Collection Algorithm #
Wait until managed code threads are in a safe state
#
Build a graph of all reachable objects
#
Move reachable objects to compact heap - Unreachable objects’ memory is reclaimed
#
Update references to all moved objects
Reference Cycles Are Handled Automatically
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Typically, garbage collection is triggered when an application creates an object, and there is not enough space left in the heap to provide memory for the object. Alternatively, you can invoke garbage collection programmatically. This process is discussed in Optimizing Garbage Collection in this module.
The Garbage Collection Algorithm Whether garbage collection occurs automatically or you invoke it programmatically, the garbage collection algorithm is used to find any objects in the heap whose memory can be reclaimed. Such objects include objects that are no longer being used by the application. If garbage collection can reclaim enough objects to free sufficient memory, memory for the new objects can be allocated. Otherwise, an OutOfMemoryException is thrown. For the sake of simplicity, this topic describes the simple garbage collection process. The finalization phase and optimization details are discussed in Implicit Resource Management in this module.
Simple Garbage Collection Process Simple garbage collection uses the following process: 1. Waits until other managed threads reach a safe state, for example, suspended. The garbage collection process modifies managed objects and their references. Therefore it must first wait until other managed threads are suspended. 2. Builds a graph of all reachable objects. 3. Compacts the heap by moving reachable objects. By moving reachable objects down in the heap, garbage collection reclaims the space in the heap that was used by unreachable objects. 4. Updates all application references to moved objects.
Module 9: Memory and Resource Management
9
Building the Graph of Reachable Objects Garbage collection accesses the collection of root references that are maintained by the runtime. Each application has a logical collection of root references. The collection contains all of the managed object references from global and static objects and local variables that are currently on the stack and in CPU registers. To build the graph of reachable objects, garbage collection performs the following actions. 1. It adds all of the objects that are referenced by each root reference. 2. It recursively adds objects that are referenced by any added object. Before an object is added to the graph, garbage collection checks to ensure that the object is not already in the graph. This check prevents garbage collection from entering an infinite loop that is caused by circular references. At the end of the process, any object that is not in the reachable object graph is considered unreachable and therefore garbage.
Reference Cycles Handled Automatically An object is reachable only if there is a path from a root reference to that object. Therefore, reference cycles between unreachable objects will not prevent the objects’ memory from being released by the garbage collection process. For example, if A references B and B references A, then both objects will be garbage collected when they are no longer reachable from a root reference.
10
Module 9: Memory and Resource Management
Multimedia: Simple Garbage Collection Topic Objective
To illustrate the .NET Framework garbage collection process.
Lead-in
This animation illustrates the .NET Framework common language runtime’s garbage collection process.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** To launch the animation, click the button in the lower left corner of the slide. To play the animation, click the Simplified Garbage Collection button at the top of the screen, and then click the play button in the lower left corner of the screen.
This animation illustrates the .NET Framework common language runtime garbage collection process. Note Compiler optimization is disabled in this scenario to prevent an object from becoming eligible for garbage collection earlier than would be expected. Otherwise, the compiler could optimize away assignments to local variables that are never observed by a later read. This optimization could result in an object being subject to garbage collection before its reference is assigned to null. !
The common language runtime allocates memory resources for reference type objects in the section of the application’s memory that is called the managed heap.
!
When there is insufficient space in the managed heap, the common language runtime executes the garbage collection algorithm to remove objects that are no longer being used by the application.
Module 9: Memory and Resource Management
11
The steps of the algorithm are as follows: 1. After all other managed threads reach a safe state, for example suspended, garbage collection builds a graph of all of the objects that are reachable from the root references. 2. Before an item is added to the graph, a check is made to ensure that the object is not already in the graph. This check ensures that circular references are handled without garbage collection entering an infinite loop. 3. After all of the root references and added objects have been processed, any objects that are not in the graph are not reachable by the application. The memory for these objects can be reclaimed when the heap is compacted. 4. The remaining objects are moved down in the heap to fill the gaps. 5. The garbage collection process must then update all references to the moved objects.
12
Module 9: Memory and Resource Management
Non-Memory Resource Management Topic Objective
To provide an overview of non-memory resource management, which is discussed in the following topics.
!
Implicit Resource Management
!
Explicit Resource Management
Lead-in
Managed objects sometimes encapsulate control over resources that are not managed by the runtime.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** For Your Information
This topic is an introduction to handling non-memory resources implicitly and explicitly. Tell students that the next two sections cover these areas in detail. You should not spend much time on this slide.
Managed objects sometimes encapsulate control over resources that are not managed by the runtime. Examples of these non-memory resources include window handles, file handles, and database connections. You need implicit and explicit ways to free these resources. Garbage collection provides implicit resource management of an object by calling the object’s finalize code. The client of an object provides explicit resource management by calling the Dispose method on the IDisposable interface of the object when the client is finished using the object.
Module 9: Memory and Resource Management
13
" Implicit Resource Management Topic Objective
To provide an overview of the section topics.
Lead-in
The .NET Framework common language runtime provides the means for notifying an object before it is destroyed so that it can clean up and release nonmemory resources.
!
Finalization
!
Garbage Collection with Finalization
!
Finalization Guidelines
!
Controlling Garbage Collection
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The .NET Framework common language runtime provides the means for notifying an object before it is destroyed so that it can clean up and release nonmemory resources. In this section, you will learn how to take advantage of this feature.
14
Module 9: Memory and Resource Management
Finalization Topic Objective
To define and describe finalization in garbage collection.
Lead-in
Implicit management of resources ensures that an object can properly clean up its resources at some time in the future when there are no longer any valid references to the object.
!
Finalize Code Called by Garbage Collection
!
In C#, the Finalize Code Is Provided by a Destructor
!
Use C# Destructor to Implicitly Close a FileStream
class class Foo Foo {{ private private System.IO.FileStream System.IO.FileStream fs; fs; //... //... public public Foo() Foo() {{ fs fs == new new System.IO.FileStream( System.IO.FileStream( "bar", "bar", FileMode.CreateNew); FileMode.CreateNew); }} ~Foo() ~Foo() {{ fs.Close(); fs.Close(); }} }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Implicit management of resources ensures that an object can properly clean up its resources at some time in the future when there are no longer any valid references to the object. If an object provides finalize code in its destructor, the garbage collection process calls this code when there are no longer any valid references to the object. This phase of garbage collection is referred to as finalization. Finalization allows an object to properly cleanup before its memory resources are freed.
Using a Destructor for Finalization In C#, the Finalize method is not directly accessible, and you cannot call or override the Finalize method. You must place code to be executed during finalization inside a C# destructor. The syntax for a C# destructor is the tilde operator (~), followed by the class name and a block of statements that will be executed during finalization. The following example shows the C# destructor syntax for a class named Foo. ~Foo() { // ... perform some cleanup operation here }
Module 9: Memory and Resource Management
15
This code implicitly translates to the following: protected override void Finalize() { try { // ... do something } finally { base.Finalize(); } }
Destructors are not inherited. When the finalization code of an object is executed and the object is destructed, the destructors in the inheritance chain of that object are called in order, from the most derived destructor to the least derived destructor.
Differences Between C# and C++ Destructors C# destructors and C++ destructors differ in several important ways, as shown in the following table. Destructor
Characteristics
C# destructors
Execute non-deterministically Automatically invoked at any time after an object becomes eligible for garbage collection Not guaranteed to run in any specific order, even if one object contains or refers to another Cannot be invoked explicitly
C++ destructors
Execute deterministically Run in the order they are called Can be invoked explicitly
An Example of Implicit Resource Management A class Foo has a constructor that creates a FileStream. To ensure that the buffer of the FileStream object is properly flushed, the class Foo provides implicit management of the resource through destructor code that closes the FileStream, as shown in the following code: class Foo { private System.IO.FileStream fs; //... public Foo() { fs = new System.IO.FileStream("bar", FileMode.CreateNew); } ~Foo() { fs.Close(); } }
Implicit management of resources may not be adequate in all circumstances. In the preceding example, full access by other objects to the file that is opened by a Foo object may be delayed for an indeterminate amount of time after the Foo object no longer needs the resource. Therefore, you typically need to use an explicit form of resource management. For more information about explicit resource management, see Explicit Resource Management in this module.
16
Module 9: Memory and Resource Management
Garbage Collection with Finalization Topic Objective
To describe the initial process of garbage collection with finalization.
Lead-in
Finalization adds to the complexity and increases performance overhead of the basic garbage collection process, as it was described in Simple Garbage Collection in this module.
!
Runtime Maintains a List of Objects That Require Finalization #
Finalization queue
!
Garbage Collection Process Invoked
!
Unreachable Objects Requiring Finalization
!
#
References added to freachable queue
#
Objects are now reachable and not garbage
Move Reachable Objects to Compact the Heap #
!
Unreachable objects' memory is reclaimed
Update References to All Moved Objects
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Finalization adds to the complexity and increases performance overhead of the basic garbage collection process, as it was described in Simple Garbage Collection in this module. The .NET Framework common language runtime maintains a list of managed objects that require finalization. This list is known as the finalization queue and is used during garbage collection to provide implicit resource management. Garbage collection is typically invoked when the creation of a new object requires more space in the managed heap than is currently available. After waiting for all other managed threads to be suspended, garbage collection with finalization proceeds as follows: 1. Garbage collection builds a graph of all reachable objects, as described in Simple Garbage Collection in this module. Any managed object that is not in the graph is unreachable. 2. Garbage collection checks the finalization queue to see if an unreachable object requires finalization. If an unreachable object requires finalization, it is removed from the finalization queue, and a reference to the object is placed in the freachable (pronounced F-reachable) queue. The freachable queue is part of the root references of an application. The object is therefore now considered reachable and is no longer garbage. 3. Garbage collection compacts the heap and updates references to all moved objects. At this point, the memory resources for the unreachable objects have been freed. 4. Garbage collection allows the application to continue normal operation. At this point, the finalization phase of the garbage collection process can commence on a separate thread.
Module 9: Memory and Resource Management
17
Garbage Collection with Finalization (continued) Topic Objective
To describe the latter part of the process of garbage collection with finalization.
Lead-in
After the memory resources for the unreachable objects are freed and the application has continued normal operation, the finalization phase of garbage collection commences on a separate thread.
!
Finalize Thread Runs #
Executes freachable objects' Finalize methods
#
References removed from freachable queue
#
Unless resurrected, objects are now garbage
#
May be reclaimed next time garbage collection occurs
*****************************ILLEGAL FOR NON-TRAINER USE****************************** After the memory resources for the unreachable objects are freed and the application has continued normal operation, the finalization phase of garbage collection commences on a separate thread.
The Finalization Phase As the application runs, a special runtime thread removes references to objects from the freachable queue and calls the finalize code of those objects. Therefore, it is important that this code does not depend upon the identity of the thread. For example, the method should not depend on thread local storage. After an object has been finalized and removed from the freachable queue, it becomes unreachable again as long as that object has not been resurrected. An object is resurrected if it becomes reachable from an application root after previously being unreachable. However, the object’s memory resources are not freed at this time. The reclamation of the object’s memory resources must wait until garbage collection occurs next.
18
Module 9: Memory and Resource Management
Resurrection Resurrection of an object occurs when a previously unreachable object becomes reachable from an application root during finalization. For example, the finalize code for an object may assign to a global or static variable a reference to the object itself. The object is now reachable and is not subject to garbage collection.
Issues with Resurrection You should avoid resurrection when possible because the object’s finalize code has been called and may have released resources that are required for the object’s proper operation, even if the object’s memory is valid. For example, when the destructor of the Foo class that was shown in An Example of Implicit Resource Management in this module executes its finalization code, the file will close, and the other methods of Foo that require an open FileStream may not be able to successfully complete. In addition, when a resurrected object becomes unreachable sometime in the future, its finalize code will not be called unless the object has called the GC.ReRegisterForFinalize method. You should also note that even if the finalize code of a class does not resurrect an object, that object may still be resurrected, as when another object that refers to the object is resurrected. Thus, all objects should be able to handle resurrection. For more information about resurrection and finalization, see Controlling Garbage Collection in this module.
Module 9: Memory and Resource Management
19
Multimedia: Garbage Collection Topic Objective
To illustrate how garbage collection, including finalization, works.
Lead-in
This animation illustrates the .NET Framework common language runtime garbage collection process, including finalization. To launch the animation, click the button in the lower left corner of the slide. To play the animation, click the play button in the lower left corner of the screen.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This animation illustrates the .NET Framework common language runtime garbage collection process, including finalization. Note Compiler optimization is disabled in this scenario to prevent an object from becoming eligible for garbage collection earlier than would be expected. Otherwise, the compiler could optimize away assignments to local variables that are never observed by a later read. This optimization could result in an object being subject to garbage collection before its reference is assigned to null. In this animation, you will learn that: !
The .NET Framework common language runtime allocates memory resources for objects in the section of the application’s memory that is called the managed heap.
!
The finalization queue holds references to objects whose classes require finalization.
!
The freachable queue is a special kind of root reference whose entries are references to objects that are ready to have their finalize code invoked. An freachable reference keeps the object alive.
!
The nondeterministic release of memory and non-memory resources is another significant difference between .NET Framework and C++ destructors.
20
Module 9: Memory and Resource Management
Finalization Guidelines Topic Objective
To alert students to issues that are associated with finalization.
!
Lead-in
This topic provides guidelines for handling finalization. !
!
Avoid Finalization and Destructors If Possible #
Performance costs
#
Complexity
#
Delay of memory resource release
If You Require Finalization, Finalize Code Should: #
Avoid calling other objects
#
Avoid making assumptions about thread ID
Classes with Finalization Should: #
Avoid making references to other objects
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This topic provides guidelines for handling finalization. To avoid problems that result from the .NET Framework’s nondeterministic ordering of calls to finalize code, you may need to use explicit resource management in addition to careful design.
Avoid Finalization if Possible You should only implement finalization, or implement a destructor in C#, on classes that require finalization. If your class has only managed references and does not have to manage non-memory resources, you should not implement finalize code. Finalization adds overhead and complexity, and delays the reclamation of an object’s memory resources.
Implementing Finalization If you must implement finalization, you should obey the following guidelines: !
Avoid calling other objects in finalization code. In your finalization code, free any external resources that your object is holding on to. However, you should avoid calling other objects, for example, contained objects, because their finalize code may have already been called. The .NET Framework common language runtime does not specify any order on its invocation of the finalize code of freachable objects. Therefore, if an object of type Foo refers to an object of type Bar, you cannot know whether the finalize code of Foo will be called before or after the finalize code of Bar. This nondeterminism may cause problems if the finalize code of Foo calls a method in Bar that requires a resource that is released by Bar in its finalize code.
!
Avoid assumptions about thread ID. As previously noted, finalization code should not make any assumptions about the thread ID.
Module 9: Memory and Resource Management !
21
Ensure that the Finalize code of your base class is called. This call is performed automatically by the C# destructor syntax.
!
Avoid references to other objects. A class that requires finalization should avoid references to other objects because a finalizable object will prolong its own lifetime and the lifetime of objects that it references. If possible, you should factor such classes into the following two classes: • One class that contains the resource that requires management and has the finalize code but has no other object references. • A second class that holds the references to other objects but has no finalize code.
22
Module 9: Memory and Resource Management
Controlling Garbage Collection Topic Objective
To introduce techniques to control garbage collection.
Lead-in
The System.GC class of the .NET Framework contains methods that can be used to control the garbage collection process.
!
To Force Garbage Collection
void void System.GC.Collect(); System.GC.Collect(); !
To Suspend Calling Thread Until Thread’s Queue of Finalizers Is Empty
void void System.GC.WaitForPendingFinalizers(); System.GC.WaitForPendingFinalizers(); !
To Allow a Finalized Resurrected Object to Have Its Finalizer Called Again
void void System.GC.ReRegisterForFinalize(object System.GC.ReRegisterForFinalize(object obj); obj); !
To Request the System Not to Call the Finalizer Method
void void System.GC.SuppressFinalize(object System.GC.SuppressFinalize(object obj); obj);
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The System.GC class of the .NET Framework contains methods that can be used to control garbage collection. The Collect method with no arguments forces the collection of all generations, as in the following code: void System.GC.Collect();
For more information about generations, see Generations in this module. Finalizers are run on a separate thread of execution. When the WaitForPendingFinalizers method is called, the current thread is suspended until the queue of finalizers that are waiting to run is empty. Because the running of finalizers may trigger another garbage collection, which may, in turn, re-queue new finalizers, there is no guarantee that the call to WaitForPendingFinalizers will terminate. void System.GC.WaitForPendingFinalizers();
After the garbage collection process calls an object’s finalize code, garbage collection assumes that there is no need to call it again. However, if an object is resurrected, the ReRegisterForFinalize method may be called to force garbage collection to call the object’s finalize code again the next time the object is destroyed. Note that if ReRegisterForFinalize is called multiple times, the object’s finalize code will also be called multiple times. void System.GC.ReRegisterForFinalize(object obj);
If an object that has finalize code no longer requires finalization to manage its resources, the object may call the SuppressFinalize method to improve performance. For example, an object that supports explicit resource management should call the SuppressFinalize method when it releases its resources, as in the following code: void System.GC.SuppressFinalize(object obj);
Module 9: Memory and Resource Management
23
Demonstration: Finalization Topic Objective
To demonstrate how garbage collection handles finalization and resurrection.
Lead-in
This demonstration shows how garbage collection handles finalization and resurrection.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This demonstration shows how garbage collection handles finalization and resurrection. For Your Information
Use the debugger to step through the code while you point out features and ask students what they think will happen next. In this section run the Introduction and ResurrectionDemo methods.
// This method demonstrates how the GC works. private static void Introduction() { Display(0, "\n\nDemo start: Introduction to Garbage Collection.", +1); // Create a new DerivedObj in the managed heap // Note: Both BaseObj and DerivedObj constructors are called DerivedObj obj = new DerivedObj("Introduction"); obj = null; // We no longer need this object // The object is unreachable so forcing a GC causes it to be // finalized. Collect(); // Wait for the GC's Finalize thread to finish // executing all queued Finalize code. WaitForFinalizers(); // // // //
NOTE: The GC calls the most-derived (farthest away from the Object base class) destructor. A C# destructor automatically calls its base class destructor.
(Code continued on the following page.)
24
Module 9: Memory and Resource Management // This is the same test as above with one slight variation obj = new DerivedObj("Introduction"); // obj = null; // Variation: this line is commented out Collect(); WaitForFinalizers(); // If compiler optimization was turned on: // Notice that we get identical results as above: // the destructor’s Finalize code // runs because the just in time compiler’s optimizer // knows that obj is not referenced later in this function // Now we explicitly release the object and the destructor’s // Finalize code is run. // If compiler optimization was turned off // we now see the finalization called otherwise nothing. obj = null; Collect(); WaitForFinalizers(); Display(-1, "Demo stop: Introduction to Garbage Collection.", 0); }
// Resurrection // This reference is accessed in the ResurrectObj.Finalize // code and is used to create a strong reference to an // object (resurrecting it). static public ResurrectObj ResObjHolder; // Defaults to null // This method demonstrates how the GC supports resurrection. // NOTE: Resurrection is discouraged. private static void ResurrectionDemo() { Display(0, "\n\nDemo start: Object Resurrection.", +1); // Create a ResurrectObj ResurrectObj obj = new ResurrectObj("Resurrection"); // Destroy all strong references to the new ResurrectionObj obj = null; // Force the GC to determine that the object is unreachable. Collect(); WaitForFinalizers(); // You should see the Finalize code called. // // // //
However, the ResurrectObj's Finalize code resurrects the object keeping it alive. It does this by placing a reference to the dying-object in Application.ResObjHolder
// You can see that ResurrectObj still exists because // the following line doesn't raise an exception. ResObjHolder.Display("Still alive after Finalize called");
(Code continued on the following page.)
Module 9: Memory and Resource Management
25
// Prevent the ResurrectObj object from resurrecting // itself again, ResObjHolder.SetResurrection(false); // Now, let's destroy this last reference to the // ResurrectObj ResObjHolder = null; // Force the GC to determine that the object is unreachable. Collect(); WaitForFinalizers(); // You should see the Finalize code called. Display(-1, "Demo stop: Object Resurrection.", 0); }
26
Module 9: Memory and Resource Management
" Explicit Resource Management Topic Objective
To provide an overview of the section topics.
Lead-in
Your classes should provide an explicit and an implicit way to free resources.
!
The IDisposable Interface and the Dispose Method
!
Temporary Resource Usage Design Pattern
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Typically, your classes should provide an explicit and an implicit way to free resources. A class provides explicit control by having a method that a client of the object will call when the client has finished using the object. In this section, you will learn how to perform explicit resource management by using the IDisposable interface and Dispose method, and how to allocate resources for temporary use.
Module 9: Memory and Resource Management
27
The IDisposable Interface and the Dispose Method Topic Objective
To explain how to use the IDisposable interface and the Dispose method when working with explicit resource management.
!
Inherit from the IDisposable Interface
!
Implement the Dispose Method
Lead-in
!
Follow the .NET Framework SDK’s Design Pattern
Classes that contain nonmemory resources should provide explicit resource management by inheriting from the IDisposable interface and implementing its Dispose method.
class class ResourceWrapper ResourceWrapper :: IDisposable IDisposable {{ // // see see code code example example for for details details }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Classes that contain non-memory resources should provide explicit resource management by inheriting from the IDisposable interface and implementing its Dispose method. A type’s Dispose method should release all of the resources that it owns. It should also release all resources owned by its base types by calling its parent type’s Dispose method. The parent type’s Dispose method should release all resources that it owns and in turn call its parent type’s Dispose method, propagating this pattern through the hierarchy of base types. To ensure that resources are always cleaned up appropriately, a Dispose method should be safely callable multiple times and should never throw an exception. For Your Information
The IDisposable interface and Dispose method are important topics that the students will need to know to do the lab. The ResourceWrapper design pattern should be discussed in detail.
A Dispose method should call the GC.SuppressFinalize method for the object it is disposing. If the object is currently on the finalization queue, GC.SuppressFinalize prevents its Finalize code from being called. Remember that executing Finalize code is costly to performance. If your Dispose method has already done the work to clean up the object, then it is not necessary for the garbage collector to call the object’s Finalize code. The following code example from the .NET Framework Software Development Kit (SDK) illustrates one possible design pattern for implementing a Dispose method for classes that encapsulate unmanaged resources. You may find this pattern convenient to use because it is implemented throughout the .NET Framework. However, this is not the only possible implementation of a Dispose method. The base class implements a public Dispose method that can be called by users of the class. The Dispose method in turn calls the appropriate virtual Dispose method, depending upon the identity of the caller. The appropriate cleanup code for the object is executed in the virtual Dispose method. The base class provides a destructor as a safeguard in the event that Dispose is not called, as in the following example of the ResourceWrapper design pattern:
28
Module 9: Memory and Resource Management // Design pattern for a base class public class BaseResource: IDisposable { // Pointer to an external resource. private IntPtr handle; // Pointer to a managed object resource this class uses. private Component Components; // To track whether Dispose has been called. private bool disposed = false; // Constructor for the BaseResource Object. public BaseResource() { handle = // Insert code here to allocate on the // unmanaged side. Components = new Component(); } // Implement IDisposable. public void Dispose() { Dispose(true); // Take yourself off of the Finalization queue. GC.SuppressFinalize(this); } // The virtual Dispose(bool) method is called by: // IDisposable.Dispose() with disposing = true // and by Finalize/C# Destructor with disposing = false protected virtual void Dispose(bool disposing) { // Check to see if Dispose has already been called. if(!this.disposed) { // If IDisposable.Dispose was called, // dispose all managed resources. if(disposing) { // Free other state (managed objects) Components.Dispose(); } // If Finalize or IDisposable.Dispose, // free your own state (unmanaged objects) this.disposed = true; Release(handle); handle = IntPtr.Zero; // Set any large fields to null } } // // // //
Use C# destructor syntax for finalization code. This destructor will run only if the Dispose method does not get called. It gives your base class the opportunity to finalize.
(Code continued on the following page.)
Module 9: Memory and Resource Management ~BaseResource() { Dispose(false); } // Allow your Dispose method to be called multiple times, // but throw an exception if a method requires resources // that were released because an object has been disposed. public void DoSomething() { // This method requires resources that may have been // disposed, // check to see if it has been disposed. if(this.disposed) { throw new ObjectDisposedException("BaseResource"); } } }
// Design pattern for a derived class public class MyResourceWrapper: BaseResource { private bool disposed = false; public MyResourceWrapper() { // Constructor for this object. } protected override void Dispose(bool disposing) { if(!this.disposed) { if(disposing) { // Release any managed resources here. } // Release any unmanaged resources here. this.disposed = true; // Call Dispose on your base class. base.Dispose(disposing); } } } // This derived class does not have Finalize code // or a Dispose method because it inherits them from // the base class.
29
30
Module 9: Memory and Resource Management
The following summarizes the way this design pattern works. Whenever an object’s Finalize code or Dispose method is called it is always the object’s base class’ implementation of these methods that is invoked. In the base class both the Finalize code and Dispose method call the virtual Dispose method, and this causes the code in the object’s actual class to be invoked. The virtual Dispose method cleans up its class specific resources. The bool flag indicates whether the call originated from Finalize or Dispose code. If finalization is occurring the virtual Dispose method should not call methods in other objects as these other objects may have already been finalized. The virtual Dispose method then calls its parent’s class virtual Dispose method allowing the parent class’ method to release its class specific resources. This calling of the parent virtual Dispose methods continues until the base class’ virtual Dispose method is called. This design pattern allows each class in the object’s class hierarchy to release its class specific resources.
Guidelines for Working with Explicit Resource Management The preceding example of the ResourceWrapper design pattern highlights some important guidelines that you should follow when working with explicit resource management. !
Suppress finalization of your instance after Dispose has been called: void System.GC.SuppressFinalize(Object obj);
!
• For example, a TextReader object holds on to a Stream object and an Encoding object, but the clients of the TextReader object are unaware of the Stream object and the Encoding object. You can assume that both the Stream and the Encoding objects have acquired external resources. When a client calls Dispose on the TextReader object, the TextReader object should in turn call Dispose on the Stream and the Encoding objects, thus causing them to release their external resources.
For Your Information
Students will need to understand the concept of containment hierarchies and how this relates to the Dispose method to do the lab. Illustrate this important concept with an example or two.
Propagate the Dispose method through containment hierarchies. Dispose should dispose of all resources that are held by an object and any object that is contained by that object.
!
Do not assume that the Dispose method will be called. As a precaution, you should also release resources in the destructor. • In finalization code, or code that is called by finalization, for example, freeState, do not propagate the Dispose or Finalize method calls through the containment hierarchy of the object because these contained objects may have been finalized already. For the same reason, you should generally avoid making calls to other objects from within code that may execute during finalization.
For Your Information
Students will need to be able to determine which methods require resources that may have been disposed to be able to do the lab. Illustrate this important concept with an example or two.
!
Throw an ObjectDisposedException when the methods of your class that depend upon resources that have been disposed are called.
!
Consider not having your object be fully usable after calling Dispose. It is often difficult to recreate an object that has already been disposed.
!
Allow your Dispose method to be called more than once without throwing an exception. It is a no-op after the first call.
Module 9: Memory and Resource Management
Using a Domain-Specific Name For Your Information
Students will need to implement a domain-specific name in the lab.
Occasionally, a domain-specific name is more appropriate than the Dispose method. For example, a file encapsulation may want to use the name Close. In this case, you should implement Dispose and have the Close method call it, as shown in the following design pattern: Note You can replace Close with a method name appropriate to your domain. // Do not make this method virtual. // A derived class should not be allowed // to override this method. public void Close() { // Call the Dispose method with no parameters. Dispose(); }
31
32
Module 9: Memory and Resource Management
Demonstration: The IDisposable Interface Topic Objective
To demonstrate how to perform explicit resource management by using the IDisposable interface.
Lead-in
This demonstration shows how to perform explicit resource management by using the IDisposable interface.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This demonstration shows how to perform explicit resource management by using the IDisposable interface. For Your Information
Use the debugger to step through the code while you point out features and ask students what they think will happen next. In this section run the DisposeDemo method.
Note The DisposeObj class in the demonstration does not contain any managed resources that have Dispose methods. Therefore, it can use an implementation of the Dispose method that is simpler than the more general design pattern. // This method demonstrates how to implement a type that // allows its users to explicitly dispose/close the object. // For many objects this paradigm is strongly encouraged. private static void DisposeDemo() { Display(0, "\n\nDemo start: Disposing an object versus Finalize.", +1); DisposeObj obj = new DisposeObj("Explicitly disposed"); // Explicitly cleanup this object, Finalize should run obj.Dispose(); obj = null; Collect(); // Finalize should NOT run (it was suppressed) WaitForFinalizers(); obj = new DisposeObj("Implicitly disposed"); obj = null; Collect(); // No explicit cleanup, Finalize SHOULD run WaitForFinalizers(); Display(-1, "Demo stop: Disposing an object versus Finalize.", 0); }
Module 9: Memory and Resource Management
33
Temporary Resource Usage Design Pattern Topic Objective
To explain how to allocate, use, and dispose of a resource in a short period of time.
Lead-in
In a temporary resource use scenario, you allocate, use, and dispose of a resource in a short period of time.
!
Temporary Resource Use #
!
Allocate a resource, use it, and dispose of it
Try and Finally
void void DoSomething() DoSomething() {{ Resource Resource rr == new new Resource(...); Resource(...); try { r.Foo(); try { r.Foo(); }} finally finally {{ if if (r (r != != null) null) ((IDisposable)r).Dispose(); ((IDisposable)r).Dispose(); }} }} !
Using Statement
using using (Resource (Resource r1 r1 == new new Resource()) Resource()) {{ r1.Foo(); r1.Foo(); }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In a temporary resource use scenario, you allocate, use, and dispose of a resource in a short period of time. The best way to ensure that the resource is disposed of, regardless of whether an exception is thrown, is to use a try and finally block. In the following example, a method named DoSomething needs to temporarily use an object of class Resource where Resource implements the IDisposable interface according to the guidelines that were specified in the preceding topic. void DoSomething() { Resource r = new Resource(...); // acquire resource try { r.Foo(); // use resource } finally { // release resource if (r != null) ((IDisposable)r).Dispose(); } }
34
Module 9: Memory and Resource Management
Handling Nested Try and Finally Blocks The syntax in the preceding example becomes even more awkward when more than one resource is used, and the try and finally blocks require nesting, as in the following example: Resource r1 = new Resource(); try { Resource r2 = new Resource(); try { r1.Foo(); r2.Foo(); } finally { r2.Dispose(); } } finally { r1.Dispose(); }
C# provides the following using statement to simplify the syntax: using-statement: using ( resource-acquisition ) embedded-statement resource-acquisition: local-variable-declaration expression The using statement requires that the type of the resource acquisition be a type that implements System.IDisposable. Local variables that are declared in a resource acquisition are read-only and must include an initializer. In the following example, Resource is a reference type that implements IDisposable: using (Resource r1 = new Resource()) { r1.Foo(); }
This statement is semantically equivalent to the following statement: Resource r1 = new Resource(); try { r1.Foo(); } finally { if (r1 != null) ((IDisposable)r1).Dispose(); }
Module 9: Memory and Resource Management
The following example: using (Resource r1 = new Resource(), Resource r2 = new Resource()) { r1.Foo(); r2.Foo(); }
is semantically equivalent to: using (Resource r1 = new Resource()) using (Resource r2 = new Resource()) { r1.Foo(); r2.Foo(); }
By expansion, the preceding example is semantically equivalent to: Resource r1 = new Resource(); try { Resource r2 = new Resource(); try { r1.Foo(); r2.Foo(); } finally { if (r2 != null) ((IDisposable)r2).Dispose(); } } finally { if (r1 != null) ((IDisposable)r1).Dispose(); }
35
36
Module 9: Memory and Resource Management
" Optimizing Garbage Collection Topic Objective
To provide an overview of the section topics.
Lead-in
In the .NET Framework, garbage collection has several advanced features that you can use to improve the performance of your software.
!
Weak References
!
Generations
!
Additional Performance Features
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The .NET Framework common language runtime garbage collection has several advanced features that you can use to improve the performance of your software. In addition to optimizing garbage collection programmatically, you can use the Performance Monitor tool (Perfmon.exe) to tune your application and build in multiprocessor support to enhance performance.
Module 9: Memory and Resource Management
37
Weak References Topic Objective
To introduce the use of weak references to conserve memory resources.
Lead-in
A weak reference allows garbage collection to collect objects if memory in the managed heap is low.
!
A Weak Reference Allows an Object to Be Collected If Memory Is Low
Object Object obj obj == new new Object(); Object(); // // create create strong strong reference reference WeakReference wr = new WeakReference(obj); WeakReference wr = new WeakReference(obj); obj obj == null; null; // // remove remove strong strong reference reference // // ... ... obj obj == (Object) (Object) wr.Target; wr.Target; if (obj if (obj != != null) null) {//garbage {//garbage collection collection hasn’t hasn’t occurred occurred // ... // ... }} else else {// {// object object was was collected, collected, reference reference is is null null //... //... }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** A weak reference to an object allows garbage collection to collect that object if memory in the managed heap is low. Weak references are useful in an application that has large amounts of easily reconstructed data. If the weak reference’s object has not had its memory resources released by garbage collection, then the application can avoid the cost of reconstructing the data.
Strong References vs. Weak References In the common language runtime, the garbage collection process reclaims inaccessible or unreachable memory that is allocated to an object. An object becomes unreachable if all references to it become invalid. For example, if an object’s references are set to a null reference, it is unreachable. A directly or indirectly referenced object is reachable, and garbage collection is not permitted to reclaim it. A reference to a reachable object is called a strong reference. A weak reference also references a reachable object, or target. You acquire a strong reference to the target by assigning the value of the target property to a variable. However, if there are no strong references to the target, the target becomes eligible for garbage collection, even though it still has a weak reference.
38
Module 9: Memory and Resource Management
Retrieving the Target There may be a delay between the time when an object becomes eligible for garbage collection and the time when it is collected. If you attempt to retrieve the target after it has been collected, you will only retrieve a null reference. If the target has not yet been collected, you will retrieve a valid reference. The following example shows how to use weak references to improve performance: Object obj = new Object(); // create strong reference WeakReference wr = new WeakReference(obj); obj = null; // remove strong reference //... obj = (Object) wr.Target; if (obj != null) { // garbage collection has not occurred, // obj valid reference // ... } else { // object was collected, reference is null //... }
A WeakReference object can specify whether the reference to its target is maintained after finalization. In this way, it can specify whether the weak reference should track the target’s resurrection. The following table defines two types of weak references. Type of weak reference
Definition
Short weak reference
Does not track resurrection
Long weak reference
Tracks resurrection
Module 9: Memory and Resource Management
39
Demonstration: Weak References Topic Objective
To demonstrate how garbage collection handles weak references.
Lead-in
This demonstration shows how garbage collection handles weak references.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This demonstration shows how garbage collection handles weak references. For Your Information
In this section run the WeakRefDemo method. Read and follow the procedure in the Note and avoid using the debugger to single-step through the WeakRefDemo code.
Important Using the debugger to single-step through the WeakRefDemo code may prevent the garbage collection process from collecting an object that is only referenced by a weak reference. Instead of using the debugger to singlestep through the code you should set breakpoints at the beginning and end of the WeakRefDemo method to observe that objects with only weak references to them are collected.
40
Module 9: Memory and Resource Management // This method demonstrates how weak references (WR) work. A // WR allows the GC to collect objects if the managed heap is // low on memory. // WRs are useful to apps that have large amounts of easily// reconstructed data that they want to keep around to improve // performance. But, if the system is low on memory, // the objects can be destroyed and replaced when // the app knows that it needs it again. private static void WeakRefDemo(Boolean trackResurrection) { Display(0, String.Format( "\n\nDemo start: WeakReferences that {0}track ! resurrections.", trackResurrection ? "" : "do not "), +1); // Create an object BaseObj obj = new BaseObj("WeakRef"); // Create a WeakReference object that refers to the new // object WeakReference wr = new WeakReference(obj, trackResurrection); // The object is still reachable, so it is not finalized. Collect(); // The Finalize code should NOT execute WaitForFinalizers(); obj.Display("Still exists"); // Let's remove the strong reference to the object obj = null; // Destroy strong reference to this object // The following line creates a strong reference to the // object obj = (BaseObj) wr.Target; Display("Strong reference to object obtained: " + (obj != null)); // Destroy strong reference to this object again. obj = null; // The GC considers the object to be unreachable and // collects it. Collect(); WaitForFinalizers(); // Finalize should run. // // // // // //
This object resurrects itself when its Finalize code is called. If wr is NOT tracking resurrection, wr thinks the object is dead If wr is tracking resurrection, wr thinks the object is still alive
(Code continued on the following page.)
Module 9: Memory and Resource Management // // // //
41
NOTE: If the object referred to by wr doesn't have a Finalize code, then wr would think that the object is dead regardless of whether wr is tracking resurrection or not.
// The following line attempts to create a strong reference // to the object. obj = (BaseObj) wr.Target; Display("Strong reference to object obtained: " + (obj != null)); if // // // //
(obj != null) { The strong reference was obtained so this wr must be tracking resurrection. At this point we have a strong reference to an object that has been finalized but its memory has not yet been reclaimed by the collector. obj.Display("See, I'm still alive"); // Destroy the strong reference to the object obj = null; // Collect reclaims the object's memory since this // object has no Finalize code registered for it // anymore. Collect(); WaitForFinalizers(); // We should see nothing here obj = (BaseObj) wr.Target; // This now returns null Display("Strong reference to object obtained: " + (obj != null));
} // Cleanup everything about this demo so there is no effect // on the next demo // Destroy strong reference (if it exists) obj = null; wr = null; // Destroy the WeakReference object (optional) Collect(); WaitForFinalizers(); // NOTE: You are discouraged from using the // WeakReference.IsAlive property // because the object may be killed immediately after // IsAlive returns making the return value incorrect. // If the Target property returns a non-null value, // then the object is alive and will stay alive // since you have a reference to it. If Target returns null, // then the object is dead. Display(-1, String.Format( "Demo stop: WeakReferences that {0}track resurrections.", trackResurrection ? "" : "do not "), 0); }
42
Module 9: Memory and Resource Management
Generations Topic Objective
To explain how the .NET Framework common language runtime uses a system of generations to make garbage collection more efficient.
Lead-in
To improve the performance of garbage collection, the .NET Framework uses a system based on object generations.
!
To Force Garbage Collection of Generation 0 Through a Specified Generation: void void System.GC.Collect(int System.GC.Collect(int Generation); Generation);
!
To Determine the Generation of an Object:
Int32 Int32 System.GC.GetGeneration(Object System.GC.GetGeneration(Object obj); obj); !
To Return the Maximum Number of Generations That the System Currently Supports: Int32 Int32 System.GC.MaxGeneration; System.GC.MaxGeneration;
*****************************ILLEGAL FOR NON-TRAINER USE****************************** To improve the performance of garbage collection, the .NET Framework uses a system based on object generations, which works as follows: 1. When a managed object is created by using the new operator and is added to the managed heap, it becomes part of generation 0. 2. After garbage collection is invoked, any generation 0 objects that remain in the managed heap are promoted to become members of generation 1. 3. Any generation 1 objects that remain in the managed heap are promoted to become members of generation 2. Because generation 2 is the highest generation that is currently supported, generation 2 objects that remain in the managed heap remain in generation 2. When garbage collection is invoked to free heap space, its performance is improved because it only compacts the section of the managed heap that contains generation 0 objects. Typically, the newer an object is, the shorter its lifetime will be. Therefore, sufficient space is usually freed when generation 0 is compacted. If sufficient space cannot be obtained when generation 0 is compacted, garbage collection will compact the older generations.
Module 9: Memory and Resource Management
To force the collection of generations from 0 through a specified generation, you can call the .NET Framework System.GC.Collect method, as follows: void System.GC.Collect(int Generation);
The System.GC.GetGeneration method returns the current generation of an object as follows: Int32 System.GC.GetGeneration(Object obj);
The System.GC.MaxGeneration property returns the maximum number of generations that the system currently supports. It is written as follows: Int32 System.GC.MaxGeneration;
43
44
Module 9: Memory and Resource Management
Demonstration: Generations Topic Objective
To demonstrate how garbage collection handles generations.
Lead-in
This demonstration shows how garbage collection handles generations.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This demonstration shows how garbage collection handles generations. For Your Information
Use the debugger to step through the code while you point out features and ask students what they think will happen next. In this section run the GenerationDemo method.
// This method demonstrates how objects are promoted between // generations. // Applications could take advantage of this info to improve // performance but most applications will ignore this info. private static void GenerationDemo() { Display(0, "\n\nDemo start: Understanding Generations.", +1); // Let's see how many generations the managed heap supports // (we know it's 2) Display("Maximum GC generations: " + GC.MaxGeneration); // Create a new BaseObj in the heap GenObj obj = new GenObj("Generation"); // Since this object is newly created, it should be in // generation 0 obj.DisplayGeneration(); // Displays 0 // Performing a GC promotes the object's generation Collect(); obj.DisplayGeneration(); // Displays 1 Collect(); obj.DisplayGeneration();
// Displays 2
Collect(); obj.DisplayGeneration();
// Displays 2
(Code continued on the following page.)
(max generation)
Module 9: Memory and Resource Management // Destroy the strong reference to this object obj = null; Collect(0); WaitForFinalizers();
// Collect objects in generation 0 // We should see nothing
// Collect objects in generations 0 and 1 Collect(1); WaitForFinalizers(); // We should see nothing Collect(2); // Same as Collect() // Now, we should see the Finalize code run WaitForFinalizers(); Display(-1, "Demo stop: Understanding Generations.", 0); }
45
46
Module 9: Memory and Resource Management
Additional Performance Features Topic Objective
To introduce additional resources for improved application performance.
Lead-in
In addition to programmatically manipulating garbage collection through weak references and generations, you can use other features and techniques to improve application performance.
!
Performance Monitoring
!
Large Object Heap
!
Multiprocessor Support
!
Unsafe Code
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In addition to programmatically manipulating garbage collection through weak references and generations, you can use other features and techniques to improve application performance.
Performance Monitoring You can obtain real-time information about the memory activity of the .NET Framework common language runtime by using performance counters. You can view these counters by using the Performance Monitor tool (Perfmon.exe). To execute this program, click Start, click Run, and in the text box, type perfmon.exe. You can also execute the program by clicking Control Panel, double-clicking Administrative Tools, and double-clicking Performance. To view .NET Framework common language runtime memory statistics in the Performance window: 1. Click the System Monitor icon under the Console Root folder. 2. Click the + button in the right pane to add a counter to the System Monitor. 3. Specify the computer that you wish to monitor. 4. In the Performance object field, select .NET CLR Memory. 5. Select the desired counters and application instance. You can obtain descriptions of the counters by clicking the Explain button. In the future, you will also be able to read these counters by using the APIs that will be provided by the .NET Framework class library.
Module 9: Memory and Resource Management
47
Large Object Heap Objects that are larger than 20,000 bytes are allocated from a separate special heap. Garbage collection handles large objects in the same way as it handles other managed heap objects, except it does not compact the separate special heap to avoid the additional performance cost of shifting large blocks of memory.
Multiprocessor Support The .NET Framework common language runtime provides two forms of garbage collection: a server version (MSCorSvr.dll) and a workstation version (MSCorWks.dll). The server version is multithreaded and highly scalable, while the workstation version is single-threaded and concurrent. Both versions feature low fragmentation, low overhead, and efficient use of cache space. The server version of the common language runtime contains features that improve the efficiency of resource collection. With synchronization-free allocations and scalable collections, a multiprocessor system receives a managed heap that is split into as many sections as there are CPUs. Each CPU has its own thread, which enables the runtime to perform garbage collection on different heaps simultaneously. If you have a garbage collection bottleneck, you can improve performance by scaling your application to a multiprocessor system.
Using Unmanaged Code to Control Garbage Collection When you need a higher degree of control or that extra margin of performance, C# provides the ability to write code that can deal directly with pointer types, and fix objects to temporarily prevent garbage collection from moving them. You must clearly mark such code in C# with the unsafe modifier. This ensures that other developers cannot possibly use unsafe features accidentally, and that the compiler and the common language runtime work together to ensure that unmanaged code cannot masquerade as safe code. You should note that unmanaged code is not verifiable by the .NET Framework common language runtime and should only be executed when trusted. It does, however, offer developers and users a way to optimize performance. Further discussion of unmanaged code and memory management is beyond the scope of this course.
48
Module 9: Memory and Resource Management
Lab 9: Memory and Resource Management Topic Objective
To introduce the lab.
Lead-in
In this lab, you will learn how to use classes to create an application that explicitly manages resources by implementing a design pattern that is based on the IDisposable interface. You will also learn how to create an application that implicitly manages resources by using destructors.
*****************************ILLEGAL FOR NON-TRAINER USE******************************
Objectives After completing this lab, you will be able to: !
Create an application, with classes, that explicitly manages resources by implementing a design pattern based on the IDisposable interface.
!
Create an application that implicitly manages resources by using destructors.
Lab Setup Starter and solution files are associated with this lab. The starter files are in the folder \Labs\Lab09\Starter. The solution files for this lab are in the folder \Labs\Lab09\Solution.
Scenario In this lab, you are provided with a Microsoft Visual Studio® .NET console application as a starting point. The application is named Memory and Resource Management. It allows a user to enter text data. When the user has finished entering data, the application outputs the number of completed sentences and the character count, and then the text. The text is formatted so that each sentence is displayed on a separate line without an ending period. The application is implemented by using a SentenceFormatter object that buffers the input text until a completed sentence is detected or until its Close method is called.
Module 9: Memory and Resource Management
49
When a completed sentence is detected or its Close method is called, the SentenceFormatter object sends its buffered text to a SimpleTextBuffer object. The SimpleTextBuffer object buffers this text until it is closed or finalized. At that time, the SimpleTextBuffer object outputs its buffered text to the console. In this lab, the Memory and Resource Management application is used to illustrate some key points about .NET Framework resource management. To provide explicit and implicit resource management, you will modify this program to follow the design pattern that is described in Module 9.
Estimated time to complete this lab: 60 minutes
50
Module 9: Memory and Resource Management
Exercise 1 Programming for Explicit Resource Management In this exercise, you will modify the Memory and Resource Management application to incorporate explicit resource management.
! Examine the application 1. In Visual Studio .NET, open the Memory and Resource Management project, which is located in \Labs\Lab09\Starter\ Memory and Resource Management. 2. Open the Memory and Resource Management.cs file and examine the code. 3. Build and run the Memory and Resource Management application. The following text should appear in the console output: Enter text, when finished enter an empty line
4. Enter the following text, which is followed by an empty line, as prompted: hello world.are you out there?
Text similar to the following should appear in the console output: SimpleTextBuffer: hello world are you out there? Completed Sentences 1, Output Characters 29 hit enter to exit program
Module 9: Memory and Resource Management
51
! Add explicit resource management Tip For detailed explanations about and code examples for adding explicit resource management, see The IDisposable Interface and the Dispose method in this module. 1. Modify the SentenceFormatter and SimpleTextBuffer classes to inherit from IDisposable. 2. For each of these classes, add a private member of type bool named disposed to indicate whether the object’s non-memory resources have been released. Initialize the value of disposed to false. 3. For each of these classes, add a protected virtual void Dispose method that takes a single bool parameter named disposing. If the object’s disposed flag indicates that the object has not already been disposed: a. If the parameter disposing indicates that the object is not in finalization mode, then propagate the Dispose/Close method call through any containment hierarchies. Tip Propagating the Dispose/Close method call through a containment hierarchy is discussed in the topic The IDisposable Interface and the Dispose Method in Module 9. Note The SentenceFormatter should send its text buffer to its contained SimpleTextBuffer object named aSimpleTextBuffer before it calls that SimpleTextBuffer object’s Close method. b. Free the object’s internal state and set disposed to indicate that the object has been disposed. Multiple calls to protected virtual void Dispose should not throw an exception. 4. For both classes, add a public void Dispose method that takes no arguments. This method calls the protected virtual void Dispose method created in step 3 with true as its argument followed by a call to the GC class’s method to suppress finalization. 5. For both classes, modify the Close method to call the public void Dispose method. 6. For the SentenceFormatter and SimpleTextBuffer methods, which require resources that are freed by their classes’ protected virtual void Dispose method, add an initial check that throws an ObjectDisposedException if those resources have been released.
52
Module 9: Memory and Resource Management
Tip When the SentenceFormatter’s protected virtual void Dispose method is called, it frees its sentenceBuffer and aSimpleTextBuffer resources. The SentenceFormatter’s SentenceCount property does not use either of these resources and therefore does not need to check if these resources have been released. The ProcessInput method uses both these resources and therefore must check whether these resources have been released. Note Explicit resource management will be investigated further in the next exercise.
Module 9: Memory and Resource Management
53
Exercise 2 Programming for Implicit Resource Management In this exercise, you will modify the Memory and Resource Management application to incorporate implicit resource management.
! Add implicit resource management 1. Add destructors to the SentenceFormatter and SimpleTextBuffer classes. These destructors should free only their object’s internal state by calling the protected virtual void Dispose method with false. Add code to the destructors to output to the console the name of the class and the fact that the destructor was executed. Note Even though you have inserted code to do implicit resource management, the program is still explicitly managing its resources through the following method call in the Main method: aSentenceFormatter.Close();
2. Build and run the Memory and Resource Management application. As in the preceding exercise, the following text should appear in the console output: Enter text, when finished enter an empty line
3. Enter the following text, and then enter an empty line, as prompted: hello world.are you out there?
Text similar to the following should appear in the console output: SimpleTextBuffer: hello world are you out there? Completed Sentences 1, Output Characters 29 hit enter to exit program
Note With explicit resource management, the output is identical to the output in Exercise 1. All the characters you entered are output and counted.
54
Module 9: Memory and Resource Management
! Test implicit resource management performance 1. In the Main method, add comments to the beginning of the following line of code so that the Close method will not be invoked: aSentenceFormatter.Close();
2. In the preceding step there is a statement after the code that outputs the sentence and character count to the console. After this Console.WriteLine statement, add code to assign the SimpleTextBuffer and SentenceFormatter objects to null, invoke garbage collection on all generations, and wait for pending finalizers. 3. Build and run the Memory and Resource Management application. As in the preceding exercise, the following text should appear in the console output: Enter text, when finished enter an empty line
4. Enter the following text, followed by an empty line, as prompted: hello world.are you out there?
Text similar to the following should appear in the console output: Completed Sentences 1, Output Characters 11 SentenceFormatter destructor called SimpleTextBuffer: hello world SimpleTextBuffer destructor called hit enter to exit program
Note Observe the difference in the number of output characters when using explicit and implicit resource management. Explicit resource management outputs all the characters you entered because it propagates the Close/Dispose method call down the containment hierarchy, thereby flushing all of the buffers. Implicit resource management cannot safely do this propagation because of the nondeterministic order of destructor calls. Therefore, not all of the characters you entered are output.
Module 9: Memory and Resource Management
55
Review Topic Objective
To reinforce module objectives by reviewing key points.
Lead-in
The review questions cover some of the key concepts taught in the module.
!
Memory Management Basics
!
Non-Memory Resource Management
!
Implicit Resource Management
!
Explicit Resource Management
!
Optimizing Garbage Collection
*****************************ILLEGAL FOR NON-TRAINER USE****************************** 1. Describe two common problems with manual memory management that the .NET Framework’s automatic memory management addresses. Failure to release memory (Memory leaks) Accessing invalid memory (dangling pointer references)
2. Describe the .NET Framework mechanism that is used to provide implicit resource management. During the finalization phase of garbage collection, garbage collection calls Finalize code (C# destructor), which allows an object to properly clean up its resources before its memory resources are freed.
3. State why explicit resource management is desirable. Describe the interface that a class should inherit from and the method(s) that a class should implement to provide explicit memory management. Implicit resource management is non-deterministic, and therefore an application cannot know exactly when a resource will be freed. Explicit resource management allows a client to invoke a method to deterministically free the resource. Therefore, classes should inherit from IDisposable and implement its Dispose method. Also garbage collection’s non-deterministic ordering of calls to finalize code makes it dangerous for classes to refer to other objects during finalization. Therefore, explicit resource management may be the only safe way for a class to refer to other objects when it releases resources, as, for example, when it flushes buffers.
56
Module 9: Memory and Resource Management
4. State the purpose of weak references. Weak references are useful in applications that have large amounts of easily reconstructed data that should be maintained to improve performance. A weak reference allows garbage collection to collect these objects if memory in the managed heap is low.
5. Explain how and why generations are used by garbage collection. When garbage collection is invoked to free heap space, its performance is improved because it only compacts the section of the managed heap that contains generation 0 objects. Typically, the newer an object is, the shorter its lifetime will be. Therefore, sufficient space is usually freed when generation 0 is compacted. If sufficient space cannot be obtained when generation 0 is compacted, garbage collection will compact the older generations.
THIS PAGE INTENTIONALLY LEFT BLANK
Module 10: Data Streams and Files Contents Overview
1
Streams
2
Readers and Writers
5
Basic File I/O
8
Lab 10: Files
21
Review
26
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, place or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. 2001-2002 Microsoft Corporation. All rights reserved. Microsoft, ActiveX, BizTalk, IntelliMirror, Jscript, MSDN, MS-DOS, MSN, PowerPoint, Visual Basic, Visual C++, Visual C#, Visual Studio, Win32, Windows, Windows Media, and Window NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
Module 10: Data Streams and Files
iii
Instructor Notes Presentation: 45 Minutes Lab: 45 Minutes
After completing this module, students will be able to: !
Use Stream objects to read and write bytes to backing stores, such as strings and files.
!
Use BinaryReader and BinaryWriter objects to read and write primitive types as binary values.
!
Use StreamReader and StreamWriter objects to read and write characters to a stream.
!
Use StringReader and StringWriter objects to read and write characters to strings.
!
Use Directory and DirectoryInfo objects to create, move, and enumerate through directories and subdirectories.
!
Use FileSystemWatcher objects to monitor and react to changes in the file system.
!
Explain the key features of the Microsoft® .NET Framework isolated storage mechanism.
Materials and Preparation This section provides the materials and preparation tasks that you need to teach this module.
Required Materials To teach this module, you need the Microsoft PowerPoint® file 2349B_10.ppt.
Preparation Tasks To prepare for this module, you should: !
Read all of the materials for this module.
!
Complete the lab.
iv
Module 10: Data Streams and Files
Module Strategy Use the following strategy to present this module: !
Streams Briefly review fundamental stream operations and introduce the stream classes that are provided by System.IO. Point out that this module discusses synchronous operations only; asynchronous operations are beyond the scope of this course. Tell students that the NetworkStream class is covered in more detail in Module 11, “Internet Access,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C#™.NET).
!
Readers and Writers Cover the commonly used reader and writer classes that are used to input and output to streams and strings that use types other than bytes.
!
Basic File I/O Discuss in more detail the stream classes that are provided by System.IO for manipulating files and directories. Discuss the security issues that are associated with writing code that will be downloaded over the Internet.
Module 10: Data Streams and Files
1
Overview Topic Objective
To provide an overview of the module topics and objectives.
Lead-in
In this module, you will learn about how to use types that allow reading from and writing to data streams and files.
!
Streams
!
Readers and Writers
!
Basic File I/O
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The System.IO namespace contains types that allow synchronous and asynchronous reading from and writing to data streams and files. This module discusses synchronous operations only, because asynchronous operations are beyond the scope of this course. After completing this module, you will be able to: For Your Information
When you talk about a particular class, you may want to display the class information for System.IO from the .NET Framework Reference section in the .NET Framework SDK.
!
Use Stream objects to read and write bytes to backing stores, such as strings and files.
!
Use BinaryReader and BinaryWriter objects to read and write primitive types as binary values.
!
Use StreamReader and StreamWriter objects to read and write characters to a stream.
!
Use StringReader and StringWriter objects to read and write characters to strings.
!
Use Directory and DirectoryInfo objects to create, move, and enumerate through directories and subdirectories.
!
Use FileSystemWatcher objects to monitor and react to changes in the file system.
!
Explain the key features of the Microsoft® .NET Framework isolated storage mechanism.
2
Module 10: Data Streams and Files
Streams Topic Objective
To introduce the functions of the Stream class and its subclasses.
Lead-in
Streams provide a way to read and write bytes from and to a backing store. A backing store is a storage medium, such as a disk or memory.
!
A Way to Read and Write Bytes from and to a Backing Store
!
Fundamental Stream Operations: Read, Write, and Seek
!
Some Streams Support Buffering for Performance
"
"
"
!
Flush method outputs and clears internal buffers Close method performs an implicit Flush for buffered streams
Stream Classes Provided by the .NET Framework "
!
CanRead, CanWrite, and CanSeek properties
Close Method Frees Resources "
!
Stream classes inherit from System.IO.Stream
NetworkStream, BufferedStream, MemoryStream, FileStream, CryptoStream
Null Stream Instance Has No Backing Store
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Streams provide a way to read and write bytes from and to a backing store. A backing store is a storage medium, such as a disk or memory. All classes that represent streams inherit from the Stream class. The Stream class and its subclasses provide a generic view of data sources and repositories, and isolate the programmer from the specific details of the operating system and underlying devices.
Fundamental Stream Operations Streams allow you to perform three fundamental operations: 1. You can read from streams. Reading is the transfer of data from a stream into a data structure, such as an array of bytes. 2. You can write to streams. Writing is the transfer of data from a data structure into a stream. 3. Streams can support seeking. Seeking is the querying and modifying of the current position within a stream. Seek capability depends on the kind of backing store that a stream has. For example, network streams have no unified concept of a current position and therefore typically do not support seeking. Depending on the underlying data source or repository, streams may support only some of these capabilities. An application can query a stream for its capabilities by using the CanRead, CanWrite, and CanSeek properties. The Read and Write methods read and write byte data. For streams that support seeking, the Seek and SetLength methods and the Position and Length properties can be used to query and modify the current position and length of a stream.
Module 10: Data Streams and Files
3
Support for Buffering Some stream implementations perform local buffering of the underlying data to improve performance. For such streams, you can use the Flush method to clear internal buffers and ensure that all data has been written to the underlying data source or repository. Calling the Close method on a stream flushes any buffered data, essentially calling the Flush method for you. The Close method also releases operating system resources, such as file handles, network connections, or memory that is used for any internal buffering.
Stream Classes Provided by the .NET Framework The .NET Framework contains several stream classes that derive from the System.IO.Stream class. The System.Net.Sockets namespace contains the NetworkStream class. NetworkStream provides the underlying stream of data for network access and will be discussed in more detail in Module 11, “Internet Access,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C#™.NET). The System.IO namespace contains the BufferedStream, MemoryStream, and FileStream classes, which are derived from the System.IO.Stream class.
BufferedStream Class The BufferedStream class is used to buffer reads and writes to another stream. A buffer is a block of bytes in memory that is used to cache data, thereby reducing the number of calls to the operating system. Buffers thus can be used to improve read and write performance. Another class cannot inherit from the BufferedStream class.
MemoryStream Class The MemoryStream class provides a way to create streams that have memory as a backing store, instead of a disk or a network connection. The MemoryStream class creates a stream out of an array of bytes.
FileStream Class The FileStream class is used for reading from and writing to files. By default, the FileStream class opens files synchronously, but it provides a constructor to open files asynchronously.
4
Module 10: Data Streams and Files
CryptoStream Class The CryptoStream class defines a stream that links data streams to cryptographic transformations. The common language runtime uses a streamoriented design for cryptography. The core of this design is CryptoStream. Any cryptographic objects that implement CryptoStream can be chained together with any objects that implement Stream, so the streamed output from one object can be fed into the input of another object. The intermediate result (the output from the first object) does not need to be stored separately. For further details about the CryptoStream class see the .NET Framework SDK.
Null Stream Instance There are times when an application needs a stream that simply discards its output and returns no input. You can obtain such a stream that has no backing store and that will not consume any operating resources from the Stream class’s public static field named Null. For example, you may code an application to always write its output to the FileStream that is specified by the user. When the user does not want an output file, the application directs its output to the Null stream. When the Write methods of Stream are invoked on this Null stream, the call simply returns, and no data is written. When the Read methods are invoked, the Null stream returns zero without reading data.
Module 10: Data Streams and Files
5
Readers and Writers Topic Objective
To show how reader and writer classes are used to input and output to streams and strings.
!
Classes That Are Derived from System.IO.Stream Take Byte Input and Output
!
Readers and Writers Take Other Types of Input and Output and Read and Write Them to Streams or Strings
!
BinaryReader and BinaryWriter Read and Write Primitive Types to a Stream
!
TextReader and TextWriter Are Abstract Classes That Implement Read Character and Write Character Methods
!
TextReader and TextWriter Derived Classes Include:
Lead-in
As previously mentioned, the Stream class is designed for byte input and output. You can use the reader and writer classes to input and output to streams and strings using other types.
" "
StreamReader and StreamWriter, which read and write to a stream StringReader and StringWriter, which read and write to a string and StringBuilder respectively
*****************************ILLEGAL FOR NON-TRAINER USE****************************** As discussed in Streams in this module, the Stream class is designed for byte input and output. You can use the reader and writer classes to input and output to streams and strings that use other types. The following table describes some commonly used reader and writer classes. Class
Description
BinaryReader and BinaryWriter
These classes read and write primitive types as binary values in a specific encoding to and from a stream.
TextReader and TextWriter
The implementations of these classes are designed for character input and output.
StreamReader and StreamWriter
These classes are derived from the TextReader and TextWriter classes, and read and write their characters to a stream.
StringReader and StringWriter
Theses classes also derive from the TextReader and TextWriter classes, but read their characters from a string and write their characters to a StringBuilder class.
A reader or writer is attached to a stream so that the desired types can be read or written easily.
6
Module 10: Data Streams and Files
The following example shows how to write data of type Integer to and read from a new, empty file stream that is named Test.data. After creating the data file in the current directory, the BinaryWriter class is used to write the integers 0 through 10 to Test.data. Then the BinaryReader class reads the file and displays the file’s content to the console. using System; using System.IO; class MyStream { private const string FILE_NAME = "Test.data"; public static void Main(String[] args) { // Create the new, empty data file. if (File.Exists(FILE_NAME)) { Console.WriteLine("{0} already exists!", FILE_NAME); return; } FileStream fs = new FileStream(FILE_NAME, FileMode.CreateNew); // Create the writer for data. BinaryWriter w = new BinaryWriter(fs); // Write data to Test.data. for (int i = 0; i < 11; i++) { w.Write( (int) i); } w.Close(); fs.Close(); // Create the reader for data. fs = new FileStream(FILE_NAME, FileMode.Open, FileAccess.Read); BinaryReader r = new BinaryReader(fs); // Read data from Test.data. for (int i = 0; i < 11; i++) { Console.WriteLine(r.ReadInt32()); w.Close(); } } }
Module 10: Data Streams and Files
7
In the following example, the code defines a string and converts it to an array of characters, which can then be read as desired by using the appropriate StringReader.Read method: using System; using System.IO; public class CharsFromStr { public static void Main(String[] args) { // Create a string to read characters from. String str = "Some number of characters"; // Size the array to hold all the characters of the // string, so that they are all accessible. char[] b = new char[24]; // Create a StringReader and attach it to the string. StringReader sr = new StringReader(str); // Read 13 characters from the array that holds // the string, starting from the first array member. sr.Read(b, 0, 13); // Display the output. Console.WriteLine(b); // Close the StringReader. sr.Close(); } }
The preceding example produces the following output: Some number o
System.Text.Encoding Internally, the common language runtime represents all characters as Unicode. However, Unicode can be inefficient when transferring characters over a network or when persisting in a file. To improve efficiency, the .NET Framework class library provides several types that are derived from the System.Text.Encoding abstract base class. These classes know how to encode and decode Unicode characters to ASCII, UTF-7, UTF-8, Unicode, and other arbitrary code pages. When you construct a BinaryReader, BinaryWriter, StreamReader, or StreamWriter, you can choose any of these encodings. The default encoding is UTF-8.
8
Module 10: Data Streams and Files
# Basic File I/O Topic Objective
To introduce the classes of the System.IO namespace, which are discussed in this section.
Lead-in
The .NET Framework’s System.IO namespace provides a number of useful classes for manipulating files and directories.
!
FileStream Class
!
File and FileInfo Class
!
Reading Text Example
!
Writing Text Example
!
Directory and DirectoryInfo Class
!
FileSystemWatcher
!
Isolated Storage
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The .NET Framework’s System.IO namespace provides a number of useful classes for manipulating files and directories. Important Default security policy for the Internet and intranets does not allow access to files. Therefore, do not use the regular, nonisolated storage IO classes if you are writing code that will be downloaded over the Internet. Use Isolated Storage instead. Caution When a file or network stream is opened, a security check is performed only when the stream is constructed. Therefore, be careful when handing off these streams to less trusted code or application domains.
Module 10: Data Streams and Files
9
FileStream Class Topic Objective
To define the FileStream class and the types that are used as parameters in some FileStream constructors.
!
The FileStream Class Is Used for Reading from and Writing to Files
!
FileStream Constructor Parameter Classes
Lead-in
The FileStream class is used for reading from and writing to files. The FileMode, FileAccess, and FileShare types are used as parameters in some FileStream constructors.
"
FileMode – Open, Append, Create
"
FileAccess – Read, ReadWrite, Write
"
FileShare – None, Read, ReadWrite, Write
FileStream FileStream ff == new new FileStream(name, FileStream(name, FileMode.Open, FileMode.Open, FileAccess.Read, FileAccess.Read, FileShare.Read); FileShare.Read); !
Random Access to Files by Using the Seek Method "
Specified by byte offset
"
Offset is relative to seek reference point: Begin, Current, End
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The FileStream class is used for reading from and writing to files. The FileMode, FileAccess, and FileShare types are used as parameters in some FileStream constructors.
FileMode Parameter FileMode parameters control whether a file is overwritten, created, or opened, or any combination of those operations. The following table describes constants that are used with the FileMode parameter class. Constant
Description
Open
This constant is used to open an existing file.
Append
This constant is used to append to a file.
Create
This constant is used to create a file if it does not exist.
FileAccess Enumeration The FileAccess enumeration defines constants for read, write, or read/write access to a file. This enumeration has a FlagsAttribute that allows a bitwise combination of its member values. A FileAccess parameter is specified in many of the constructors for File, FileInfo, and FileStream, and in other class constructors where it is important to control the kind of access that users have to a file.
10
Module 10: Data Streams and Files
FileShare Enumeration The FileShare enumeration contains constants for controlling the kind of access that other FileStreams can have to the same file. This enumeration has a FlagsAttribute that allows a bitwise combination of its member values. The FileShare enumeration is typically used to define whether two processes can simultaneously read from the same file. For example, if a file is opened and FileShare.Read is specified, other users can open the file for reading but not for writing. FileShare.Write specifies that other users can simultaneously write to the same file. FileShare.None declines sharing of the file. In the following example, a FileStream constructor opens an existing file for read access and allows other users to read the file simultaneously: FileStream f = new FileStream(name, FileMode.Open, FileAccess.Read, FileShare.Read);
Using the Seek Method for Random Access to Files FileStream objects support random access to files by using the Seek method. The Seek method allows the read/write position within the file stream to be moved to any position within the file. The read/write position can be moved by using byte offset reference point parameters. The byte offset is relative to the seek reference point, as represented by the three properties of the SeekOrigin class, which are described in the following table. Property Name
Description
Begin
The seek reference position of the beginning of a stream.
Current
The seek reference position of the current position within a stream.
End
The seek reference position of the end of a stream.
Module 10: Data Streams and Files
11
File and FileInfo Class Topic Objective
To introduce the File and FileInfo classes and demonstrate how they are used to create a new object.
Lead-in
The File and FileInfo classes are utility classes with methods that are primarily used for the creation, copying, deletion, moving, and opening of files.
!
File Is a Utility Class with Static Methods Used to: "
!
!
Create, copy, delete, move, and open files
FileInfo Is a Utility Class with Instance Methods Used to: "
Create, copy, delete, move, and open files
"
Can eliminate some security checks when reusing an object.
Example: "
Assign to aStream a newly created file named foo.txt in the current directory
FileStream FileStream aStream aStream == File.Create("foo.txt"); File.Create("foo.txt");
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The File and FileInfo classes are utility classes with methods that are primarily used for the creation, copying, deletion, moving, and opening of files. All methods of the File class are static and can therefore be called without having an instance of a file. The FileInfo class contains all instance methods. The static methods of the File class perform security checks on all methods. If you are going to reuse an object several times, consider using the corresponding instance method of FileInfo instead, because the security check will not always be necessary. For example, to create a file named Foo.txt and return a FileStream object, use the following code: FileStream aStream = File.Create("Foo.txt");
To create a file named Foo.txt and return a StreamWriter object, use the following code: StreamWriter sw = File.CreateText("Foo.txt");
To open a file named Foo.txt and return a StreamReader object, use the following code: StreamReader sr = File.OpenText("Foo.txt");
12
Module 10: Data Streams and Files
Reading Text Example Topic Objective
To provide an example of reading.
Lead-in
In the following example, you read an entire file and are notified when the end of the file is detected.
!
Read Text from a File and Output It to the Console
//... //... StreamReader StreamReader sr sr == File.OpenText(FILE_NAME); File.OpenText(FILE_NAME); String String input; input; while while ((input=sr.ReadLine())!=null) ((input=sr.ReadLine())!=null) {{ Console.WriteLine(input); Console.WriteLine(input); }} Console.WriteLine Console.WriteLine (( "The "The end end of of the the stream stream has has been been reached."); reached."); sr.Close(); sr.Close(); //... //...
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In the following example of reading text, you read an entire file and are notified when the end of the file is detected. using System; using System.IO; public class TextFromFile { private const string FILE_NAME = "MyFile.txt"; public static void Main(String[] args) { if (!File.Exists(FILE_NAME)) { Console.WriteLine("{0} does not exist!", FILE_NAME); return; } StreamReader sr = File.OpenText(FILE_NAME); String input; while ((input=sr.ReadLine())!=null) { Console.WriteLine(input); } Console.WriteLine ( "The end of the stream has been reached."); sr.Close(); } }
This code creates a StreamReader object that points to a file named MyFile.txt through a call to File.OpenText. StreamReader.ReadLine returns each line as a string. When there are no more characters to read, a message is displayed to that effect, and the stream is closed.
Module 10: Data Streams and Files
13
Writing Text Example Topic Objective
To provide an example of writing text.
Lead-in
This example creates a new text file that is named MyFile.txt, writes a string, integer, and floating point number to it, and finally closes the file.
!
Create a File
!
Write a String, an Integer, and a Floating Point Number
!
Close the File
//... //... StreamWriter StreamWriter sw sw == File.CreateText("MyFile.txt"); File.CreateText("MyFile.txt"); sw.WriteLine ("This sw.WriteLine ("This is is my my file"); file"); sw.WriteLine ( sw.WriteLine ( "I "I can can write write ints ints {0} {0} or or floats floats {1}", {1}", 1, 1, 4.2); 4.2); sw.Close(); sw.Close(); //... //...
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The following example creates a new text file that is named MyFile.txt, writes a string, integer, and floating-point number to it, and finally closes the file. using System; using System.IO; public class TextToFile { private const string FILE_NAME = "MyFile.txt"; public static void Main(String[] args) { if (File.Exists(FILE_NAME)) { Console.WriteLine("{0} already exists!", FILE_NAME); return; } StreamWriter sw = File.CreateText(FILE_NAME); sw.WriteLine ("This is my file."); sw.WriteLine ( "I can write ints {0} or floats {1}, and so on.", 1, 4.2); sw.Close(); } }
14
Module 10: Data Streams and Files
Directory and DirectoryInfo Class Topic Objective
To explain how the Directory and DirectoryInfo classes are used to create directory listings.
!
Directory Has Static Methods Used to: "
!
DirectoryInfo Has Instance Methods Used to: "
Lead-in
The Directory and DirectoryInfo classes expose routines for creating, moving, and enumerating through directories and subdirectories.
"
!
Create, move, and enumerate through directories and subdirectories Create, move, and enumerate through directories and subdirectories Can eliminate some security checks when reusing an object
Example: "
Enumerating through the current directory
DirectoryInfo DirectoryInfo dir dir == new new DirectoryInfo("."); DirectoryInfo("."); foreach foreach (FileInfo (FileInfo ff in in dir.GetFiles("*.cs")) dir.GetFiles("*.cs")) {{ String String name name == f.FullName; f.FullName; }} !
Use Path Class Objects to Process Directory Strings
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The Directory and DirectoryInfo classes expose routines for creating, moving, and enumerating through directories and subdirectories. All methods of the Directory class are static and can therefore be called without having an instance of a directory. The DirectoryInfo class contains all instance methods. The static methods of the Directory class do a security check on all methods. If you are going to reuse an object several times, consider using the corresponding instance method of DirectoryInfo instead, because the security check will then not always be necessary. The following example shows how to use the DirectoryInfo class to create a listing of a directory: using System; using System.IO; class DirectoryLister { public static void Main(String[] args) { DirectoryInfo dir = new DirectoryInfo("."); foreach (FileInfo f in dir.GetFiles("*.cs")) { String name = f.FullName; long size = f.Length; DateTime creationTime = f.CreationTime; Console.WriteLine("{0,-12:N0} {1,-20:g} {2}", size, creationTime, name); } } }
Module 10: Data Streams and Files
In the preceding example, the DirectoryInfo object is the current directory, denoted by ("."). The code lists the names of all of the files in the current directory that have a .cs extension, together with their file size and creation time. Assuming that there are .cs files in the \Bin subdirectory of drive C, the output of this code appears as follows: 953 664 403
7/20/2000 10:42 AM 7/27/2000 3:11 PM 8/8/2000 10:25 AM
C:\Bin\paramatt.cs C:\Bin\tst.cs C:\Bin\dirlist.cs
If you want a list of files in another directory, such as C:\, remember to use the backslash (\) escape character, as in the following example: "C:\\"
Or, use an @-quoted string literal in C#, as in the following example: @"C:\"
Paths To processes directory strings in a cross-platform manner, use the Path class. The members of the Path class enable you to quickly and easily perform common operations, such as determining whether a file extension is part of a path, and combining two strings into one path name.
15
16
Module 10: Data Streams and Files
FileSystemWatcher Topic Objective
To explain how the FileSystemWatcher component can be used to monitor and react to changes in a file system.
Lead-in
You use the FileSystemWatcher component to monitor a file system and react when changes to it occur.
!
FileSystemWatcher Is Used to Monitor a File System
!
Creating a FileSystemWatcher Object
FileSystemWatcher FileSystemWatcher watcher watcher == new new FileSystemWatcher(); FileSystemWatcher(); !
Configure
watcher.Path watcher.Path == args[0]; args[0]; watcher.Filter watcher.Filter == "*.txt"; "*.txt"; watcher.NotifyFilter watcher.NotifyFilter == NotifyFilters.FileName; NotifyFilters.FileName; watcher.Renamed watcher.Renamed += += new new RenamedEventHandler(OnRenamed); RenamedEventHandler(OnRenamed); !
Begin Watching watcher.EnableRaisingEvents watcher.EnableRaisingEvents == true; true;
!
Catch Events
public public static static void void OnRenamed(object OnRenamed(object s, s, RenamedEventArgs RenamedEventArgs e) e) {{ Console.WriteLine("File: Console.WriteLine("File: {0} {0} renamed renamed to to {1}", {1}", e.OldFullPath, }} e.OldFullPath, e.FullPath); e.FullPath);
*****************************ILLEGAL FOR NON-TRAINER USE****************************** You use the FileSystemWatcher component to monitor a file system and react when changes to it occur. By using the FileSystemWatcher component, you can quickly and easily launch business processes when specified files or directories are created, modified, or deleted. For example, if a group of users is collaborating on a document that is stored in a shared directory on a server, you can use the FileSystemWatcher component to easily program your application to watch for changes to the shared directory. When a change is detected, the component can run processing that notifies each user through e-mail. You can configure the component to watch an entire directory and its contents or a specific file or set of files within a specific directory. To watch for changes in all files, set the Filter property to an empty string (""). To watch a specific file, set the Filter property to the file name. For example, to watch for changes in the file MyDoc.txt, set the Filter property to "MyDoc.txt". You can also watch for changes in a certain type of file. For example, to watch for changes in text files, set the Filter property to "*.txt". Note Hidden files are not ignored. There are several types of changes you can watch for in a directory or file. For example, you can watch for changes in Attributes, the LastWrite date and time, or the Size of files or directories. This is done by setting the FileSystemWatcher.NotifyFilter property to one of the NotifyFilters values. For more information on the type of changes you can watch, see NotifyFilters in the .NET Framework Software Development Kit (SDK). You can watch for renaming, deletion, or creation of files or directories. For example, to watch for renaming of text files, set the Filter property to "*.txt" and call one of the WaitForChanged methods with the WatcherChangeTypes value Renamed provided.
Module 10: Data Streams and Files
Creating a FileSystemWatcher Component The following example creates a FileSystemWatcher component to watch the directory that is specified at run time. The component is set to watch for changes in LastWrite and LastAccess times, and the creation, deletion, or renaming of text files in the directory. If a file is changed, created, or deleted, the path to the file prints to the console. When a file is renamed, the old and new paths print to the console. using System; using System.IO; public class Watcher { public static void Main(string[] args) { // If a directory is not specified, exit program. if(args.Length != 1) { // Display the proper way to call the program. Console.WriteLine( "Usage: Watcher.exe (directory)"); return; } // Create a new FileSystemWatcher // and set its properties. FileSystemWatcher watcher = new FileSystemWatcher(); watcher.Path = args[0]; /* Watch for changes in LastAccess and LastWrite times, and the renaming of files or directories */ watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; // Only watch text files. watcher.Filter = "*.txt"; // Add event handlers. // The Changed event occurs when changes are made to // the size, system attributes, last write time, last // access time, or security permissions in a file or // directory in the specified Path of a // FileSystemWatcher. watcher.Changed += new FileSystemEventHandler(OnChanged);
(Code continued on the following page.)
17
18
Module 10: Data Streams and Files // The Created event occurs when a file or directory // in the specified Path of a FileSystemWatcher is // created. watcher.Created += new FileSystemEventHandler(OnChanged); // The Deleted event occurs when a file or directory // in the specified Path of a FileSystemWatcher is // deleted. watcher.Deleted += new FileSystemEventHandler(OnChanged); // The Deleted event occurs when a file or directory // in the specified Path of a FileSystemWatcher is // deleted. watcher.Renamed += new RenamedEventHandler(OnRenamed); // Begin watching. watcher.EnableRaisingEvents = true; // Wait for the user to quit the program. Console.WriteLine("Press \'q\' to quit the sample."); while(Console.Read()!='q'); } // Define the event handlers. public static void OnChanged( object source, FileSystemEventArgs e) { // Specify what is done when a file is changed, // created, or deleted. Console.WriteLine("File: " + e.FullPath + " " + e.ChangeType); } public static void OnRenamed( object source, RenamedEventArgs e) { // Specify what is done when a file is renamed. Console.WriteLine("File: {0} renamed to {1}", e.OldFullPath, e.FullPath); } }
Module 10: Data Streams and Files
19
Isolated Storage Topic Objective
To introduce isolated storage and its potential uses.
!
Isolated Storage Provides Standardized Ways of Associating Applications with Saved Data
!
Semi-Trusted Web Applications Require:
Lead-in
For some applications, such as downloaded Web applications and code that may come from untrusted sources, the basic file system does not provide the necessary isolation and safety.
!
"
Isolation of their data from other applications' data
"
Safe access to a computer’s file system
System.IO.IsolatedStorage Namespace Contains:
public public sealed sealed class class IsolatedStorageFile IsolatedStorageFile :: IsolatedStorage, IsolatedStorage, IDisposable IDisposable public public class class IsolatedStorageFileStream IsolatedStorageFileStream :: FileStream FileStream
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Basic file I/O functionality, found in the System.IO root, provides the ability to access, store, and manipulate data that is stored in hierarchical file systems whose files are referenced by using unique paths. For some applications, such as downloaded Web applications and code that may come from un-trusted sources, the basic file system does not provide the necessary isolation and safety. Isolated storage is a data storage mechanism that provides isolation and safety by defining standardized ways of associating code with saved data.
Isolation When an application stores data in a file, the file name and storage location must be carefully chosen to minimize the possibility that the storage location will be known to another application and, therefore, vulnerable to corruption. Isolated storage provides the means to manage downloaded Web application files to minimize storage conflicts.
Security Risks of Semi-Trusted Code It is important to restrict semi-trusted code’s access from a computer's file system. Allowing code that has been downloaded and run from the Internet to have access to I/O functions leaves a system vulnerable to viruses and unintentional damage. The security risks associated with file access are sometimes addressed by using access control lists (ACLs), which restrict the access that users have to files. However, this approach is often not feasible with Web applications because it requires administrators to configure ACLs on all of the systems on which the application will run.
20
Module 10: Data Streams and Files
Safety Through Isolated Storage Administrators can use tools that are designed to manipulate isolated storage to configure file storage space, set security policies, and delete unused data. With isolated storage, code no longer needs to invent unique paths to specify safe locations in the file system, while data is protected from unauthorized access. There is no need for hard coding of information that indicates where an application’s storage area is located. With isolated storage, partially trusted applications can store data in a manner that is controlled by the computer’s security policy. Security policies rarely grant permission to access the file system by using standard I/O mechanisms. However, by default, code that runs from a local computer, a local network, or the Internet is granted the right to use isolated storage. Web applications can also use isolated storage with roaming user profiles, thereby allowing a user’s isolated stores to roam with their profile. The namespace System.IO.IsolatedStorage contains the IsolatedStorageFile and IsolatedStorageFileStream classes, which applications can use to access the files and directory in their isolated storage area. Further discussion of isolated storage is beyond the scope of this course.
Module 10: Data Streams and Files
21
Lab 10: Files Topic Objective
To introduce the lab.
Lead-in
In this lab, you will create an application that reads and writes characters to and from files, and create an application that can use StringReader and StreamReader objects to read character data from either files or strings.
*****************************ILLEGAL FOR NON-TRAINER USE******************************
Objectives After completing this lab, you will be able to: !
Create an application that reads and writes characters to and from files.
!
Create an application that can use StringReader and StreamReader objects to read character data from files or strings.
Lab Setup Starter and solution files are associated with this lab. The starter files are in the folder \Labs\Lab10\Starter, and the solution files are in the folder \Labs\Lab10\Solution.
Scenario In this lab, you are provided with a Microsoft Visual Studio® .NET console application as a starting point. The application, named Files, opens one or more files, which are specified on the command line, and counts each file’s bytes, characters, words, and lines. The results from each file and the total of all files are displayed on the console. The application supports an –f switch to allow the output display to be redirected to a file and a –t switch to run a special test mode of operation where input is obtained from a coded-in test string, instead of from user-specified files. The application is based on a slightly modified version of the .NET Framework SDK sample, Word Count.
Estimated time to complete this lab: 45 minutes
22
Module 10: Data Streams and Files
Exercise 1 Reading and Writing Files and Strings In this exercise, you will modify the Files application to output to a specified file.
! Examine the application 1. In Visual Studio .NET, open the Files project, which is located in \Labs\Lab10\Starter\Files. 2. Open the WordCount.cs file and examine the code. Pay attention to those methods that you will be modifying: The Main method of the Application class and the CountStats method of the WordCounter class. 3. Build the WordCount application. 4. Set the application’s command line arguments by performing the following steps: a. If the Solution Explorer pane in the Visual Studio .NET window is not visible, on the View menu, click Solution Explorer. b. In the Solution Explorer pane, right-click Files to display the contextsensitive menu, and then click Properties. c. In the Files Property Pages dialog box, click the Configuration Properties folder, and then click Debugging. d. In the right pane under Start Options, set the command line arguments, as follows: -a -o -t -foutput.txt test.txt
5. View the WordCount.cs file, and locate the last line in Application’s Main method, which is: return 0;
6. Right-click this line, and select Run To Cursor. You should see the following output: Replace this Console.WriteLine in Application's Main! method with code as per the lab Lines Words Chars Bytes Pathname Replace this Console.WriteLine in WordCounter's! CountStats method 0 0 0 0 Test String ------------------------------------0 0 0 0 Total in all files Word usage sorted alphabetically (0 unique words) Word usage sorted by occurrence (0 unique words)
7. Stop debugging, and in the Main method code of the Application class, locate the following line of code: if (ap.OutputFile != null) {
If ap.OutputFile does not contain null, then it contains the name of the output file that is specified in the –f command line switch.
Module 10: Data Streams and Files
23
8. Replace the following line’s Console.WriteLine call with code that: a. Assigns fsOut to a FileStream object that creates a file with the specified name with Write access and no file sharing. b. Assigns sw to a StreamWriter object that is bound to the FileStream object created in step a. c. Redirects the console’s output to the file by associating the StreamWriter object with the console by using the following command: Console.SetOut(sw);
9. Rebuild and run the application, and examine the output file that is specified in the command line switch options. The file should be located in the bin\Debug subdirectory. It should contain the following text: Lines Words Chars Bytes Pathname Replace this Console.WriteLine in WordCounter's! CountStats method 0 0 0 0 Test String ----- ----- ----- ----- --------------------0 0 0 0 Total in all files Word usage sorted alphabetically (0 unique words) Word usage sorted by occurrence (0 unique words)
! Add the test mode and normal mode CountStats processing 1. Locate the following lines in the CountStats method code of the WordCounter class: Boolean Ok = true; numLines = numWords = numChars = numBytes = 0; try {
If the –t option has been set, the CountStats caller will have set the pathname parameter to the empty string, which is called String.Empty. 2. Replace the following line’s Console.WriteLine call with code that: a. Declares a variable of type TextReader and names it tr. You use the type TextReader because it is the common base type for both StringReader and StreamReader. Polymorphism will allow code that uses a TextReader object to be provided with either a StringReader or StreamReader object. b. If the pathname is empty: i. Create a StringReader that is named sr, which is bound to a member of WordCounter that is named testString. ii. Assign the number of bytes in this string to numBytes. iii. Assign sr to tr. This assignment does an implicit cast of a StringReader to a TextReader.
24
Module 10: Data Streams and Files
c. If the pathname is not empty: i. Create a FileStream that is named fsIn by opening the file that is specified in the parameter pathname with read access and shared read access. ii. Assign the number of bytes in fsIn to numBytes. iii. Create a StreamReader that is named sr that is bound to this file. iv. Assign sr to tr. This assignment does an implicit cast of a StreamReader to a TextReader d. In this module, uncomment out the for loop that follows the comment: // Process every line in the file
e. Following the for loop, add code to close the TextReader object. 3. Rebuild and run the application, and examine the output file in the bin\Debug subdirectory. It should contain the following text: Lines Words Chars Bytes Pathname 2 3 16 17 Test String ----- ----- ----- ----- --------------------2 3 16 17 Total in all files Word usage sorted alphabetically (2 unique words) 2: "hello" 1: "world" Word usage sorted by occurrence (2 unique words) 1: world 2: hello
4. Change the command line options that Visual Studio .NET will use to run the application to remove the test switch. It should be set to: -a -o -foutput.txt test.txt
Module 10: Data Streams and Files
25
5. Run the application, and examine the output file in the bin\Debug subdirectory. It should contain the following text: Lines Words Chars Bytes Pathname 5 16 65 73 ...\test.txt ----- ----- ----- ----- --------------------5 16 65 73 Total in all files Word usage sorted alphabetically (14 unique words) 1: "aid" 1: "all" 1: "come" 1: "country" 1: "for" 1: "good" 1: "is" 1: "men" 1: "now" 1: "of" 2: "the" 1: "their" 1: "time" 2: "to" Word usage sorted by occurrence (14 unique words) 1: aid 1: all 1: come 1: country 1: for 1: good 1: is 1: men 1: now 1: of 1: their 1: time 2: the 2: to
6. Examine the file Test.txt in the bin\Debug subdirectory, and verify that the output is what you expected.
26
Module 10: Data Streams and Files
Review Topic Objective
To reinforce module objectives by reviewing key points.
Lead-in
The review questions cover some of the key concepts taught in the module.
!
Streams
!
Readers and Writers
!
Basic File I/O
*****************************ILLEGAL FOR NON-TRAINER USE****************************** 1. Name at least three types of .NET Framework streams and how they differ. FileStream does reads and writes to a file. MemoryStream does reads and writes to memory. BufferedStream is used to buffer reads and writes to another stream. NetworkStream provides the underlying stream of data for network access.
2. Name the three basic stream operations. Read, Write, and Seek.
3. Name the classes that are used to read and write primitive types as binary values. BinaryReader and BinaryWriter.
4. Name the method used to provide random access to files. Seek.
Module 10: Data Streams and Files
5. Name the class that you would use to monitor changes to a file system. FileSystemWatcher.
6. Name the two important features that the .NET Framework’s isolated storage provides for an application. Isolation and Safety.
27
THIS PAGE INTENTIONALLY LEFT BLANK
Module 11: Internet Access Contents Overview
1
Internet Application Scenarios
2
The WebRequest and WebResponse Model
3
Application Protocols
16
Handling Errors
25
Security
28
Best Practices
35
Lab 11: Creating a DateTime Client/Server Application 36 Review
41
Course Evaluation
43
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, place or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. 2001-2002 Microsoft Corporation. All rights reserved. Microsoft, ActiveX, BizTalk, IntelliMirror, Jscript, MSDN, MS-DOS, MSN, PowerPoint, Visual Basic, Visual C++, Visual C#, Visual Studio, Win32, Windows, Windows Media, and Window NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
Module 11: Internet Access
iii
Instructor Notes Presentation: 60 Minutes Lab: 45 Minutes
After completing this module, students will be able to: !
Use the basic request/response model to send and receive data over the Internet.
!
Use the System.Net classes to communicate with other applications by using the HTTP, Transmission Control Protocol (TCP), User Datagram Protocol (UDP), and Socket Internet protocols.
Materials and Preparation This section provides the materials and preparation tasks that you need to teach this module.
Required Materials To teach this module, you need the Microsoft® PowerPoint® file 2349B_11.ppt.
Preparation Tasks To prepare for this module, you should: !
Read all of the materials for this module.
!
Complete the lab.
iv
Module 11: Internet Access
Module Strategy Use the following strategy to present this module: !
Internet Application Scenarios Briefly introduce examples of Internet applications that use the System.Net classes, including server-side ASP.NET applications, peer-to-peer Microsoft Windows® Forms applications that act as servers and clients to send and receive data, and client applications that periodically access the network for updates.
!
The WebRequest and WebResponse Model Introduce the WebRequest and WebResponse model. Explain how the Microsoft .NET Framework uses the Uniform Resource Identifier (URI) to identify the desired communication protocol and Internet resource. Discuss network streams as the means of obtaining and receiving Web data. Explain how to use the WebRequest class to request data from a server, invoke the request for the Internet resource, and send data through a network stream. Discuss how the WebResponse.GetResponseStream method serves as the means of obtaining a stream that contains response data from a network resource.
!
Application Protocols Discuss the HTTP, TCP, and UDP protocol support that is provided in the .NET Framework, as well as information about using the Windows Sockets interface to implement custom protocols.
!
Handling Errors Discuss how the WebRequest and WebResponse classes can throw system exceptions, such as InvalidArgumentException, and Web-specific exceptions, which are instances of WebException and thrown by the GetResponse method.
!
Security Explain how an application can provide security for sending and receiving data over the Internet by using a Web proxy, Secure Sockets Layer (SSL) encryption, Internet authentication, and the NET Framework code access permissions.
!
Best Practices Briefly outline the list of recommendations that will help students use the classes that are contained in System.Net more effectively.
Module 11: Internet Access
1
Overview Topic Objective
To provide an overview of the module topics and objectives.
Lead-in
In this module, you will learn about the basic request/response model that is used to send and receive data over the Internet, the System.Net classes that are used to communicate with other applications, and various techniques to enhance application security and performance.
!
Internet Application Scenarios
!
The WebRequest and WebResponse Model
!
Application Protocols
!
Handling Errors
!
Security
!
Best Practices
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The Microsoft® .NET Framework System.Net and System.Net.Sockets namespaces provide a layered, extensible, and managed implementation of Internet protocols that applications can use to send or receive data over the Internet. The System.Net classes provide functionality that is similar to the Microsoft WinInet API. These classes provide varying levels of detail, from a generic request/response model to control over application protocols and sockets. In particular, the System.Net classes are designed for writing scaleable, high-performance applications. An application can use the System.Net classes to communicate with any other application that supports the basic Internet protocols. However, that other application need not be a .NET application. The .NET Framework provides alternative mechanisms for inter-application communication. For example, .NET Framework remoting implements a generic mechanism for .NET Framework objects to interact with one another across application domains. In addition, the System.Web.Services namespace contains classes for applications to build and use XML Web services that are based on the standard Simple Object Access Protocol (SOAP). For more information about the remoting and XML Web services approaches to inter-application communication, see Module 12, “Serialization,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C#™ .NET). After completing this module, you will be able to: !
Use the basic request/response model to send and receive data over the Internet.
!
Use the System.Net classes to communicate with other applications by using the Hypertext Transfer Protocol (HTTP), Transmission Control Protocol (TCP), User Datagram Protocol (UDP), and Socket Internet protocols.
2
Module 11: Internet Access
Internet Application Scenarios Topic Objective
To introduce Internet application scenarios that use the System.Net classes.
!
Lead-in
!
Server-Side ASP.NET Applications "
Several types of applications use the System.Net classes to send or receive data over the Internet.
Peer-to-Peer Applications "
!
Obtain data from back-end sources for a browser request Send and receive data by acting as servers and clients
Client Applications That Periodically Access the Network "
A robust implementation of HTTP 1.1, including: Pipelining, chunking, authentication, pre-authentication, encryption, proxy support, server certificate validation, and connection management
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Several types of applications use the System.Net classes to send or receive data over the Internet. The following three Internet application scenarios are examples of Internet applications that use the System.Net classes: !
Server-side ASP.NET applications that request data from server resources in response to a browser request. The System.Net classes are designed for writing scalable, high-performance ASP.NET middle-tier applications. The server-side ASP.NET scenario requires a robust middle-tier networking stack that can tolerate a high load. The System.Net classes specifically fulfill this important customer requirement. Such features as connection management, pipelining, Keep-alive, and asynchronous operations ensure strong support for the middle tier. In addition, because the System.Net classes are part of an overall framework, integration with ASP.NET features, such as impersonation and caching, is seamless.
!
Peer-to-peer Windows Forms applications that act as servers and clients to send and receive data.
!
Client applications that periodically access the network for updates. The System.Net classes expose a robust implementation of the HTTP protocol. Because a large share of Internet traffic travels over the HTTP protocol, the protocol’s importance as an application protocol is significant. The System.Net classes support most of the HTTP 1.1 protocol features. The advanced features of HTTP 1.1 include pipelining, chunking, authentication, pre-authentication, encryption, proxy support, server certificate validation, connection management, and HTTP extensions.
Module 11: Internet Access
3
# The WebRequest and WebResponse Model Topic Objective
To introduce the topics in the section.
Lead-in
Internet applications can be classed broadly into two types: client applications that request information, and server applications that respond to information requests from clients.
!
Uniform Resource Identifier
!
NetworkStream Class
!
Creating a WebRequest
!
Invoking a WebRequest
!
Sending Data
!
Receiving Data
!
Using the WebRequest and WebResponse Model
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Internet applications can be classed broadly into two types: client applications that request information, and server applications that respond to information requests from clients. The classic Internet client/server application is the World Wide Web, where people use browsers to access documents and other data that is stored on Web servers worldwide. Applications are not limited to playing either the client or server role; the familiar middle-tier application server responds to requests from clients by requesting data from another server. In this case, it is acting as both a server and a client. The client application makes a request by identifying the desired Internet resource and the communication protocol that will be used to exchange the request and response. If necessary, the client application also specifies any additional data that is required to complete the request, such as proxy location or authentication information. Authentication information includes such information as user name and password. When the request is formed, it can be sent to the server. After the server has received the request and processed the response, the response is returned to the client application. The response includes information that supplements the contents of the response, such as the type of content, which may include raw text or XML data.
4
Module 11: Internet Access
The .NET Framework provides classes that can be used to implement a request/response model to access Internet resources. The two principal classes are the WebRequest class, which contains a request for the resource; and the WebResponse class, which provides a container for the incoming response. In addition, the Uri class is used to contain a Uniform Resource Identifier (URI), which identifies the Internet resource that you are seeking. The NetworkStream class is used to write and read the data. Note A Uniform Resource Identifier URI is a compact representation of a resource that is available to your application through the Internet. You may be more familiar with the term, URL, which stands for Uniform Resource Locator. URLs form a subset of the more general URI naming scheme. A URL identifies an Internet resource that has a Web page address. For applications that need to make simple requests for Internet resources, the WebClient class provides common methods for uploading data to or downloading data from an Internet server. WebClient relies on the WebRequest class to provide access to Internet resources; therefore, the WebClient class can use any registered pluggable protocol.
Module 11: Internet Access
5
Uniform Resource Identifier Topic Objective
To introduce the four parts of the URI.
!
Lead-in
The .NET Framework uses the URI to identify the desired communication protocol and Internet resource.
!
URI Contains: "
Scheme identifier – specifies protocol to be used
"
Server identifier – specifies DNS name or TCP address
"
Path identifier – specifies location on the server
"
Optional query string – provides additional request information
Example: http://www.contoso.com/whatsnew.aspx?date=today "
Scheme identifier – http
"
Server identifier – www.contoso.com
"
Path identifier – /whatsnew.aspx
"
Query String – ?date=today
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The .NET Framework uses the URI to identify the desired communication protocol and Internet resource. The URI consists of at least three, and possibly four, parts: !
The scheme identifier, which identifies the communications protocol that is used by the request and response
!
The server identifier, which consists of a Domain Name System (DNS) host name or TCP address that uniquely identifies the server on the Internet
!
The path identifier, which locates the requested information on the server
!
An optional query string, which passes information from the client to the server
For example, the URI http://www.contoso.com/whatsnew.aspx?date=today consists of the scheme identifier http, the server identifier www.contoso.com, the path identifier /whatsnew.aspx, and the query string ?date=today.
6
Module 11: Internet Access
NetworkStream Class Topic Objective
!
To explain the function of Network streams in the .NET Framework.
Lead-in
When resources on the Internet are obtained by using the System.Net classes, the data that is being sent and received is represented through a Stream object.
!
A NetworkStream Object Provides: "
A Way to Send and Receive All Types of Web Data
"
Methods That Are Compatible with Other .NET Streams
"
Processing of Data As It Arrives
System.Text.Encoding – Characters from and to Bytes
// // reading reading ASCII ASCII stream stream to to string string Byte[] read = new Byte[32]; Byte[] read = new Byte[32]; int int bytes bytes == anASCIIStream1.Read(read, anASCIIStream1.Read(read, 0, 0, read.Length); read.Length); string string stringData stringData == Encoding.ASCII.GetString(read); Encoding.ASCII.GetString(read); // // writing writing string string to to ASCII ASCII stream stream Byte[] Byte[] asciiBytes asciiBytes == Encoding.ASCII.GetBytes(stringData); Encoding.ASCII.GetBytes(stringData); anASCIIStream2.Write(asciiBytes, anASCIIStream2.Write(asciiBytes, 0, 0, asciiBytes.Length); asciiBytes.Length); !
Sequential Blocks Use StreamReader and StreamWriter
*****************************ILLEGAL FOR NON-TRAINER USE****************************** When resources on the Internet are obtained by using the System.Net classes, a Stream object represents the data that is being sent and received. Streams provide: !
A common way to send and receive Web data. Whether the actual contents of the file are HTML, XML, or another format, an application uses Stream.Write and Stream.Read to send and receive byte data.
!
Compatibility with streams across the .NET Framework. Streams are used throughout the .NET Framework, which provides a rich infrastructure for handling them. For example, by changing only the few lines of code that initialize the stream, you can modify an application that reads XML data from a file stream to read data from a network stream instead. The major differences between the NetworkStream class and other streams are that the NetworkStream class is not seekable, the CanSeek property always returns false, and the Seek and Position methods throw a NotSupportedException.
!
Processing of data as it arrives. Streams provide access to data as it arrives from the Internet, rather than forcing your application to wait for an entire data set to be downloaded.
Module 11: Internet Access
Conversion Between Characters and Bytes The System.Text namespace contains classes for converting blocks of characters to and from blocks of bytes. In particular, the Encoding class has methods to convert arrays and strings of Unicode characters to and from arrays of bytes, as in the following example: // variable named anASCIIStream1 of type Stream // has been previously assigned to a Stream // containing bytes representing 7 bit ASCII character // // reading ASCII stream and converting to string Byte[] read = new Byte[32]; int bytes = anASCIIStream1.Read(read, 0, read.Length); string stringData = Encoding.ASCII.GetString(read); // ... // variable named anASCIIStream2 of type Stream // has been previously assigned to a writable Stream // // converting string and writing 7 bit ASCII characters Byte[] asciiBytes = Encoding.ASCII.GetBytes(stringData); anASCIIStream2.Write(asciiBytes, 0, asciiBytes.Length);
Converting Data from Sequential Blocks When the data that must be converted is only available in sequential blocks, such as data that is read from a long stream, an application may choose to use a decoder or an encoder to perform the conversion. However, you can use the StreamReader and StreamWriter classes to facilitate decoding and encoding characters, as in the following example: // variable anASCIIStream has been previously assigned // to a Stream of ASCII bytes StreamReader sr = new StreamReader( anASCIIStream,Encoding.ASCII); int length = 1024; char[] Buffer = new char[1024]; int bytesread = 0; //Read from the stream and write data to console bytesread = sr.Read( Buffer, 0, length); while( bytesread > 0 ) { Console.Write( Buffer,0, bytesread); bytesread = sr.Read( Buffer, 0, length); } //Close the stream when finished sr.Close();
7
8
Module 11: Internet Access
Creating a WebRequest Topic Objective
To describe how client applications use the WebRequest.
Lead-in
Client applications request data from servers by using the WebRequest class and its descendents.
!
The WebRequest Encapsulates Details of Request "
Created by calling WebRequest.Create method
WebRequest WebRequest req req == WebRequest.Create("http://www.contoso.com/"); WebRequest.Create("http://www.contoso.com/"); "
Set any property values that are required
req.Credentials req.Credentials == new new NetworkCredential("username","password"); NetworkCredential("username","password"); "
Cast to access protocol-specific features
HttpWebRequest HttpWebRequest httpReq httpReq == (HttpWebRequest) (HttpWebRequest) WebRequest.Create("http://www.contoso.com/"); WebRequest.Create("http://www.contoso.com/"); // // Turn Turn off off connection connection keep-alives. keep-alives. httpReq.KeepAlive httpReq.KeepAlive == false; false;
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Client applications request data from servers by using the WebRequest class and its descendents. The WebRequest class encapsulates the details of the process of connecting to the server, sending the request, and receiving the response.
Calling WebRequest.Create Applications create WebRequest instances through the static WebRequest.Create method. WebRequest.Create is a static method that creates a descendent WebRequest instance that is based on the URI scheme that is passed. For example, the following code creates an HTTP request to www.contoso.com: WebRequest req = WebRequest.Create("http://www.contoso.com/");
Setting Required Property Values Clients set required property values in the WebRequest instance. For example, to support authentication, you can set the Credentials property to an instance of the NetworkCredential class, as shown in the following code: req.Credentials = new NetworkCredential("username","password");
Module 11: Internet Access
Casting WebRequest Objects to HttpWebRequest To handle HTTP protocol requests to the Internet, the .NET Framework provides an HttpWebRequest class that is derived from the WebRequest class. In most cases, the WebRequest class provides all of the properties that you need to make a request. However, if you need to access the HTTP protocol-specific properties of the request, you can typecast WebRequest objects that are created by the WebRequest.Create to HttpWebRequest, as in the following example: HttpWebRequest httpReq = (HttpWebRequest) WebRequest.Create("http://www.contoso.com/"); // Turn off connection keep-alives. httpReq.KeepAlive = false;
Supporting Additional Protocols The .NET Framework provides protocol-specific WebRequest and WebResponse descendants for URIs that begin with http:, https:, and file:. The programmable pluggable protocols of System.Net allow applications to provide access through other protocols. To support additional protocols, you should implement protocol-specific descendants of WebRequest and WebResponse and register the descendant’s constructor with the WebRequest.RegisterPrefix method. For more information about supporting additional protocols, see the .NET Framework Software Development Kit (SDK) documentation.
9
10
Module 11: Internet Access
Invoking a WebRequest Topic Objective
To explain how to invoke the request for an Internet resource by calling the GetResponse method on the WebRequest instance.
!
Request Is Made by Calling the GetResponse Method
WebResponse WebResponse resp resp == req.GetResponse(); req.GetResponse();
Lead-in
After creating the WebRequest, you invoke the request for the Internet resource by calling the GetResponse method on the WebRequest instance.
"
Cast to access HTTP-specific features
HttpWebResponse HttpWebResponse httpResp httpResp == (HttpWebResponse)httpReq.GetResponse(); (HttpWebResponse)httpReq.GetResponse(); //Get //Get the the HTTP HTTP content content length length returned returned by by the the server. server. String contentLength = String contentLength = httpResp.ContentLength.ToString(); httpResp.ContentLength.ToString();
*****************************ILLEGAL FOR NON-TRAINER USE****************************** After creating the WebRequest, you invoke the request for the Internet resource by calling the GetResponse method on the WebRequest instance. The GetResponse method is responsible for: !
Constructing the protocol-specific request from the properties of the WebRequest instance.
!
Making the TCP or UDP socket connection to the server.
!
Sending the request.
The GetResponse method returns an instance that is derived from WebResponse that matches the instance that is derived from WebRequest, as in the following example: WebResponse resp = req.GetResponse();
The WebResponse class is also an abstract class that defines properties and methods that are available to all applications that use pluggable protocols. WebResponse descendents are responsible for implementing these properties and methods for the underlying protocol. For example, the HttpWebResponse class implements the WebResponse class for the HTTP protocol. If you need to access the HTTP protocol-specific properties of the response, you can typecast WebResponse objects to HttpWebResponse, as in the following example: HttpWebResponse httpResp = (HttpWebResponse)httpReq.GetResponse(); //Get the HTTP content length returned by the server. String contentLength = httpResp.ContentLength.ToString();
Module 11: Internet Access
11
Sending Data Topic Objective
To describe how requests that send data to the server are created.
Lead-in
For requests that send data to the server, such as HTTP-POST or FTP-PUT requests, the data is sent through a network stream that is provided by the WebRequest.GetRequest Stream method.
!
// // ... ... try try {{
For Requests That Send Data to the Server
byte[] byte[] sendData sendData == Encoding.ASCII.GetBytes("some Encoding.ASCII.GetBytes("some data"); data"); int int sendLength sendLength == sendData.Length; sendData.Length; HttpWebRequest HttpWebRequest httpReq httpReq == (HttpWebRequest) (HttpWebRequest) WebRequest.Create( WebRequest.Create( "http://www.contoso.com/"); "http://www.contoso.com/"); httpReq.Method httpReq.Method == "POST"; "POST"; httpReq.ContentLength httpReq.ContentLength == sendLength; sendLength; Stream Stream sendStream sendStream == httpReq.GetRequestStream(); httpReq.GetRequestStream(); sendStream.Write(sendData,0,sendLength); sendStream.Write(sendData,0,sendLength); sendStream.Close(); sendStream.Close();
}} catch(Exception catch(Exception e) e) {//...} {//...} //... //...
*****************************ILLEGAL FOR NON-TRAINER USE****************************** For requests that send data to the server, such as HTTP-POST or FTP-PUT requests, the data is sent through a network stream that is provided by the WebRequest.GetRequestStream method. You use the resulting Stream object to write the data. When you have finished uploading, you must close the request stream with the Stream.Close method. The following example shows how to create a request that uses HTTP-POST to send data to the server: // ... try { byte[] sendData = Encoding.ASCII.GetBytes("some data"); int sendLength = sendData.Length; HttpWebRequest httpReq = (HttpWebRequest) WebRequest.Create( "http://www.contoso.com/"); httpReq.Method = "POST"; httpReq.ContentLength = sendLength; Stream sendStream = httpReq.GetRequestStream(); sendStream.Write(sendData,0,sendLength); sendStream.Close(); } catch(Exception e) {//... } //...
After closing the stream, you can call GetResponse to ensure that the server received the data correctly.
12
Module 11: Internet Access
Receiving Data Topic Objective
To explain how to obtain the stream that contains response data from the network.
Lead-in
To obtain the stream that contains response data from the network resource, use the GetResponseStream method of the WebResponse instance.
!
Reading Response Data
// // Get Get the the response response stream. stream. Stream respstrm Stream respstrm == resp.GetResponseStream(); resp.GetResponseStream(); // // Create Create aa buffer buffer to to hold hold the the response response data. data. int BufferSize = 512; int BufferSize = 512; Byte[] Byte[] Buffer Buffer == new new Byte[BufferSize]; Byte[BufferSize]; // // Read Read the the stream stream to to access access the the data. data. int int bytesRead bytesRead == respstrm.Read(Buffer, respstrm.Read(Buffer, 0, 0, BufferSize); BufferSize); while (bytesRead > 0) { while (bytesRead > 0) { Console.Write( Console.Write( Encoding.ASCII.GetString(Buffer, Encoding.ASCII.GetString(Buffer, 0, 0, bytesRead)); bytesRead)); bytesRead = respstrm.Read(Buffer, 0, bytesRead = respstrm.Read(Buffer, 0, BufferSize); BufferSize); }} respstrm.Close(); respstrm.Close();
*****************************ILLEGAL FOR NON-TRAINER USE****************************** To obtain the stream that contains response data from the network resource, use the GetResponseStream method of the WebResponse instance, as in the following example: // Get the response stream. Stream respstrm = resp.GetResponseStream(); // Create a buffer to hold the response data. int BufferSize = 512; Byte[] Buffer = new Byte[BufferSize]; // Read the stream to access the data. int bytesRead = respstrm.Read(Buffer, 0, BufferSize); while (bytesRead > 0) { Console.Write( Encoding.ASCII.GetString(Buffer, 0, bytesRead)); bytesRead = respstrm.Read(Buffer, 0, BufferSize); } respstrm.Close();
If your application requires only the header information that is returned in the WebResponse and ignores any returned data, then you do not need to get the response stream.
Closing Responses After reading the data from the response, you must close any opened stream by using the Stream.Close method or close the response by using the WebResponse.Close method, as in the following example: resp.Close();
Module 11: Internet Access
13
You do not have to call the Close method on both the response stream and the WebResponse instance, but it is recommended. WebResponse.Close calls Stream.Close when it closes the response. If you do not close each response, your application will run out of connections to the server and be unable to process additional requests.
Considerations for Working with NetworkStreams When using streams from network resources, remember the following: !
Because the NetworkStream class cannot change position in the stream, the CanSeek property always returns false. The Seek and Position methods throw a NotSupportedException.
!
When you use WebRequest and WebResponse, stream instances that are created by calling GetResponseStream are read-only, and stream instances that are created by calling GetRequestStream are write-only.
!
The call to GetResponse may block if network resources are not available. You should consider using an asynchronous request with the BeginGetResponse and EndGetResponse methods.
!
The call to GetRequestStream may block while the connection to the server is created. You should consider using an asynchronous request for the stream with the BeginGetRequestStream and EndGetRequestStream methods. Asynchronous operations are beyond the scope of this course.
!
You can use the StreamReader class to make the task of encoding easier. The following code example uses a StreamReader to read an ASCIIencoded stream from a WebResponse instance. The creation of the request is not shown. // Create a response object. WebResponse response = request.GetResponse(); // Get a readable stream from the server. StreamReader sr = new StreamReader( response.GetResponseStream(), Encoding.ASCII); // Use the stream. Remember when you are through // with the stream to close it //... sr.Close();
14
Module 11: Internet Access
Using the WebRequest and WebResponse Model Topic Objective
//... //... WebRequest WebRequest wReq wReq == WebRequest.Create WebRequest.Create ("http://localhost/postinfo.html"); ("http://localhost/postinfo.html"); WebResponse WebResponse wResp wResp == wReq.GetResponse(); wReq.GetResponse(); // // Get Get aa readable readable stream stream from from the the server server StreamReader StreamReader sr sr == new new StreamReader( StreamReader( wResp.GetResponseStream(),Encoding.ASCII); wResp.GetResponseStream(),Encoding.ASCII); int int length length == 1024; 1024; char[] char[] Buffer Buffer == new new char[1024]; char[1024]; int int bytesread bytesread == 0; 0; //Read //Read from from the the stream stream and and write write data data to to console console bytesread bytesread == sr.Read( sr.Read( Buffer, Buffer, 0, 0, length); length); while( while( bytesread bytesread >> 00 )) {{ Console.Write( Console.Write( Buffer,0, Buffer,0, bytesread); bytesread); bytesread bytesread == sr.Read( sr.Read( Buffer, Buffer, 0, 0, length); length); }} //Close //Close the the stream stream when when finished finished sr.Close(); sr.Close(); //... //...
To provide an example of the WebRequest and WebResponse model.
Lead-in
You will now see an example of the use of the WebRequest and WebResponse model as discussed in this section.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The following code example fully demonstrates the use of the WebRequest and WebResponse model as discussed in this section: using using using using
System; System.Net; System.IO; System.Text;
class App { public static void Main(string[] args) { try { WebRequest wReq = WebRequest.Create ("http://localhost/postinfo.html"); WebResponse wResp = wReq.GetResponse(); // Get a readable stream from the server StreamReader sr = new StreamReader( wResp.GetResponseStream(),Encoding.ASCII); int length = 1024; char[] Buffer = new char[1024]; int bytesread = 0;
(Code continued on the following page.)
Module 11: Internet Access //Read from the stream and write data to console bytesread = sr.Read( Buffer, 0, length); while( bytesread > 0 ) { Console.Write( Buffer,0, bytesread); bytesread = sr.Read( Buffer, 0, length); } //Close the stream when finished sr.Close(); } catch(Exception e) { Console.WriteLine( "\r\nThe request URI could not be found or was! malformed:\n {0}", e.ToString()); } } }
15
16
Module 11: Internet Access
# Application Protocols Topic Objective
To introduce the topics in the section.
Lead-in
The .NET Framework supports application protocols that are currently in common use on the Internet.
!
HTTP
!
Internet Domain Name System
!
TCP and UDP
!
Sockets
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The .NET Framework supports application protocols that are currently in common use on the Internet. This section provides information about using the HTTP, TCP, and UDP protocol support that is provided in the .NET Framework, in addition to information about using the Windows Sockets interface to implement custom protocols.
Module 11: Internet Access
17
HTTP Topic Objective
To describe the support that the .NET Framework provides for the HTTP protocol.
Lead-in
With the HttpWebRequest and HttpWebResponse classes, the .NET Framework provides comprehensive support for the HTTP protocol, which makes up the majority of all Internet traffic.
!
Classes That Provide HTTP and HTTPS Protocols "
HttpWebRequest and HttpWebResponse
!
Support Most HTTP 1.1 features
!
HTTP Redirects Automatically if AllowAutoRedirect Property Is true (the Default)
!
Use ServicePoint, ServicePointManager, and ConnectionGroupName Classes to Manage Connections
*****************************ILLEGAL FOR NON-TRAINER USE****************************** With the HttpWebRequest and HttpWebResponse classes, the .NET Framework provides comprehensive support for the HTTP protocol, which makes up the majority of all Internet traffic.
Classes for the HTTP and HTTPS Protocols The HttpWebRequest and HttpWebResponse classes are derived from the WebRequest and WebResponse classes, as was described in Creating a WebRequest in this module. HttpWebRequest and HttpWebResponse are returned by default whenever the static method WebRequest.Create encounters a URI that begins with http or https. In most cases, the WebRequest and WebResponse classes provide all that is necessary to make the request, but if you need access to the HTTP-specific features that are exposed as properties, you can typecast these classes to HttpWebRequest or HttpWebResponse, as demonstrated in Creating a WebRequest in this module. Note Do not use the HttpWebRequest constructor. Use the WebRequest.Create method to initialize new HttpWebRequest instances. If the scheme for the URI is http:// or https://, Create returns an HttpWebRequest instance.
Support for HTTP 1.1 Features The HttpWebRequest and HttpWebResponse classes encapsulate a standard HTTP request/response transaction and provide access to common HTTP headers. These classes also support most HTTP 1.1 features, including pipelining, chunking, authentication, pre-authentication, encryption, proxy support, server certificate validation, and connection management. Custom headers and headers that are not provided through properties can be stored in and accessed through the Headers property.
18
Module 11: Internet Access
Support for HTTP Redirects You can make your application follow HTTP redirects automatically by setting the AllowAutoRedirect property to true, which is the default value. The application redirects requests, and the ResponseURI property of HttpWebResponse contains the actual Internet resource that responds to the request. If you set AllowAutoRedirect to false, your application must be able to handle redirects as HTTP protocol errors. Applications receive HTTP protocol errors by catching a WebException with the value of the Status property set to WebExceptionStatus.ProtocolError. The Response property contains the WebResponse that is sent by the server and indicates the actual HTTP error that is encountered.
Managing Internet Connections Applications that use HTTP to connect to data resources can use the .NET Framework ServicePoint and ServicePointManager classes to manage the number of connections to the Internet and to optimize scale and performance. The number of connections between a client and server can have a dramatic effect on application throughput. You can use ConnectionGroupName to form connection grouping that associates specific requests within a single application to a defined connection pool. You may have to use this technique with a middle-tier application that connects to a back-end server on behalf of a user and uses an authentication protocol that supports delegation, such as Kerberos, or by a middle-tier application that supplies its own credentials. For more information about classes that are used to manage connections and their methods, see the .NET Framework SDK documentation.
Module 11: Internet Access
19
Internet Domain Name System Topic Objective
To explain how to use the Dns class to retrieve information about a host.
Lead-in
The Dns class is a static class that retrieves information about a specific host from the Internet Domain Name System.
!
Dns Class Retrieves Data About a Host from DNS "
GetHostByName query for www.contoso.com
IPHostEntry IPHostEntry hostInfo hostInfo == Dns.GetHostByName("www.contoso.com"); Dns.GetHostByName("www.contoso.com"); "
Resolve query for www.contoso.com
IPHostEntry IPHostEntry hostInfo hostInfo == Dns.Resolve("www.contoso.com"); Dns.Resolve("www.contoso.com");
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The Dns class is a static class that retrieves information about a specific host from the Internet Domain Name System (DNS). The host information from a GetHostByName query is returned in an instance of the IPHostEntry class. If the specified host has more than one entry in the DNS database, IPHostEntry contains multiple Internet Protocol (IP) addresses and aliases. The following example queries the DNS database for information about a host that is called www.contoso.com: IPHostEntry hostInfo = Dns.GetHostByName("www.contoso.com");
The Resolve method queries a DNS server for the IP address that is associated with a host name, such as www.contoso.com, or for an IP address in dotted-quad notation, such as 192.168.1.2. When the host name is a DNS-style host name that is associated with multiple IP addresses, only the first IP address that resolves to that host name is returned, as in the following example: IPHostEntry hostInfo = Dns.Resolve("www.contoso.com");
20
Module 11: Internet Access
TCP and UDP Topic Objective
To explain how to use the TcpClient and TcpListener classes to request data from an Internet resource and to monitor TCP ports.
Lead-in
Applications can use TCP and UDP services with the TcpClient, TcpListener, and UdpClient classes.
!
TCP Client Connecting to a Server/Listener
TcpClient TcpClient tcpc tcpc == new new TcpClient(serverURI, TcpClient(serverURI, 14); 14); Stream Stream ss == tcpc.GetStream(); tcpc.GetStream(); Byte[] Byte[] read read == new new Byte[32]; Byte[32]; int bytes = s.Read(read, int bytes = s.Read(read, 0, 0, read.Length); read.Length); String strInData = Encoding.ASCII.GetString(read); String strInData = Encoding.ASCII.GetString(read); !
TCP Server/Listener Monitors Port for Clients
TcpListener TcpListener tcpl tcpl == new new TcpListener(14); TcpListener(14); tcpl.Start(); tcpl.Start(); while {{ while (!done) (!done) // Accept will block // Accept will block until until someone someone connects connects Socket s = tcpl.AcceptSocket(); Socket s = tcpl.AcceptSocket(); //Code //Code to to handle handle request request goes goes here here }} tcpl.Stop(); tcpl.Stop();
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Applications can use TCP and UDP services with the TcpClient, TcpListener, and UdpClient classes. These classes, which are built on top of the Socket class, represent the data that is sent and received from the network as streams. These classes also take care of the details of creating a connection.
Using the TcpClient Class You use the TcpClient class to request data from an Internet resource that uses TCP. The methods and properties of TcpClient abstract the details for creating a Socket instance that requests and receives data through TCP. The connection to the Internet is represented as a stream; therefore data can be read and written in a standard manner. The following code demonstrates how to set up a TcpClient object to connect to a server on TCP port 14: TcpClient tcpc = new TcpClient(serverURI, 14); Stream s = tcpc.GetStream(); Byte[] read = new Byte[32]; int bytes = s.Read(read, 0, read.Length); String strInData = Encoding.ASCII.GetString(read);
Module 11: Internet Access
21
Using the TcpListener Class You use a TcpListener object to monitor a TCP port for incoming requests and then create a Socket instance that manages the connection to the client. The Start method enables listening, and the Stop method disables listening on the port. The AcceptSocket method accepts incoming connection requests and creates the socket that will handle the request. The following code demonstrates how to set up a TcpListener object to monitor TCP port 14: TcpListener tcpl = new TcpListener(14); // listen on port 14 tcpl.Start(); while (!done) { // Accept will block until someone connects Socket s = tcpl.AcceptSocket(); //Code to handle request goes here } tcpl.Stop();
22
Module 11: Internet Access
Sockets Topic Objective
To describe the Socket class and provide an example of how the Socket class can be used to send data to an HTTP server and to receive the response.
!
System.Net Classes Are Built on System.Net.Sockets
!
System.Net.Sockets Are Based on the WinSock32 API
!
See Example in Student Notes
Lead-in
The System.Net.Sockets namespace contains an implementation of the Windows Sockets interface.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The System.Net.Sockets namespace contains an implementation of the Windows Sockets interface. All other network access classes in the System.Net namespace are built on top of this implementation of sockets. The .NET Framework’s Socket class is a managed code version of the socket services that are provided by the WinSock32 API. If you are familiar with the Winsock API, you should be comfortable using the Socket class to develop applications. In most cases, the Socket class methods simply marshal data into their native Microsoft Win32® counterparts and handle necessary security checks.
Module 11: Internet Access
23
The following example shows how the Socket class can be used to send data to an HTTP server and receive the response: using using using using using
System; System.Net; System.Net.Sockets; System.IO; System.Text;
class App { public static void Main(string[] args) { DoSocketGet("localhost"); } public static string DoSocketGet(string server) { //Set up variables and String to write to the server Encoding ASCII = Encoding.ASCII; string Get = "GET / HTTP/1.1\r\nHost: " + server + "\r\nConnection: Close\r\n\r\n"; Byte[] ByteGet = ASCII.GetBytes(Get); Byte[] RecvBytes = new Byte[256]; String strRetPage = ""; // IPHostEntry and IPEndPoint // represent the endpoint that // will receive the request IPHostEntry hostEntry = Dns.Resolve(server); IPEndPoint EPhost = new IPEndPoint(hostEntry.AddressList[0], 80); //Create the Socket for sending data over TCP Socket mySocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp ); // Connect to host using IPEndPoint try { mySocket.Connect(EPhost); // Sent the GET text to the host mySocket.Send(ByteGet, ByteGet.Length, 0); // Receive the page, loop until all received Int32 bytes = mySocket.Receive(RecvBytes, RecvBytes.Length, 0); strRetPage = "Default HTML page on " + server + ":\r\n"; strRetPage = strRetPage + ASCII.GetString(RecvBytes, 0, bytes);
(Code continued on the following page.)
24
Module 11: Internet Access while (bytes > 0) { bytes = mySocket.Receive(RecvBytes, RecvBytes.Length, 0); strRetPage = strRetPage + ASCII.GetString(RecvBytes, 0, bytes); } } catch (Exception e) { Console.WriteLine("Exception {0}",e.ToString()); } return strRetPage; } }
Module 11: Internet Access
25
Handling Errors Topic Objective
!
To explain how the Status property is used to determine if an error has occurred.
Lead-in
The WebRequest and WebResponse classes throw system exceptions and Web-specific exceptions, which are instances of WebException and thrown by the GetResponse method.
A WebException Can Be Thrown by GetResponse " "
Has Status property, value from WebExceptionStatus If WebExceptionStatus.ProtocolError, then WebResponse contains protocol error information
try try {{ // // ... ... Create Create aa request, request, get get response response and and process process stream. stream. }} catch catch (WebException (WebException webExcp) webExcp) {{ Console.WriteLine("A Console.WriteLine("A WebException WebException has has been been caught"); caught"); Console.WriteLine(webExcp.ToString()); Console.WriteLine(webExcp.ToString()); WebExceptionStatus WebExceptionStatus status status == webExcp.Status; webExcp.Status; if if (status (status == == WebExceptionStatus.ProtocolError) WebExceptionStatus.ProtocolError) {{ Console.Write( Console.Write( "The "The server server returned returned protocol protocol error error "); "); Console.WriteLine(webExcp.Response.ToString()); Console.WriteLine(webExcp.Response.ToString()); }} }} catch catch (Exception (Exception e) e) {// {// Code Code to to catch catch other other exceptions exceptions goes goes here.} here.}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The WebRequest and WebResponse classes throw system exceptions, such as InvalidArgumentException, and Web-specific exceptions, which are instances of WebException and are thrown by the GetResponse method. Each WebException includes a Status property that contains a value from the WebExceptionStatus class. You can examine the Status property to determine the particular error that has occurred and take the proper steps to resolve the error. The following table describes some of the possible values for the Status property. For a complete list of values, see the .NET Framework SDK documentation. Value
Description
ConnectFailure
The remote service could not be contacted at the transport level.
ConnectionClosed
The connection was closed prematurely.
KeepAliveFailure
The server closed a connection made with the Keep-alive header set.
NameResolutionFailure
The name service could not resolve the host name.
ProtocolError
The response received from the server was complete but indicated an error at the protocol level.
ReceiveFailure
A complete response was not received from the remote server.
RequestCanceled
The request was canceled.
SecureChannelFailure
An error occurred in a secure channel link.
SendFailure
A complete request could not be sent to the remote server.
26
Module 11: Internet Access
(continued) Value
Description
ServerProtocolViolation
The server response was not a valid HTTP response.
Success
No error was encountered.
Timeout
No response was received within the time-out set for the request.
TrustFailure
A server certificate could not be validated.
When the Status property is WebExceptionStatus.ProtocolError, a WebResponse that contains the response from the server is available. You can examine this response to determine the actual source of the protocol error. The following example shows how to catch a WebException; the invalid URL argument in the WebRequest.Create call will throw an exception: using using using using
System; System.Net; System.IO; System.Text;
class App { public static void Main(string[] args) { try { // Create a request instance // Note invalid URL will throw exception WebRequest myRequest = WebRequest.Create("http://localhost_bad_URL"); // Get the response. WebResponse myResponse = myRequest.GetResponse(); //Get a readable stream from the server. StreamReader sr = new StreamReader( myResponse.GetResponseStream(), Encoding.ASCII); int length = 1024; char [] Buffer = new char[1024]; int bytesread = 0; //Read from the stream and write data to Console. bytesread = sr.Read( Buffer, 0, length); while( bytesread > 0 ) { Console.Write( Buffer, 0, bytesread); bytesread = sr.Read( Buffer, 0, length); } sr.Close(); myResponse.Close(); }
(Code continued on the following page.)
Module 11: Internet Access
27
catch (WebException webExcp) { Console.WriteLine("A WebException has been caught"); // Write out the WebException message. Console.WriteLine(webExcp.ToString()); // Get the WebException status code. WebExceptionStatus status = webExcp.Status; // If status is WebExceptionStatus.ProtocolError, // there has been a protocol error and a // WebResponse should exist. // Display the protocol error. if (status == WebExceptionStatus.ProtocolError) { Console.Write( "The server returned protocol error "); Console.WriteLine(webExcp.Response.ToString()); } } catch (Exception e) { // Code to catch other exceptions goes here. } } }
Applications that use the Socket class throw instances of SocketException when errors occur on the Windows socket. The TcpClient, TcpListener, and UdpClient classes are built on top of the Socket class and also throw instances of SocketException. When a SocketException is thrown, the Socket class sets the ErrorCode property to the last operating system socket error that occurred.
28
Module 11: Internet Access
# Security Topic Objective
To introduce the topics in the section.
Lead-in
Your application can provide security for sending and receiving data over the Internet by using a Web proxy, SSL encryption, Internet authentication, and the NET Framework’s code access permissions.
!
Web Proxy
!
Secure Sockets Layer
!
Internet Authentication
!
Permissions
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Your application can provide security for sending and receiving data over the Internet by using a Web proxy, Secure Sockets Layer (SSL) encryption, Internet authentication, and the NET Framework’s code access permissions.
Module 11: Internet Access
29
Web Proxy Topic Objective
To show how to use a Web proxy to communicate with the Internet.
Lead-in
If your Web site uses a proxy to provide access to the Internet, you must configure a proxy instance to enable your application to communicate with the Web proxy.
!
Global Proxy for All Web Requests "
Proxy named webproxy using port 80
WebProxy WebProxy proxyObject proxyObject == new new WebProxy( WebProxy( "http://webproxy:80/"); "http://webproxy:80/"); GlobalProxySelection.Select GlobalProxySelection.Select == proxyObject; proxyObject; !
Overriding the Global Proxy Setting "
Request uses proxy named alternateproxy and port 80
WebRequest WebRequest req req == WebRequest.Create( WebRequest.Create( "http://www.contoso.com/"); "http://www.contoso.com/"); req.Proxy req.Proxy == new new WebProxy( WebProxy( "http://alternateproxy:80/"); "http://alternateproxy:80/");
*****************************ILLEGAL FOR NON-TRAINER USE****************************** If your Web site uses a proxy to provide access to the Internet, you must configure a proxy instance to enable your application to communicate with the Web proxy.
Creating a Global Proxy Instance The following example shows how to create a global proxy instance that enables any WebRequest to use a proxy to communicate with the Internet. The example assumes that the proxy server is named webproxy and that it communicates on port 80, the standard HTTP port. WebProxy proxyObject = new WebProxy("http://webproxy:80/"); GlobalProxySelection.Select = proxyObject;
Overriding the Global Proxy Selection You can override the global proxy selection by assigning an instance that implements the IWebProxy interface to the Proxy property of the WebRequest. The following code sends a WebRequest to http://www.contoso.com. The WebRequest overrides the global proxy selection with a proxy server that is named alternateproxy on port 80. WebRequest req = WebRequest.Create("http://www.contoso.com/"); req.Proxy = new WebProxy("http://alternateproxy:80/");
30
Module 11: Internet Access
Secure Sockets Layer Topic Objective
To explain how SSL is used for secure network communication.
!
SSL Is Used Automatically If the URI Begins with https
Lead-in
The WebRequest and WebResponse classes use SSL automatically.
String String MyURI MyURI == "https://www.contoso.com/"; "https://www.contoso.com/"; WebRequest WebRequest wReq wReq == WebRequest.Create(MyURI); WebRequest.Create(MyURI);
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The WebRequest and WebResponse classes use SSL automatically. The WebRequest object decides to use SSL on the basis of the URI that it is assigned. If the URI begins with https:, SSL is used. If the URI begins with http:, SSL is not used. The following example illustrates the use of SSL: String MyURI = "https://www.contoso.com/"; WebRequest wReq = WebRequest.Create(MyURI);
Module 11: Internet Access
31
Internet Authentication Topic Objective
To introduce the client authentication mechanisms that are supported by the System.Net classes.
!
Lead-in
The System.Net classes support a variety of client authentication mechanisms, including the standard Internet authentication methods: basic, digest, negotiate, NTLM, and Kerberos authentication, and custom methods that you can create.
!
.NET Supports Various Kinds of Authentication "
Basic, digest, negotiate, NTLM, and Kerberos authentication
"
Users can also create their own authentication
Credentials Stored in Classes "
NetworkCredential – for a single Internet resource
"
CredentialCache – for multiple Internet resources
!
Authentication Managed by the AuthenticationManager
!
Some Schemes Allow Pre-Authentication to Save Time
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The System.Net classes support a variety of client authentication mechanisms, including the standard Internet authentication methods: basic, digest, negotiate, NTLM, and Kerberos authentication, and custom methods that you can create.
Classes and Interfaces Used for Authentication Authentication credentials are stored in the NetworkCredential and CredentialCache classes, which implement the ICredentialLookup interface. When one of these classes is queried for credentials, it returns an instance of the NetworkCredential class. The AuthenticationManager class manages the authentication process, while an authentication module class that implements the IAuthenticationModule interface performs the actual authentication process. You must register a custom authentication module with the AuthenticationManager before it can be used. Modules for the basic, digest, negotiate, NTLM, and Kerberos authentication methods are registered by default.
The NetworkCredential Class The NetworkCredential class stores a set of credentials, which is associated with a single Internet resource and that is identified by a URI, and returns them in response to any call to the GetCredential method. The NetworkCredential class is typically used by applications that access a limited number of Internet resources or by applications that use the same set of credentials in all cases.
The CredentialCache Class The CredentialCache class stores a collection of credentials for various Internet resources. When the GetCredential method is called, CredentialCache returns the proper set of credentials, as determined by the URI of the Internet resource and the requested authentication scheme. Because the CredentialCache class stores all of the credentials and provides them as requested, applications that use a variety of Internet resources with different authentication schemes benefit from using the CredentialCache class.
32
Module 11: Internet Access
The Authentication Process When an Internet resource requests authentication, the WebRequest.GetResponse method sends the WebRequest and the request for credentials to the AuthenticationManager. The request is then authenticated according to the following procedure: 1. The AuthenticationManager calls the Authenticate method on each of the registered authentication modules in the order that they were registered. 2. The AuthenticationManager uses the first module that does not return null to carry out the authentication process. The details of the process vary depending on the type of authentication module involved. 3. When the authentication process is complete, the authentication module returns an Authorization instance to the WebRequest that contains the information that is needed to access the Internet resource. Some authentication schemes can authenticate a user without first making a request for a resource. An application can save time by pre-authenticating the user with the resource, thus eliminating at least one roundtrip to the server. Alternatively, the application can perform authentication during program startup to be more responsive to the user later. Authentication schemes that can use pre-authentication set the CanPreAuthenticate property to true.
Basic and Digest Authentication The System.Net implementation of basic and digest authentication complies with RFC2617, “HTTP Authentication: Basic and Digest Authentication,” which is available on the World Wide Web Consortium (W3C) Web site at http://www.w3c.org. To use basic and digest authentication, an application must provide a user name and password in the Credentials property of the WebRequest object that it uses to request data from the Internet, as shown in the following example: Caution String literals in an application are stored and transported as clear text. Therefore, you should avoid putting sensitive information such as passwords in string literals. // variables named username and password // of type string have been previously assigned String MyURI = "http://www.contoso.com/"; WebRequest wReq = WebRequest.Create(MyURI); wReq.Credentials = new NetworkCredential( username, password);
Module 11: Internet Access
33
NTLM and Kerberos Authentication Default NTLM authentication and Kerberos authentication use the Microsoft Windows NT® user credentials that are associated with the calling application to attempt authentication with the server to pass the username, password, and domain to the host, as in the following example: // variables named username, password, and domain // of type string have been previously assigned String MyURI = "http://www.contoso.com/"; WebRequest wReq = WebRequest.Create(MyURI); wReq.Credentials = new NetworkCredential(username, password, domain);
Applications that need to connect to Internet services by using the credentials of the application user can do so with the user’s default credentials, as in the following example: String MyURI = "http://www.contoso.com/"; WebRequest wReq = WebRequest.Create(MyURI); wReq.Credentials = CredentialCache.DefaultCredentials;
The negotiate authentication module determines whether the remote server is using NTLM or Kerberos authentication and sends the appropriate response. Note NTLM authentication does not work through a proxy server.
Passport Authentication Passport authentication is a centralized authentication service provided by Microsoft that offers a single logon and core profile services for member sites. This benefits the user because it is no longer necessary to log on to access new protected resources or sites. If you want your site to be compatible with Passport authentication and authorization, this is the provider you should use. For more information, see the Passport documentation located at http://www.passport.com/business.
34
Module 11: Internet Access
Permissions Topic Objective
To explain which permission classes best suit which application types.
Lead-in
The WebPermissions and SocketPermissions classes provide Internet security for applications that use System.Net.
!
WebPermissions "
!
SocketPermissions "
!
!
Controls an application's right to request data from a URI or to serve a URI to the Internet Controls an application's right to accept data on a local port or to contact applications
Choose Permission Class Based on Application Use "
WebRequest and its descendents use WebPermissions
"
Socket-level access uses SocketPermissions
Both Classes Support Two Kinds of Permissions "
Accept – application can answer an incoming connection
"
Connect – application can initiate a connection
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The WebPermissions and SocketPermissions classes provide Internet security for applications that use System.Net. The WebPermissions class controls an application’s right to request data from a URI or to serve a URI to the Internet. The SocketPermissions class controls an application’s right to accept data on a local port or to contact applications through a transport protocol at another address that is based on the host, port number, and transport protocol. You should choose the permission class on the basis of your application type. Applications that use WebRequest and its descendents should use the WebPermissions class to manage permissions. Applications that use socket-level access should use the SocketPermissions class to manage permissions. WebPermissions and SocketPermissions define two permissions: accept and connect. Accept grants the application the right to answer an incoming connection from another party. Connect grants the application the right to initiate a connection to another party. For WebPermissions, accept means that an application can export a particular URI anywhere on the Internet. Connect means that an application can access that URI, whether it is remote or local. For SocketPermissions, accept means that an application can accept incoming connections on a local transport address. Connect means that an application can connect to a remote, or potentially local, transport address.
Module 11: Internet Access
35
Best Practices Topic Objective
To introduce best practices that will help students use the System.Net classes more effectively.
Lead-in
The following recommendations will help you use the classes that are contained in System.Net more effectively.
!
When Possible, Use WebRequest and WebResponse, Instead of Protocol-Specific Subclasses
!
For Better Performance, Use Asynchronous Methods
!
Tune Performance by Adjusting the Number of Connections "
ConnectionLimit property in the ServicePoint instance
!
When Possible, Use TcpClient or UdpClient, Instead of Writing Directly to a Socket
!
Use the CredentialCache Class If Credentials Are Required
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The following recommendations will help you use the classes that are contained in System.Net more effectively: !
Whenever possible, use WebRequest and WebResponse, instead of typecasting to descendent classes. Applications that use WebRequest and WebResponse can take advantage of new Internet protocols without extensive code changes.
!
When writing ASP.NET applications that run on a server that uses the System.Net classes, it is often better, from a performance standpoint, to use the asynchronous methods for GetResponse and GetResponseStream.
!
Set the ConnectionLimit property in the ServicePoint instance for your application. The number of connections opened to an Internet resource can have a significant effect on network performance and throughput. By default, System.Net uses two connections per application for each host. Setting the ConnectionLimit property in the ServicePoint instance for your application can increase this number.
!
When writing socket-level protocols, try to use the TcpClient or UdpClient classes, instead of writing directly to a socket. The TcpClient and UdpClient classes encapsulate the creation of TCP and UDP sockets without requiring you to handle the details of the connection.
!
When accessing sites that require credentials, use the CredentialCache class to create a cache of credentials, rather than supplying them with every request. The CredentialCache class will search the cache to find the appropriate credential to present with a request, thus relieving you of the responsibility of creating and presenting credentials based on the URI.
36
Module 11: Internet Access
Lab 11: Creating a DateTime Client/Server Application Topic Objective
To introduce the lab.
Lead-in
In this lab, you will create a client application that uses the System.Net.Sockets .TcpClient class to connect to and obtain date and time information from a server. You will also create a server application that uses the System.Net.Sockets .TcpListener class to accept requests from and provide date and time information to clients.
*****************************ILLEGAL FOR NON-TRAINER USE******************************
Objectives After completing this lab, you will be able to: !
Create a client application that uses the System.Net.Sockets.TcpClient class to connect to and obtain date and time information from a server.
!
Create a server application that uses the System.Net.Sockets.TcpListener class to accept requests from and provide date and time information to clients.
Lab Setup Only solution files are associated with this lab. The solution files for this lab are in the folder \Labs\Lab11\Solution.
Scenario In this lab, you will create two Microsoft Visual Studio® .NET console applications: DateTimeClient and DateTimeServer. The DateTimeClient application will make a TCP connection to the DateTimeServer application and obtain a stream that contains date and time information. The DateTimeClient will read the stream and convert the stream’s ASCII data into a string that is then output to the console.
Estimated time to complete this lab: 45 minutes
Module 11: Internet Access
37
Exercise 1 Creating the DateTime Server In this exercise, you will create a server application that will provide date and time information to clients through TCP.
! Create the server application 1. In Visual Studio .NET, create a new C# console application project named DateTimeServer in \Labs\Lab11. 2. Rename the starting C# source file Datetimeserver.cs. 3. Add the following using statements: using System.Net; using System.Net.Sockets; using System.Text;
4. Rename the wizard generated Class1 to Server. 5. Modify the Main method in the following steps. 6. In the try section of a try/catch block: a. Instantiate a TcpListener object to listen on port 14. b. Start the TcpListener object. c. Write out the following message to the console: Waiting for clients to connect Press Ctrl+c to Quit...
d. Enter an infinite loop that will: i. Accept TCP client connections using the AcceptSocket method. ii. Call a DateTime method to get the current date and time. iii. Create a string that consists of the short version of the date, followed by the long version of the time. iv. Convert the string to an ASCII-encoded byte array. v. Send this byte array to the TCP client. vi. Close the socket. vii. Write out to the console a message that contains the string that was just sent. 7. In the catch section of the try/catch block: • Catch any exceptions of type SocketException and if the exception’s ErrorCode property has a value of 10048, then write to the console a message that states “Connection to this port failed. There is another server is listening on this port.” 8. Build the server application.
38
Module 11: Internet Access
Exercise 2 Creating the DateTime Client In this exercise, you will create a client application. The client application takes as its runtime argument the name of the computer on which the server is running. The client connects to the server to obtain date and time information through TCP. The client then displays the date and time information on the console.
! Create the client application 1. In Visual Studio .NET, create a new C# console application project named DateTimeClient in \Labs\Lab11. 2. Rename the starting C# source file Datetimeclient.cs. 3. Add the following using statements: using using using using
System.Net; System.Net.Sockets; System.IO; System.Text;
4. Rename the wizard generated Class1 to Client. 5. Make the static Main method capable of handling runtime-supplied arguments. 6. In the Main method, add code to: a. Create a TcpClient object. b. Create a byte array object of size 32 bytes. c. Check the number of runtime arguments. If the number of arguments is not one, print out an error message to the console, and exit. The message should state: Please specify a server name in the command line
d. In a try/catch block, verify that the named server computer exists by calling the GetHostByName method of the Dns class. Catch any SocketException exceptions and write out to a console window the message “Cannot find server: ”, followed by the name of the server and the exception’s data. Then exit the application. e. Connect to the named server using port 14. f. Declare a variable of type Stream. g. In a try/catch block, get the stream and assign it to the variable declared in the preceding step. Catch any InvalidOperationException exceptions and write out to a console window the message “Cannot connect to server ”, followed by the server’s name, and then exit the application. h. Read all of the bytes in the stream into the byte array that you created in step 6b, and store the number of bytes that are read in an integer. i. Convert the ASCII-encoded byte array into a string. j. Write out to a console window a message that states the number of bytes received and the current date and time string retrieved from the server.
Module 11: Internet Access
39
k. Close the TcpClient object. l. Write out to a console window the following message: Press Enter to exit. m. Do a read from the console to wait for the Enter key and exit the application. 7. Build the client application.
! Test the client and server applications 1. Ensure that there are no server applications running on the computer using port 14, for example, other instances of the server application. Tip You can display your computer’s current TCP/IP network connections by running the program netstat in a command prompt window. The following example shows that port 7 is in use by a process with PID 1504: C:\>netstat -o -n -a Active Connections Proto TCP
Local Address 0.0.0.0:7
Foreign Address 0.0.0.0:0
State PID LISTENING 1504 …
2. Run the server application in a Visual Studio .NET Command Prompt window or in the Visual Studio .NET debugger. 3. Run the client application in another Visual Studio .NET Command Prompt window or in another Visual Studio .NET debugger. Do not specify any runtime arguments. 4. Note that the client application returns with the proper error message, as follows: Please specify a server name in the command line
5. Run the client application as directed in step 3 with a runtime argument that contains a nonexistent server name. For example, assuming that there is no computer named foo on the network, enter the following text in a Visual Studio .NET Command Prompt window: datetimeclient foo
40
Module 11: Internet Access
6. Note that the client application returns with the proper error message, for example: Cannot find server: foo …
7. Run the client application as directed in step 3 with a runtime argument that contains the name of the computer on which the Datetimeserver application is running. If the server application is running on your current computer, specify the local computer as follows: datetimeclient localhost
Note that the client application outputs the number of bytes received and the current date and time string retrieved from the server.
Module 11: Internet Access
41
Review Topic Objective
To reinforce module objectives by reviewing key points.
Lead-in
The review questions cover some of the key concepts taught in the module.
!
Internet Application Scenarios
!
The WebRequest and WebResponse Model
!
Application Protocols
!
Handling Errors
!
Security
!
Best Practices
*****************************ILLEGAL FOR NON-TRAINER USE****************************** 1. Name the four parts of the following URI: http://www.microsoft.com/default.htm?foo=bar Scheme identifier
http
Server identifier
www.Microsoft.com
Path identifier
/default.htm
Query String
?foo=bar
2. Write the line of code that creates a WebRequest to the URI in question 1. WebRequest req = WebRequest.Create ("http://www.microsoft.com/default.htm?foo=bar");
3. Write the line of code that gets a WebResponse from the WebRequest in question 2. WebResponse resp = req.GetResponse();
4. Write the line of code that gets a Stream from the WebResponse in question 3. Stream respstrm = resp.GetResponseStream();
42
Module 11: Internet Access
5. Name the type of Web-specific exceptions that are thrown by the GetResponse method. WebException
6. State how a WebRequest can be made to use the Secure Socket Layer (SSL) protocol. URI begins with https.
7. Name at least three authentication methods that are supported by the .NET Framework. Basic Digest Negotiate NTLM Kerberos
Module 11: Internet Access
43
Course Evaluation Topic Objective
To direct students to a Web site to complete a course evaluation.
Lead-in
Between now and the end of the course, you can go to the Web site listed on this page to complete a course evaluation.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Your evaluation of this course will help Microsoft understand the quality of your learning experience. At a convenient time before the end of the course, please complete a course evaluation, which is available at http://www.metricsthatmatter.com/survey/. Microsoft will keep your evaluation strictly confidential and will use your responses to improve your future learning experience.
THIS PAGE INTENTIONALLY LEFT BLANK
Module 12: Serialization
Contents Overview
1
Serialization Scenarios
2
Serialization Attributes
4
Object Graph
5
Serialization Process
7
Serialization Example
9
Deserialization Example
10
Custom Serialization
12
Custom Serialization Example
14
Security Issues
17
Lab 12: Serialization
18
Review
27
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, place or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. 2001-2002 Microsoft Corporation. All rights reserved. Microsoft, ActiveX, BizTalk, IntelliMirror, Jscript, MSDN, MS-DOS, MSN, PowerPoint, Visual Basic, Visual C++, Visual C#, Visual Studio, Win32, Windows, Windows Media, and Window NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
Module 12: Serialization
iii
Instructor Notes Presentation: 30 Minutes Lab: 45 Minutes
After completing this module, students will be able to: • Write an application that serializes and deserializes an object graph by using either a binary or Simple Object Access Protocol (SOAP) XML format.
Materials and Preparation This section provides the materials and preparation tasks that you need to teach this module.
Required Materials To teach this module, you need the Microsoft® PowerPoint® file 2349B_12.ppt.
Preparation Tasks To prepare for this module, you should: !
Read all of the materials for this module.
!
Complete the lab.
iv
Module 12: Serialization
Module Strategy Use the following strategy to present this module: !
Serialization Scenarios Discuss briefly how serialization is used in scenarios such as persisting inmemory objects to disk and in remoting. Mention the Microsoft .NET Framework’s support for serialization and deserialization as an introduction to the rest of the module.
!
Serialization Attributes Explain how to mark a class with serialization attributes in C# by using the Serializable attribute. Also cover the NonSerialized attribute.
!
Object Graph Use the diagram on the Object Graph slide to discuss the object graph concept and the algorithm that is used to serialize or deserialize an object graph.
!
Serialization Process Introduce the classes that are used in the serialization process.
!
Serialization Example Discuss the code example on the Serialization Example slide in which default serialization is performed on a graph of objects, whose root is an ArrayList, and the serialized stream is written to a FileStream in binary format.
!
Deserialization Example Use the preceding serialization example to show how to create a clone of the graph by deserializing it.
!
Custom Serialization Discuss when to use custom serialization and the implementation details of using the ISerializable interface to perform custom serialization and deserialization.
!
Custom Serialization Example Show how to provide custom serialization for a class named ISerializableExample.
!
Security Issues Because the serialization engine handles both the public and private state of the objects that are passed to it, emphasize that streams with private data should be treated carefully, and that some form of encryption should be used for sensitive data, before that data is transmitted over the wire or persisted to disk.
Module 12: Serialization
1
Overview Topic Objective
To provide an overview of the module topics and objectives.
!
Serialization Scenarios
!
Serialization Attributes
Lead-in
!
Object Graph
!
Serialization Process
!
Serialization Example
!
Deserialization Example
!
Custom Serialization
!
Custom Serialization Example
!
Security Issues
In this module, you will learn about serialization and learn how to write an application that serializes and deserializes an object graph by using a binary or SOAP XML format.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Serialization is the process of converting a graph of objects into a linear sequence of bytes. That sequence of bytes can be sent elsewhere, for example, to a remote computer, and be deserialized, thereby making a clone in the remote memory of the original graph of objects. After completing this module, you will be able to: • Write an application that serializes and deserializes an object graph by using either a binary or Simple Object Access Protocol (SOAP) XML format.
2
Module 12: Serialization
Serialization Scenarios Topic Objective
To show how serialization is used.
Lead-in
Serialization is used in some very common scenarios, such as persisting a graph of objects to disk or to objects in another process.
!
Persistence "
!
Store and retrieve a graph of objects to and from a file
Remoting "
Pass by value arguments that are transmitted between processes
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Serialization is used in some very common scenarios, such as persisting a graph of objects to disk or to objects in another process. The Microsoft® .NET Framework provides support for serialization and deserialization.
Persistence Consider a simple single-user desktop application, such as a two-dimensional drafting package that is built by using object-oriented techniques. In such an application, a drawing is composed of different kinds of graphical objects of various types. The application represents the drawing as a graph of in-memory objects. One object represents the root of the entire picture. For example, a simple round table could be represented by a graph that consists of a root object that is an instance of a circle class. This instance of the circle class has four children that are each instances of a line class. To save the entire drawing to a disk file so the drawing can be restored after rebooting the computer, you could force each class to implement a serialize and corresponding deserialize method. However, this approach is a potentially burdensome task for the application programmer.
Serialization in the .NET Framework The .NET Framework common language runtime reduces the amount of work that is involved in serialization. At run time, the common language runtime maintains metadata that allows serialization code to discover the types and values of all fields and properties that make up any object. Using the common language runtime, an application requires only a few lines of code to serialize a object, such as the drawing described in the preceding paragraphs, and write it to a file, or to deserialize such a file into an in-memory graph of objects.
Module 12: Serialization
3
Remoting In distributed computing, objects in one process may need to communicate with objects in another process. In the .NET Framework, the term remoting is typically used to refer to the process in which an object invokes a method in another object that is not in the same application domain. If the remote method takes as one of its arguments an object that lies at the root of a graph of objects, and if all of the objects in the graph are marked as remote-by-value, you must serialize a copy of the object graph and pass the graph to the remote object. The remote object must then deserialize the argument into an in-memory graph of objects. For more information about remoting, see Module 13, “Remoting and Web Services,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET).
4
Module 12: Serialization
Serialization Attributes Topic Objective
To explain how to mark a class with serialization attributes in C#.
Lead-in
If you are writing a class, you should be aware of serialization.
!
To Mark a Class, Use Serializable Attribute
[Serializable] [Serializable] public public class class MyClass MyClass {} {} !
To Skip Specified Members, Use NonSerialized Attribute
[Serializable] [Serializable] public public class class MyClass MyClass {{ [NonSerialized] [NonSerialized] int int _cashSize; _cashSize; //... //... }} !
To Provide Custom Serialization, Implement ISerializable
*****************************ILLEGAL FOR NON-TRAINER USE****************************** If you are writing a class, you should be aware of serialization. The common language runtime’s serialization services are built with the assumption that a type is not serializable unless the type is specifically marked as serializable. In the simplest case, all you need to do is mark a class as serializable because the runtime metadata understands everything about each object’s layout in memory, and its field and property definitions. To mark a type as serializable in C#, you use the Serializable attribute, which is a reserved custom attribute. All fields in classes with this attribute are serialized, even private fields. In the following example, MyClass is marked as serializable: [Serializable] public class MyClass {}
For slightly more complex classes that have state that is invalid to serialize, the runtime provides support for marking those fields and properties as transient. For example, the following code uses the NonSerialized attribute to ensure that the _cashSize member of MyClass is not serialized: [Serializable] public class MyClass { [NonSerialized] int _cashSize; //... }
The small set of classes that need to participate in their own serialization and deserialization can provide a custom implementation of the ISerializable interface. For more information about custom serialization, see Custom Serialization in this module.
Module 12: Serialization
5
Object Graph Topic Objective
33
To define an object graph and explain its function.
Dog
Lead-in
An object graph is a set of objects that share a set of references to each other.
44
Cat
Duck
77
99
Horse
11
Mouse
22
Duck
*****************************ILLEGAL FOR NON-TRAINER USE****************************** An object graph is a set of objects with references to each other. Serialization must provide a way to represent the links between the graph’s objects in the serialized stream that is created in the serialization process.
Understanding Object Graphs Serialization of an object graph must provide a way to represent the links between the graph’s objects in the serialized stream that it creates. The value that is held in the field of the in-memory object, which links to another object, is essentially a 32-bit address. This address has meaning only in the owner address space and may change during garbage collection. Therefore, serialization must allocate a unique number to each object in the stream. The illustration in the slide shows a graph of animal objects. Each object is represented as a box with its identification number inside the box and its class name to the right of the box. You can represent the graph of objects that is shown in this illustration with a serialized stream, as in the following example: Dog, 3, ref4, ref7, ref1 || Cat, 4, ref9 || Duck, 7 || Mouse, 1, ref9, ref2 || Horse, 9, ref4 || Duck, 2
The order in which you stream out the objects does not matter, nor does it matter what numbers you assign to the objects. What does matter is that no two objects are assigned the same number. The object numbers are significant only within a serialized stream. They are simply a way to represent the graph topology and to allow you to reconstruct a copy of that graph, perhaps on another computer.
6
Module 12: Serialization
Tracking Object References An algorithm that visits objects one at a time clearly must keep track of which objects it has already visited, for example, by using an internal list. Without due care, the algorithm may incorrectly serialize or deserialize an object graph. For example, in the object graph in the illustration, to avoid entering an infinite loop, you must detect the cycle in the graph that occurs because of the mutual references between Cat 4 and Horse 9. During serialization, you must note that the Cat 4 that is linked to by Dog 3 is the same Cat 4 that is linked to by Horse 9 to ensure that deserialization will result in both Dog 3 and Horse 9 referring to the same Cat 4 object and not to two different copies.
Module 12: Serialization
7
Serialization Process Topic Objective
!
To introduce the classes that are used in the serialization process.
Lead-in
The process of serializing an object graph involves identifying the individual objects in the graph and the relationships between them.
!
Classes Used by the Default Serialization Process "
ObjectIDGenerator – generates IDs for objects
"
ObjectManager – tracks objects as they are being deserialized
Examples of Classes Used with Serialized Streams "
!
FileStream, MemoryStream, NetworkStream
Formatter Class "
Writes or reads data in a specified format to the output or input streams
"
Runtime provides BinaryFormatter and SoapFormatter
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The process of serializing an object graph involves identifying the individual objects in the graph and the relationships between them.
Classes Used by the Default Serialization Process To create and track object ID numbers, serialization uses several classes, as follows: !
ObjectIDGenerator The ObjectIDGenerator class generates IDs for objects. It keeps track of objects that have already been seen, so when you ask for the ID of an object, the ObjectIDGenerator knows whether to return the existing ID, or to generate, and remember, a new ID.
!
ObjectManager The ObjectManager class keeps track of objects as they are being deserialized. During deserialization, queries are made to the ObjectManager to determine whether a reference to an object that is in the serialized stream refers to an object that has already been deserialized or to an object that has not yet been deserialized. A reference to an object that has already been deserialized is called a backward reference. A reference to an object that has not yet been serialized is called a forward reference.
8
Module 12: Serialization
Both the ObjectIDGenerator and ObjectManager classes are pluggable, so you can build your own alternatives. Note During deserialization, fields are returned in the order in which they are returned from reflection. Reflection does not guarantee that it will follow metadata ordering. You can serialize to many different kinds of streams, for example, to a FileStream, a MemoryStream, or a NetStream. The serialized stream’s format is determined by the formatter object that you instantiate. The .NET Framework runtime provides the following formatter classes: !
BinaryFormatter The BinaryFormatter class is used for a compact binary representation.
!
SoapFormatter The SoapFormatter class is used for an XML representation.
Because formatters are pluggable, you can also build your own formatters.
Module 12: Serialization
9
Serialization Example Topic Objective
class class SerializeExample{ SerializeExample{
To provide an example of serialization of an object graph.
Lead-in
This code sample shows how to perform default serialization of a graph of objects, whose root is an ArrayList.
}}
public public static static void void Main(String[] Main(String[] args) args) {{ // // create create the the object object graph graph ArrayList ArrayList ll == new new ArrayList(); ArrayList(); for (int x=0; x< for (int x=0; x< 100; 100; x++) x++) {{ l.Add (x); l.Add (x); }} // // create create the the filestream filestream FileStream FileStream ss == File.Create("foo.bin"); File.Create("foo.bin"); // // create create the the BinaryFormatter BinaryFormatter BinaryFormatter BinaryFormatter bb == new new BinaryFormatter(); BinaryFormatter(); // serialize the graph // serialize the graph to to the the stream stream b.Serialize(s, l); b.Serialize(s, l); s.Close(); s.Close(); }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The following code sample shows how to perform default serialization of a graph of objects, whose root is an ArrayList. The serialized stream is written to a FileStream in binary format. using using using using using
System; System.IO; System.Collections; System.Runtime.Serialization; System.Runtime.Serialization.Formatters.Binary;
class SerializeExample { public static void Main(String[] args) { // create the object graph ArrayList l = new ArrayList(); for (int x=0; x< 100; x++) { l.Add (x); } // create the filestream FileStream s = File.Create("foo.bin"); // create the BinaryFormatter BinaryFormatter b = new BinaryFormatter(); // serialize the graph to the stream b.Serialize(s, l); s.Close(); } }
10
Module 12: Serialization
Deserialization Example Topic Objective
To show how to create a clone of the graph by deserializing it.
Lead-in
The preceding Serialization Example shows how to perform default serialization of a graph of objects, whose root is an ArrayList, with a serialized stream that is written to a FileStream in binary format.
class class DeSerialize DeSerialize {{ public public static static void void Main(String[] Main(String[] args) args) {{ // // open open the the filestream filestream FileStream FileStream ss == File.OpenRead("foo.bin"); File.OpenRead("foo.bin");
}}
// // create create the the formatter formatter BinaryFormatter BinaryFormatter bb == new new BinaryFormatter(); BinaryFormatter(); // // deserialize deserialize ArrayList ArrayList pp == (ArrayList) (ArrayList) b.Deserialize(s); b.Deserialize(s); s.Close(); s.Close(); // // print print out out the the new new object object graph graph // see module text for // see module text for PrintValues’ PrintValues’ code code PrintValues(p); PrintValues(p);
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The preceding Serialization Example shows how to perform default serialization of a graph of objects, whose root is an ArrayList, with a serialized stream that is written to a FileStream in binary format. The following code sample shows how to create a clone of the graph by deserializing it. The root of the clone graph is called p. using using using using using
System; System.IO; System.Collections; System.Runtime.Serialization; System.Runtime.Serialization.Formatters.Binary;
class DeSerialize { public static void Main(String[] args) { // open the filestream FileStream s = File.OpenRead("foo.bin"); // create the formatter BinaryFormatter b = new BinaryFormatter(); // deserialize ArrayList p = (ArrayList) b.Deserialize(s); s.Close(); // print out the new object graph PrintValues(p); }
(Code continued the following page.)
Module 12: Serialization public static void PrintValues( IEnumerable myList ) { System.Collections.IEnumerator myEnumerator = myList.GetEnumerator(); while ( myEnumerator.MoveNext() ) Console.WriteLine( "{0}", myEnumerator.Current ); } }
11
12
Module 12: Serialization
Custom Serialization Topic Objective
To explain what is required to implement ISerializable for performing custom serialization.
Lead-in
This module has so far discussed the default serialization process. However, you may also want to customize the way data from a given object is serialized.
!
!
Customize Serialization by Implementing ISerializable: "
When some of the data is not valid after deserialization
"
When some of the data must be obtained by calculation
ISerializable Requires: "
GetObjectData method, called during serialization, which returns a PropertyBag of type SerializationInfo
"
PropertyBag, which contains the type of the object being serialized and the name/object pairs for the values being serialized
"
A constructor, called during deserialization, which uses SerializationInfo to reconstitute the state of the object
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This module has so far discussed the default serialization process. However, you may also want to customize the way that data from a given object is serialized. Custom serialization can be useful when some of the data that is associated with an object is no longer valid after deserialization. You may want to use custom serialization when working with pointers or hashcodes, or when you want to create data through calculations or other means that allow you to reconstruct the full state of the object during deserialization. To perform custom serialization, you should implement the ISerializable interface on the given object.
Implementation Details Required by ISerializable To implement the ISerializable interface, you implement the GetObjectData method on your object and add a constructor that takes a SerializationInfo and a StreamingContext, as shown in Custom Serialization Example in this module. When GetObjectData is called during serialization, you are responsible for populating a SerializationInfo object. A SerializationInfo object is a PropertyBag that contains the type of the object that is being serialized and the name/object pairs for the values that are being serialized. The Formatter emits the data out onto the wire in the method required by its particular format. You are free to serialize as few or as many fields as you want, but the data that is transmitted must be sufficient to reconstitute the entire state of the object. If the base object of the current class implements ISerializable, it is usually correct to call the base object’s ISerializable.GetObjectData and add any additional fields that are required for serializing the derived class to the returned SerializationInfo.
Module 12: Serialization
13
Deserialization Deserialization occurs during the call to the class’s constructor. If you need to create custom deserialization of an object, you use the object’s SerializationInfo, which has been populated with the type of the object and the name/object pairs that were transmitted over the stream. You are responsible for completely reconstituting the state of the object from this information. If the base class also implements ISerializable, you are responsible for calling the base class’s constructor. The serialization infrastructure will delay the call on this constructor until the entire SerializationInfo has been completed. If, for example, the SerializationInfo that is transmitted references objects A, B, and C, the SerializationInfo that is passed to the constructor will have been populated with references to objects A, B, and C. However, there is no guarantee that any of the objects that are referenced by A, B, or C has been completed. Because there is no guarantee that any of the objects that are referenced by A, B, or C have been completed, you cannot safely call any code on A, B, or C that may require the objects to which they refer. For some objects, this code may include code as simple as GetHashCode. If your code requires you to perform any execution that is based on the value of data that is contained in the objects that are referenced, it is usually best to cache the SerializationInfo and then implement IDeserializationCallback.
14
Module 12: Serialization
Custom Serialization Example [Serializable] [Serializable] public public class class ExampleFoo ExampleFoo :: ISerializable ISerializable {{ public public int int i, i, j, j, k; k; public public ExampleFoo() ExampleFoo() {} {} internal internal ExampleFoo(SerializationInfo ExampleFoo(SerializationInfo si, si, StreamingContext StreamingContext context) context) {{ //Restore //Restore our our scalar scalar values. values. ii == si.GetInt32("i"); si.GetInt32("i"); jj == si.GetInt32("j"); si.GetInt32("j"); kk == si.GetInt32("k"); si.GetInt32("k"); }} public public void void GetObjectData(SerializationInfo GetObjectData(SerializationInfo si, si, StreamingContext StreamingContext context) context) {{ //SerializationInfo //SerializationInfo -- essentially essentially aa property property bag bag //Add //Add our our three three scalar scalar values; values; si.AddValue("i", si.AddValue("i", i); i); si.AddValue("j", si.AddValue("j", j); j); si.AddValue("k", k); si.AddValue("k", k); Type Type tt == this.GetType(); this.GetType(); si.AddValue("TypeObj", si.AddValue("TypeObj", t); t); }} }}
Topic Objective
To provide an example of custom serialization.
Lead-in
This example shows how to provide custom serialization for a class named ISerializableExample.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The following example shows how to provide custom serialization for a class: using using using using using
System; System.IO; System.Runtime.Serialization; System.Runtime.Serialization.Formatters; System.Runtime.Serialization.Formatters.Soap;
public class Sample { public static void Main() { ToFile(); FromFile(); }
(Code continued the following page.)
Module 12: Serialization
15
public static void ToFile() { Console.WriteLine("ToFile"); ExampleFoo fooOut = new ExampleFoo(); fooOut.i = 1; fooOut.j = 20; fooOut.k = 50; Console.WriteLine("i: {0}", fooOut.i); Console.WriteLine("j: {0}", fooOut.j); Console.WriteLine("k: {0}", fooOut.k); IFormatter objFormatterToStream = new SoapFormatter(); Stream toStream = new FileStream("myFoo.xml", FileMode.Create, FileAccess.Write, FileShare.None); objFormatterToStream.Serialize(toStream, fooOut); toStream.Close(); } public static void FromFile() { Console.WriteLine("FromFile"); //Then you can read it back in with code like this: IFormatter objFormatterFromStream = new SoapFormatter(); Stream fromStream = new FileStream("myFoo.xml", FileMode.Open, FileAccess.Read, FileShare.Read); ExampleFoo fooIn = (ExampleFoo) objFormatterFromStream.Deserialize(fromStream); fromStream.Close(); Console.WriteLine("i: {0}", fooIn.i); Console.WriteLine("j: {0}", fooIn.j); Console.WriteLine("k: {0}", fooIn.k); } }
[Serializable] public class ExampleFoo : ISerializable { public int i, j, k; public ExampleFoo() { } internal ExampleFoo(SerializationInfo si, StreamingContext context) { //Restore our scalar values. i = si.GetInt32("i"); j = si.GetInt32("j"); k = si.GetInt32("k"); }
(Code continued on the following page.)
16
Module 12: Serialization public void GetObjectData(SerializationInfo si, StreamingContext context) { //SerializationInfo is essentially a property bag. //Add our three scalar values; si.AddValue("i", i); si.AddValue("j", j); si.AddValue("k", k); Type t = this.GetType(); si.AddValue("TypeObj", t); } }
Outputs: ToFile i: 1 j: 20 k: 50 FromFile i: 1 j: 20 k: 50
Module 12: Serialization
17
Security Issues Topic Objective
To alert students to the need for security when serializing objects.
Lead-in
The serialization engine handles both the public and private state of the objects that are passed to it.
!
Serialization Handles an Object’s Private Data "
If private data is sensitive, consider encrypting the stream before transmitting or saving to a disk
"
System.Security.Cryptography namespace contains classes to perform cryptography The CryptoStream class can be used to encrypt streams of serialized data
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The serialization engine handles both the public and private state of the objects that are passed to it. When serializing an object to a stream, you must remember that the stream now contains the public and private data of the object. If the private data is sensitive, you should treat the stream with particular care. For example, the stream should not be transmitted over the wire or persisted to disk without some form of encryption.
18
Module 12: Serialization
Lab 12: Serialization Topic Objective
To introduce the lab.
Lead-in
In this lab, you will write a client/server application that uses serialization.
*****************************ILLEGAL FOR NON-TRAINER USE******************************
Objectives After completing this lab, you will be able to: • Create an application that uses serialization as it is implemented by the .NET Framework, to persist an object graph to and from a disk file in both binary and SOAP XML format.
Lab Setup Starter and solution files are associated with this lab. The starter files are in the folder \Labs\Lab12\Starter, and the solution files are in the folder \Labs\Lab12\Solution.
Scenario In this lab, you will create a Microsoft Visual Studio® .NET console application that uses the common language runtime’s ability to serialize an object graph in memory to disk. You will create binary and SOAP formatter implementations of the application. In the first exercise, you will create a singly-linked linked list, which you will fill with values and serialize to a file on disk. You will then deserialize the list from the file on disk, thus restoring the list to an object graph in memory. During deserialization, the elements within the list are swapped multiple times. In the second exercise, you will modify the application to demonstrate the ability of the .NET Framework’s serialization mechanism to handle object graphs that contain multiple references to the same object and that contain objects that have mutual references, which can create cycles in the graph.
Estimated time to complete this lab: 45 minutes
Module 12: Serialization
19
Exercise 1 Creating the Basic Serialization Application In this exercise, you will modify the Serialization application to provide methods to serialize and deserialize a linked list.
! To create the basic Serialization application in binary format 1. In the \Labs\Lab12\Starter directory, open the Serialization project in Visual Studio .NET and examine the Serialize.cs and LinkedList.cs source files. 2. In Serialize.cs, locate the SaveListToDisk method, and add code to: a. Create a Stream object that is initialized to a file named Linkedlist.bin by using the static File.Create method. b. Create a new BinaryFormatter object. c. Invoke the method of the BinaryFormatter that serializes the LinkedList parameter to the stream. d. Close the file. 3. In Serialize.cs, locate the LoadListFromDisk method and add code to: a. Create a Stream object that is initialized to a file named Linkedlist.bin by using the static File.OpenRead method. b. Create a new BinaryFormatter object. c. Invoke the method of the BinaryFormatter that deserializes the stream into a LinkedList named list2. d. Close the file. e. Output the contents of list2 to console by calling the LinkedList method Draw(). f. Return list2. 4. Build the Serialization application.
20
Module 12: Serialization
5. Step through the application in the Visual Studio .NET debugger, and note that due to the random swapping your output may vary slightly from the following: Entering Scope 1 Creating and filling List .. List: 1 2 3 4 5 6 7 8 9 Serializing LinkedList to file .. Leaving Scope 1 Entering Scope 2 Deserializing LinkedList from binary file .. List: 1 2 3 4 5 6 7 8 9 Swapping Entries Swapping 1 and 2 Swapping 3 and 4 Swapping 5 and 6 Swapping 7 and 8 List: 2 1 4 3 6 5 8 7 9 Serializing LinkedList to file .. Leaving Scope 2 Entering Scope 3 Deserializing LinkedList from binary file .. List: 2 1 4 3 6 5 8 7 9 Swapping Random Entries Swapping 2 and 8 Swapping 4 and 3 Swapping 4 and 3 Swapping 6 and 5 Swapping 1 and 4 Swapping 1 and 8 Swapping 2 and 4 Swapping 3 and 5 Swapping 7 and 2 Swapping 5 and 4 Swapping 6 and 2 Swapping 9 and 5 Swapping 1 and 7 Swapping 7 and 8 Swapping 8 and 2 List: 2 1 7 4 3 8 9 6 5 Serializing LinkedList to file .. Leaving Scope 3 Entering Scope 4 Deserializing LinkedList from binary file .. List: 2 1 7 4 3 8 9 6 5 Removing Entries Removing 1 Removing 2 Removing 3 List: 7 4 8 9 6 5 Serializing LinkedList to file .. Leaving Scope 4
Module 12: Serialization
21
Note You can open a binary file using Visual Studio .NET by running Microsoft Windows® Explorer, Explorer.exe, and navigating to the binary file’s icon. Then either right-click on the icon and choose Open With Microsoft Visual Studio .NET, or drag and drop the icon into a running Visual Studio .NET application’s left-hand File View pane. 6. Using Visual Studio .NET, open and visually examine the contents of the Linkedlist.bin file in the bin\Debug subdirectory, and note the serialized list data’s format, structure, and size.
! To create the basic Serialization application in SOAP format 1. Add code to the SaveListToDisk and LoadListFromDisk methods to use the SoapFormatter to write to and read the LinkedList parameter to and from a file named Linkedlist.soap. 2. Build and execute the application, and confirm that the console output remains correct. 3. Using Visual Studio .NET, open and visually examine the contents of the Linkedlist.soap file in the bin\Debug subdirectory, and note the serialized list data’s format and size. Compare this list data’s format, structure, and size to the Linkedlist.bin list data’s format, structure, and size.
22
Module 12: Serialization
Exercise 2 Handling Complex Object Graphs In this exercise, you will modify the Serialization application to create and manipulate an object graph that has multiple references to the same object and has reference cycles.
! To create the complex Serialization application in SOAP format 1. In Serialize.cs, locate the Ser class and add a static method named SaveArrayToDisk that returns a void and that takes as its single argument an array of type Node. Within SaveArrayToDisk, add code to: a. Iterate through the array of nodes, and, for each node, print out the array’s index, the node’s value, and the value of the Node object in the node’s NextNode field. b. Write out a message to the console that states that the application is serializing the array to a file. c. Create a stream object that is initialized with the file named Array.soap by using the static File.Create method. d. Create a SoapFormatter object. e. Serialize the node array to the file. f. Close the file. 2. In the Ser class, add a static method named LoadArrayFromDisk that takes no arguments and returns an array of type Node. Within LoadArrayFromDisk, add code to: a. Write out a message to the console that states that the application is deserializing an array from the file. b. Create a stream object that is initialized with the file named Array.soap by using the static File.OpenRead method. c. Create a SoapFormatter object. d. Using the SoapFormatter object, deserialize the stream into an array of type Node. e. Close the file. f. Iterate through the array of nodes, and, for each node, print out the array’s index, the node’s value, and the value of the Node object in the node’s NextNode field. g. Return the array of type Node.
Module 12: Serialization
23
3. In the Ser class add a static public method named Scope5 that takes no arguments and returns void. Within Scope5, add code to: a. Create two objects of type Node named n0 and n1 that are initialized to values 0 and 1 respectively. b. Write out a message to the console that states that the application is entering Scope5 and creating a graph cycle. c. Assign n1 to the NextNode field of n0. d. Assign n0 to the NextNode field of n1. e. Create an array of type Node that is initialized to contain references to the three objects: n0, n1, and n0. Note that index 0 and 2 refer to the same object. f. Call the SaveArrayToDisk method and pass in the array of type Node. g. Write out a message to the console that states that the application is leaving Scope5. 4. In the Ser class, add a static public method named Scope6 that takes no arguments and returns void. Within Scope6, add code to: a. Write out a message to the console that states that the application is entering Scope6 and creating a graph cycle. b. Assign to a variable named nodes of type array of Node the array that is returned by the LoadArrayFromDisk method. The array references the objects: n0, n1, and n0 that were created in step 1. Note that index 0 and 2 refer to the same object. c. Write out a message to the console that states that the value of n0 is changing to 42 and that this change should result in a change in the value in both locations in the array. d. Assign the integer 42 to the Value field of nodes[0]. e. Write out a message to the console that states that the array’s structure should be preserved during serialization and deserialization. f. Call SaveArrayToDisk to persist nodes to the disk. g. Assign to a variable named nodes2 of type array of Node the array that is returned by the LoadArrayFromDisk method. h. Write out a message to the console that states that the value of n0 is being incremented. i. Increment by 1 the value of the first element of nodes2. j. Call SaveArrayToDisk, and pass nodes2. k. Write out a message to the console that states that the application is leaving Scope6. 5. In the Main method of Ser, and after the call to the Scope4 method, add calls to Scope5 and Scope6. 6. Build the Serialization application. 7. Step through the application in the Visual Studio .NET debugger, and note console output similar to the following output:
24
Module 12: Serialization Entering Scope 1 Creating and filling List .. List: 1 2 3 4 5 6 7 8 9 Serializing LinkedList to file .. Leaving Scope 1 Entering Scope 2 Deserializing LinkedList from soap file .. Deserializing LinkedList from binary file .. List: 1 2 3 4 5 6 7 8 9 Swapping Entries Swapping 1 and 2 Swapping 3 and 4 Swapping 5 and 6 Swapping 7 and 8 List: 2 1 4 3 6 5 8 7 9 Serializing LinkedList to file .. Leaving Scope 2 Entering Scope 3 Deserializing LinkedList from soap file .. Deserializing LinkedList from binary file .. List: 2 1 4 3 6 5 8 7 9 Swapping Random Entries Swapping 8 and 7 Swapping 7 and 5 Swapping 6 and 5 Swapping 2 and 1 Swapping 1 and 8 Swapping 4 and 2 Swapping 2 and 3 Swapping 3 and 5 Swapping 9 and 4 Swapping 9 and 4 Swapping 6 and 8 Swapping 2 and 4 Swapping 2 and 1 Swapping 9 and 7 Swapping 1 and 5 List: 6 5 1 4 3 9 8 2 7 Serializing LinkedList to file .. Leaving Scope 3 Entering Scope 4 Deserializing LinkedList from soap file .. Deserializing LinkedList from binary file .. List: 6 5 1 4 3 9 8 2 7 Removing Entries Removing 1 Removing 2 Removing 3 List: 6 5 4 9 8 7 Serializing LinkedList to file .. Leaving Scope 4
(Code continued on the following page.)
Module 12: Serialization
25
Entering Scope 5 Creating a circular reference: n0's NextNode is n1 and n1's NextNode is n0 Also adding in a third node, n2, that is another reference to n0 Node 0 Value: 0 Value of NextNode Ref: 1 Node 1 Value: 1 Value of NextNode Ref: 0 Node 2 Value: 0 Value of NextNode Ref: 1 Serializing Array to file .. Leaving Scope 5 Entering Scope 6 Deserializing Array from file .. Node 0 Value: 0 Value of NextNode Ref: 1 Node 1 Value: 1 Value of NextNode Ref: 0 Node 2 Value: 0 Value of NextNode Ref: 1 changing value of node n0 to 42 should change value in both locations in array array's structure should be preserved during serialization and deserialization
Node 0 Value: 42 Value of NextNode Ref: 1 Node 1 Value: 1 Value of NextNode Ref: 42 Node 2 Value: 42 Value of NextNode Ref: 1 Serializing Array to file .. Deserializing Array from file .. Node 0 Value: 42 Value of NextNode Ref: 1 Node 1 Value: 1 Value of NextNode Ref: 42 Node 2 Value: 42 Value of NextNode Ref: 1 incrementing value of node n0 Node 0 Value: 43 Value of NextNode Ref: 1 Node 1 Value: 1 Value of NextNode Ref: 43 Node 2 Value: 43 Value of NextNode Ref: 1 Serializing Array to file .. Leaving Scope 6
26
Module 12: Serialization
8. Using Visual Studio .NET, open and visually examine the Array.soap file in the bin\Debug subdirectory, and note the serialized array data’s format, structure, and size.
Module 12: Serialization
27
Review Topic Objective
To reinforce module objectives by reviewing key points.
Lead-in
The review questions cover some of the key concepts taught in the module.
!
Serialization Scenarios
!
Serialization Attributes
!
Object Graph
!
Serialization Process
!
Serialization Example
!
Deserialization Example
!
Custom Serialization
!
Custom Serialization Example
!
Security Issues
*****************************ILLEGAL FOR NON-TRAINER USE****************************** 1. Declare a serializable class named Foo with two integer fields F1 and F2 in which F2 is transient and should not be serialized. [Serializable] public class Foo { int F1; [NonSerialized] int F2;
2. Name and describe the two kinds of formatters that the .NET Framework provides. BinaryFormatter for a compact binary SoapFormatter for XML representation
3. Describe what a class should do to provide custom serialization. The class should inherit from the ISerializable interface, implement the interface’s GetObjectData method, and provide a constructor that takes SerializationInfo and StreamingContext parameters.
4. What kind of data that is not normally accessible by clients can be made visible by serialization? Private object state
THIS PAGE INTENTIONALLY LEFT BLANK
Module 13: Remoting and XML Web Services Contents Overview
1
Remoting
2
Remoting Configuration Files
19
Demonstration: Remoting
22
Lab 13.1: Building an Order-Processing Application by Using Remoted Servers
28
XML Web Services
36
Lab 13.2: Using an XML Web Service
48
Review
54
Course Evaluation
56
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, place or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. 2001-2002 Microsoft Corporation. All rights reserved. Microsoft, ActiveX, BizTalk, IntelliMirror, Jscript, MSDN, MS-DOS, MSN, PowerPoint, Visual Basic, Visual C++, Visual C#, Visual Studio, Win32, Windows, Windows Media, and Window NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
Module 13: Remoting and XML Web Services
Instructor Notes Presentation: 120 Minutes Lab: 105 Minutes
After completing this module, students will be able to: !
Write and configure distributed applications that use .NET Remoting.
!
Create an XML Web service by using Microsoft® Visual Studio® .NET and ASP.NET.
!
Consume an XML Web service by using the Web Services Description Language tool (Wsdl.exe).
Materials and Preparation This section provides the materials and preparation tasks that you need to teach this module.
Required Materials To teach this module, you need the Microsoft PowerPoint® file 2349B_13.ppt.
Preparation Tasks To prepare for this module, you should: !
Read all of the materials for this module.
!
Practice the demonstrations.
!
Complete the lab.
iii
iv
Module 13: Remoting and XML Web Services
Demonstrations This section provides demonstration procedures that will not fit in the margin notes or are not appropriate for the student notes.
Remoting In this demonstration, you will show students how a client application uses .NET Remoting to make a method call on an object in a server application. The code for this demonstration is contained in one project and is located in \Democode\Mod13\Demo13.1. In addition, the code for the individual demonstration is provided in the student notes.
Using Visual Studio .NET to Create an XML Web Service In this demonstration, you will show students how to use Visual Studio .NET to create an XML Web service. The code for this demonstration is contained in one project and is located in \Democode\Mod13\Demo13.2. In addition, the code for the individual demonstration is provided in the student notes. In both of the preceding demonstrations, use the debugger to step through the code while you point out features.
Module 13: Remoting and XML Web Services
Module Strategy Use the following strategy to present this module: !
Remoting Use the diagram on the Remoting Overview slide to introduce the services that are provided by the Microsoft .NET Framework for use with remoting. You will cover each of these services in more detail in the subsequent slides in this section. Explain how channels and formatters are used to transmit data. Discuss how the .NET Framework supports server-side and client-side activation of remote objects and describe the differences between server-side and clientside activation. Explain how to control the lifetime of client-activated remote objects by using a leasing mechanism. Discuss how objects are marshaled in .NET Remoting. Explain how to register and activate a remote object from the server side and the client side. Conclude this section with a brief discussion of client compilation techniques.
!
Remote Configuration Files Discuss the use of configuration files in remoting. Do not spend time on the .NET Remoting configuration file format; instead refer students to the .NET Framework Software Developer’s Guide (SDK) documentation. Because the module is long, conclude this part of the lecture and instruct students to do Lab 13.1.
!
XML Web Services Explain how to use Visual Studio .NET to implement an ASP.NET XML Web service and how to access the XML Web service from a Web browser and a client application. Use the Using Visual Studio .NET to Create an XML Web Service demonstration to illustrate the concepts that are covered in this section. Introduce the XML Web service discovery process and the tools that are available for discovery.
v
Module 13: Remoting and XML Web Services
1
Overview Topic Objective
To provide an overview of the module topics and objectives.
Lead-in
In this module, you will learn about distributed applications that use .NET Remoting.
!
Remoting
!
Remoting Configuration Files
!
XML Web Services
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Microsoft® .NET Remoting provides a framework that allows objects to interact across remoting boundaries, for example, across application domains (AppDomain). XML Web services is a term for communication that uses industry-standard HTTP and Simple Object Access Protocol (SOAP) protocols. You can implement XML Web services by using remoting and by using ASP.NET. After completing this module, you will be able to: !
Write and configure distributed applications that use .NET Remoting.
!
Create an XML Web service by using Microsoft Visual Studio® .NET and ASP.NET.
!
Consume an XML Web service by using the Web Services Description Language tool (Wsdl.exe).
2
Module 13: Remoting and XML Web Services
" Remoting Topic Objective
To provide an overview of the topics covered in this section.
Lead-in
This section shows how .NET Remoting supports communication in various scenarios.
!
Remoting Overview
!
Channels and Formatters
!
Activation and Proxies
!
Lease-Based Lifetime
!
Object Marshaling
!
Server Side
!
Client Side
!
Client Compilation Techniques
*****************************ILLEGAL FOR NON-TRAINER USE****************************** .NET Remoting supports communication in the following scenarios: !
Between objects in different application domains
!
In different processes
!
On different computers
The common language runtime remoting infrastructure provides a rich set of classes that enable you to ignore most of the complexities of deploying and managing remote objects. Even with applications that run under different runtime environments, the process of calling methods on remote objects is almost identical to the process of calling local methods.
Module 13: Remoting and XML Web Services
3
Remoting Overview Topic Objective
To provide an overview of .NET Remoting.
Lead-in
Client AppDomain
The .NET Framework provides several services that are used in remoting.
Client Object Server Proxy
Formatter
Channel
Server AppDomain Formatter
Channel
Server Object
Remoting Boundary *****************************ILLEGAL FOR NON-TRAINER USE****************************** The .NET Framework provides several services that are used in remoting: !
Communication channels that are responsible for transporting messages to and from remote applications
!
Formatters that encode and decode messages before they are transported by the channel
!
Proxies that forward remote method calls to the proper object
!
Remote object activation and lifetime support
Note Because the .NET context mechanism is beyond the scope of this course, this module does not cover remoting calls between contexts.
4
Module 13: Remoting and XML Web Services
Channels and Formatters Topic Objective
To explain how channels and formatters are used to transmit data.
!
Channels Transport Messages To and From Remote Objects
!
Client Selects a Channel That Is Registered on the Server #
Lead-in
#
Channels are used to transport messages to and from remote objects. Formatters are used to encode and serialize data into messages before transmission over a channel.
!
Before calling a remote object, the client registers the channel Channels are registered on a per application domain basis One computer cannot have multiple channels listening to same port
.NET Provides Implementation of HTTP and TCP Channels #
HTTP Channel default: SOAP protocol to transport XML messages
#
TCP Channel default:TCP protocol to transport binary messages Faster than HTTP SOAP Web Services but not as open
!
Example: Programmatic Registration of TCP Channel on Port 8085
using using System.Runtime.Remoting.Channels; System.Runtime.Remoting.Channels; using using System.Runtime.Remoting.Channels.Tcp; System.Runtime.Remoting.Channels.Tcp; TcpChannel TcpChannel chan chan == new new TcpChannel(8085); TcpChannel(8085); ChannelServices.RegisterChannel(chan); ChannelServices.RegisterChannel(chan);
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Channels are used to transport messages to and from remote objects. When a client calls a method on a remote object, the parameters, as well as other details that are related to the call, are transported through the channel to the remote object. Any results from the call are returned back to the client in the same way. Formatters are used to encode and serialize data into messages before they are transmitted over a channel.
Channel Selection Because a client can use any of the channels that are registered on the server to communicate with a remote object, you can select the channels that best suit your needs. You can also customize any existing channel or build new ones that use different communication protocols. Channel selection is subject to the following rules: !
Channels must be registered before objects are registered. At least one channel must be registered with the remoting infrastructure before a remote object can be called.
!
Channels are registered on a per application domain basis. A single process may contain multiple application domains. When a process dies, all channels that it registers are automatically destroyed.
!
It is not valid to register a channel that listens on the same port on which another channel is currently listening. Though channels are registered on a per application domain basis, different application domains on one computer cannot register the same channel that listens on the same port.
Module 13: Remoting and XML Web Services
5
Clients can communicate with a remote object by using any registered channel. The remoting framework ensures that the remote object is connected to the proper channel when a client attempts to connect to it. The client is responsible for specifying a channel before it attempts to communicate with a remote object. To specify a channel, you can use a .NET Remoting configuration file, or you can call the RegisterChannel method on the ChannelServices class. The .NET Framework provides support for HTTP, TCP, and SMTP channels. Because .NET Remoting channels are pluggable, you can write and plug in additional channels with unique transport and encoding requirements.
Formatters Used to Encode and Decode Messages Each channel provides a default formatter. However you can specify the formatter that you wish to use. Note A Uniform Resource Identifier (URI) is a compact representation of a resource that is available to your application through the Internet. You may be more familiar with the term URL, which stands for Uniform Resource Locator. URLs form a subset of the more general URI naming scheme. A URL identifies an Internet resource that has a Web page address.
HTTP Channel By default, the HTTP channel uses the SOAP protocol to transport messages to and from remote objects. All messages are passed through the SOAP formatter, where the message is changed into XML and serialized, and the required SOAP headers are added to the stream. Alternatively, you can specify the binary formatter, which results in a binary data stream. In either case, the data stream is then transported to the target Uniform Resource Identifier by using the HTTP protocol. You can create industry-standard XML Web services by using the HTTP channel with the default SOAP formatter.
TCP Channel By default, the TCP channel uses a binary formatter to serialize all messages to a binary stream and transports the stream to the target Uniform Resource Identifier by using the TCP protocol. Alternatively, you can specify the SOAP formatter, which results in an XML data stream. You can obtain better performance by using the TCP channel with the default binary formatter than you can by using XML Web services.
6
Module 13: Remoting and XML Web Services
Code Example The following code example shows how to programmatically register a TCP Channel on port 8085 by using ChannelServices.RegisterChannel: using System; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; //... TcpChannel chan = new TcpChannel(8085); ChannelServices.RegisterChannel(chan); //...
Security Issues If you have a choice between using the HttpChannel and the TcpChannel, it is recommended that you use the HttpChannel and host your remote objects in Internet Information Services (IIS), no matter what the user authentication and authorization models are. IIS hosting provides support for wire-level protection using Secure Sockets Layer (SSL) and authentication using Integrated Windows Authentication (formerly NTLM authentication) or Kerberos. For configuring SSL and authentication, see the IIS documentation. The TcpChannel, as an implementation of the Transmission Control Protocol (TCP), does not have default support for some of the robust authentication standards that the HTTP standard does. Within a secured environment (one that has wire-level protection such as IPSec), the high-speed TcpChannel can be used, but it is not recommended over the Internet or a nonsecure intranet. You can use cryptography to protect data from being viewed or modified and thereby provide secure channels of communication over otherwise insecure channels. The System.Security.Cryptography namespace contains a set of classes that allow you to perform both symmetric and asymmetric cryptography, create hashes, and provide random number generation. Successful cryptography is the result of combining these tasks. See the .NET SDK for documentation about the System.Security.Cryptography namespace.
Module 13: Remoting and XML Web Services
7
Activation and Proxies Topic Objective
To show how the remoting framework supports serverside and client-side activation of remote objects and to describe the differences between serverside and client-side activation.
!
Before Using a Remote Object, the Client Must Activate It #
!
!
Lead-in
The remoting framework supports server-side and client-side activation of remote objects.
!
By calling new, Activator.CreateInstance, or Activator.GetObject
Activation Returns Proxy Used by Client to Access Remote Object #
Proxy represents remote object in client’s AppDomain
#
Proxy forwards client’s calls, and returns results and exceptions
Server-Side Activation – Automatic Instantiation by Server #
Single call object handles only one request (stateless)
#
Singleton object services multiple clients and requests (stateful)
Client-Side Activation – Instantiation by Explicit Client Call #
State maintained between method calls for specific client instance
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The remoting framework supports server-side and client-side activation of remote objects. Server-side activation means that when a client attempts to access the object, the object is instantiated on the server automatically. Client-side activation, on the other hand, means that the object is instantiated in response to a deliberate activation request from a client. You can select the best activation model to control the instantiation and lifetime of a remote object.
Methods of Remote Object Activation Before a client can use a remote object, the remote object must be activated, and the client must obtain a proxy to access the remote object. You can activate a remote object by calling new or by calling the following methods of the Activator class: !
Activator.CreateInstance Used to create an object instance
!
Activator.GetObject Generally used to connect to an object that is already running at a specified Uniform Resource Identifier
8
Module 13: Remoting and XML Web Services
The Role of Proxies in Remote Object Interaction When a client activates a remote object, the client obtains a proxy to the class instance on the server. Any interaction with a remote object occurs by reference through the proxy. The proxy object acts as a representative of the remote object and ensures that all calls that are made on the proxy are forwarded to the correct remote object instance. All methods that are called on the proxy are automatically forwarded to the remote class, and any results are returned to the client. From the client’s perspective, this process is identical to the process of making a local call. Any exceptions that are thrown by the remote object are automatically returned to the client. Because those exceptions are returned to the client, the client can use try/catch blocks around sections of code to trap and handle exceptions.
Server-Side Activation Server-side activation supports single call and singleton modes of activation.
Single Call Objects A single call object services only one request. Single call objects are useful in scenarios in which: !
The overhead of creating an object is not significant.
!
Objects are configured in a load-balanced fashion.
!
State information is usually not needed between calls.
Because single call objects cannot hold state information between method calls, they are sometimes referred to as stateless objects.
Singleton Objects A singleton object services multiple clients and multiple requests. Therefore, a singleton object can store state information between client invocations. Singleton objects are useful when you want to share data explicitly between clients and method invocations, and when the overhead of creating and maintaining objects is substantial. Because singleton objects can maintain their state over a prolonged period of time, they are sometimes referred to as stateful objects.
Module 13: Remoting and XML Web Services
9
Client-Side Activation Client-activated objects are activated on a request from the client. This method of activating server objects is similar to the classic COM coclass activation. The activation process is as follows: 1. When the client requests a server object, an activation request message is sent to the remote application. 2. The server then creates an instance of the requested class and returns an ObjRef object to the client application that invoked it. 3. A proxy is then created on the client side by using the ObjRef object. A client-activated object can store state information between method calls for its specific client. However, state information is not shared between multiple client-activated objects. Each request for a remote object instance returns a proxy to an independent instance of the server type. A useful function of client-activated objects is that constructor arguments can be passed by the local application to the constructor of the object in the remote application.
10
Module 13: Remoting and XML Web Services
Lease-Based Lifetime Topic Objective
To explain how using a leasing mechanism can extend the lifetime of clientactivated remote objects.
!
A Leasing Mechanism Controls the Lifetime of a ClientActivated Remote Object
Lead-in
!
An Object’s Lease Time Can Be Extended
!
When an Object’s Lease Time Reaches Zero
You can control the lifetime of client-activated remote objects by using a leasing mechanism.
#
The object is disconnected from remoting infrastructure
#
The object may be garbage-collected
#
A lease provides an alternative to reference counting
*****************************ILLEGAL FOR NON-TRAINER USE****************************** You can control the lifetime of client-activated remote objects by using a leasing mechanism. When an object is first created, it is given a lease time. When the lease time of the object reaches zero, the object is disconnected from the remoting infrastructure. After the references to the object from within the object’s application domain have been freed, the object may be collected when the next garbage collection occurs. You can extend the lease on an object by using a number of mechanisms. For more information about extending the lease on an object, see “Lease-Based Lifetime Concepts” in the .NET Framework Software Development Kit (SDK) documentation. You can use leases to manage the lifetime of remote objects as an alternative to reference counting, which tends to be complex and inefficient over unreliable network connections. A potential disadvantage of leasing is that the lifetime of a remote object may be extended for longer than is required. However, the advantages of reducing network traffic that is devoted to reference counting and the pinging of clients outweigh the disadvantage of the extended lifetimes of remote objects.
Module 13: Remoting and XML Web Services
11
Object Marshaling Topic Objective
To explain how objects are marshaled in .NET Remoting.
!
Objects Instantiated Remotely Are Returned by Reference and Accessed by the Client Through a Proxy
Lead-in
!
Remote Call Parameters, Return Values, and Fields Can Be:
Because calls to a remote object can cause other objects to be passed across a remoting boundary, you should understand how objects are marshaled in .NET Remoting.
#
#
#
Marshal-by-value objects – A copy of the object is passed from one AppDomain to another - Value types and classes that are serializable Marshal-by-reference objects – A reference to the object is passed from one AppDomain to another - Classes that derive from the System.MarshalByRefObject class Not-marshaled objects – Objects suitable for local use only - Any class that is not Marshal-By-Value or Marshal-By-Reference
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Because calls to a remote object can cause other objects to be passed across a remoting boundary, you should understand how objects are marshaled in .NET Remoting. Inside the remoting boundary of the caller, for example, inside a single application domain, objects are passed by reference, and primitive data types are passed by value. Application domains are hard boundaries. Applications running in different application domains share no information, no global variables, and no static fields on classes. As mentioned in Remoting in this module, .NET Remoting is used to communicate between objects in different application domains. All objects that are instantiated remotely are returned by reference. When a client instantiates a remote object, it receives a proxy to the class instance on the server. All methods that are called on the proxy are automatically forwarded to the remote class, and any results are returned to the client. The following examples show scenarios where objects are passed across remoting boundaries because of calls to remote objects: !
Object parameters in a method call, such as myObj in: public int myRemoteMethod (MyRemoteObject myObj)
!
Object return values of method calls, such as MyRemoteObject instances in: public MyRemoteObject myRemoteMethod(String myString)
!
Objects that result from property or field access of a remote object, such as instances that are accessed in the myNestedObject field of myObj in: myObj.myNestedObject
All objects in the .NET Framework fall into three general remoting categories: marshal-by-value, marshal-by-reference, and not-marshaled.
12
Module 13: Remoting and XML Web Services
Marshal-By-Value Objects Marshal-by-value objects include value types and classes that are serializable. A copy of such marshal-by-value objects is passed from one application domain to another. You should use marshal-by-value objects when you need to move the complete state of the object with the execution to the target application domain for performance or processing purposes. In many scenarios, marshal-by-value objects reduce boundary crossing, such as network, process, and application domain roundtrips. Marshal-by-value objects have no distributed identity, and no proxy is ever created to reference them, as shown in the following example: [Serializable] class Foo1A { //. . . }
To participate in its own serialization when it is marshaled across application domain boundaries, an object can expose the ISerializable interface, as in the following example: using System.Runtime.Serialization; //... [Serializable] class Foo1B : ISerializable { //. . . }
Marshal-By-Reference Objects References to marshal-by-reference objects are made when the object reference (ObjRef) is passed from one application to another. When the object reference arrives in the remote application, it is converted to a proxy back to the original object. The original object remains in the application domain in which it was created. A marshal-by-reference object’s class must derive from the System.MarshalByRefObject class. Use marshal-by-reference objects when an object’s state should remain in the application domain in which it was created and when only references to that object should be marshaled at the time that the object is remoted. For example, make a file object, whose internal representation contains a field that is an operating system handle, application domain-bound. In this case, the operating system handle would not be useful or appropriate in another application domain, process, or computer. All operations on a marshal-by-reference object are appropriately indirected so that the common language runtime can intercept and forward them. This indirection applies to fields, properties, and methods of marshal-by-reference objects. For this reason, the performance overhead of marshal-by-reference objects is greater than the performance overhead of marshal-by-value objects.
Module 13: Remoting and XML Web Services
13
The following example declares a class Foo2 that is marshal-by-reference: class Foo2 : MarshalByRefObject { //. . . }
Not-Marshaled Objects Not-marshaled objects are the default for all objects that do not derive from System.MarshalByRefObject and that do not have the [Serializable] custom attribute. The use of not-marshaled objects is appropriate when an object should not leave the application domain because the object was designed for local use only, as in the following example: class Foo3 { //. . . }
14
Module 13: Remoting and XML Web Services
Server Side Topic Objective
To explain how to register and activate a remote object from the server side.
Lead-in
Remote objects must be registered with the remoting framework before clients can access them.
!
Register the Channel
!
Register Remote Objects by Using: #
The RegisterWellKnownServiceType call
RemotingConfiguration.RegisterWellKnownServiceType( RemotingConfiguration.RegisterWellKnownServiceType( typeof(HelloServer), typeof(HelloServer), "SayHello", "SayHello", WellKnownObjectMode.SingleCall); WellKnownObjectMode.SingleCall); #
Or a configuration file
RemotingConfiguration.Configure("MyHello.exe.config"); RemotingConfiguration.Configure("MyHello.exe.config");
*****************************ILLEGAL FOR NON-TRAINER USE****************************** All remote objects must be registered with the remoting framework before clients can access them. Object registration is usually performed by a hosting application that starts up, registers one or more channels with ChannelServices, registers one or more remote objects with RemotingServices, and then waits until it is terminated. Note The registered channels and objects are available only while the process that registered them is alive. When the process terminates, all channels and objects that are registered by this process are automatically removed from the remoting services where they were registered. The following information is required when you register a remote object with the remoting framework: !
The type name of the remote object
!
The object Uniform Resource Identifier that clients will use to locate the object
!
For server-side activation, the object mode that is required • The object mode can be single call or singleton.
Module 13: Remoting and XML Web Services
15
Methods of Remote Object Registration You can register a remote object by calling RemotingConfiguration.RegisterWellKnownServiceType, and passing the information that is described in the preceding list as parameters, or by storing that information in a configuration file and then calling RemotingConfiguration.Configure and passing the name of the configuration file as a parameter. RegisterWellKnownServiceType and Configure perform the same function, but Configure is more convenient to use because the contents of the configuration file can be altered without recompiling the host application.
RegisterWellKnownServiceType The following example code shows how to register the HelloServer class as a SingleCall remote object by using the RegisterWellKnownServiceType method. RemotingConfiguration.RegisterWellKnownServiceType( typeof(HelloServer), "SayHello", WellKnownObjectMode.SingleCall);
In the preceding example, RemotingSamples.HelloServer is the name of the class, and SayHello is the object Uniform Resource Identifier.
Configuration File The following example code shows how to register the HelloServer class as a SingleCall remote object by using the Configure method: RemotingConfiguration.Configure("MyHello.exe.config");
In the preceding example, the configuration file, MyHello.exe.config stores the same registration information that is stored in the parameters of the preceding RegisterWellKnownServiceType method. The format of the .NET Remoting configuration file is described in Remoting Configuration Files in this module. When the remote object in the preceding example is registered, the remoting framework creates an object reference and then extracts the required metadata about the object from the assembly. The object’s required metadata, the Uniform Resource Identifier, and the assembly name are then stored in the object reference, which is filed in a remoting framework table that is used for tracking registered remote objects. Note The remote object itself is not instantiated by the registration process. Instantiation occurs only when a client attempts to call a method on the object or activates the object from the client side.
16
Module 13: Remoting and XML Web Services
Client Side Topic Objective
To explain how to register and activate a remote object from the client side.
Lead-in
On the client side, a client registers the channel and activates the remote object.
!
Register the Channel
ChannelServices.RegisterChannel(new ChannelServices.RegisterChannel(new TcpChannel()); TcpChannel()); !
Activate Remote Object by Using: #
Activator.GetObject
HelloServer HelloServer obj obj == (HelloServer)Activator.GetObject( (HelloServer)Activator.GetObject( typeof(RemotingSamples.HelloServer), typeof(RemotingSamples.HelloServer), "tcp://localhost:8085/SayHello"); "tcp://localhost:8085/SayHello"); #
new and a configuration file
RemotingConfiguration.Configure("MyHello.exe.config"); RemotingConfiguration.Configure("MyHello.exe.config"); HelloServer HelloServer obj obj == new new HelloServer(); HelloServer();
*****************************ILLEGAL FOR NON-TRAINER USE****************************** On the client side, a client registers the channel and activates the remote object.
Registering the Channel A client must first register the channel by calling ChannelServices.RegisterChannel. As previously mentioned in Server Side in this module, the server must first have registered the selected channel. The following code example shows how to register a TCP Channel: ChannelServices.RegisterChannel(new TcpChannel());
Important You do not specify a port number when you register the client channel.
Activating Remote Objects After the client registers the channel, it can then activate the remote object by using GetObject or the new operator. It is important to note that the object is not instantiated when either of these calls is made. Actually, no network calls are generated at all. The remoting framework obtains enough information from the metadata to create the proxy without connecting to the remote object. A network connection is only established when the client calls a method on the proxy. When the call arrives at the server, the remoting framework extracts the Uniform Resource Identifier from the message, examines the remoting framework tables to locate the reference for the object that matches the Uniform Resource Identifier, and then instantiates the object if necessary, forwarding the method call to the object. If the object is registered as SingleCall, it is destroyed after the method call is completed. For each method that is called, a new instance of the object is created.
Module 13: Remoting and XML Web Services
17
The only difference between GetObject and new is that GetObject allows you to specify a Uniform Resource Identifier as a parameter, while new obtains the Uniform Resource Identifier from the configuration file. You can use CreateInstance or new for client-activated objects. Both CreateInstance and new allow you to instantiate an object by using constructors with parameters. The lifetime of client-activated objects is controlled by the leasing service that is provided by the remoting framework.
Using Activator.GetObject The following example code shows how to obtain a server-activated object by using Activator.GetObject: HelloServer obj = (HelloServer)Activator.GetObject( typeof(RemotingSamples.HelloServer), "tcp://localhost:8085/SayHello");
In the preceding example, "tcp://localhost:8085/SayHello" specifies that a connection should be made to the remote object at the SayHello endpoint by using TCP on port 8085.
Using the new Operator To use the new operator, you must load a configuration file with the remote object’s information. For example, for a file called MyHello.exe.config, you load the configuration file as follows: RemotingConfiguration.Configure("MyHello.exe.config");
After the configuration file has been loaded, the client can activate the object as follows: HelloServer obj = new HelloServer();
18
Module 13: Remoting and XML Web Services
Client Compilation Techniques Topic Objective
To explain how type information is provided to the compiler.
!
When the Client Is Compiled, the Compiler Needs Server Class Data
!
Class Information Can Be Provided by:
Lead-in
When compiling the client code, the compiler requires type information about the HelloServer class.
#
A reference to the assembly where the class is stored
#
Splitting the remote object into an implementation class and an interface type
#
Using Wsdl.exe to extract the required metadata directly from the endpoint
*****************************ILLEGAL FOR NON-TRAINER USE****************************** When compiling the client code, the compiler requires type information about the HelloServer class. This type information is discussed in the preceding topic. You can provide type information in one of the following ways: !
Provide a reference to the assembly in which the HelloServer class is stored. Providing a reference is useful when the client and server components are developed at the same site.
!
Split the remote object into an implementation class and an interface type, and use the interface as a reference when compiling the client. Splitting the remote object into an implementation class and an interface type is useful when the client and server components are not developed at the same site. The interface or interfaces can be compiled to a DLL and shipped to the client sites when necessary. According to COM guidelines, you should avoid changing the published interface.
!
Use the Web Services Description Language tool (Wsdl.exe) to extract the required metadata directly from the endpoint. The Web Services Description Language tool lets you connect to the endpoint that is provided, extract the metadata, and generate source code that can then be used to compile the client. The Web Services Description Language tool is useful when client and server components are developed at different sites and when no interface classes are available. To use the Web Services Description Language tool, point the tool at a remote Uniform Resource Identifier and generate the required metadata.
Note The Web Service Utility extracts only metadata and does not generate the source for the remote object.
Module 13: Remoting and XML Web Services
19
Remoting Configuration Files Topic Objective
To explain how to configure objects with configuration files for use in remoting.
Lead-in
Programmatic registration is a simple process, but it is not practical for use in reallife situations where large numbers of remote objects must be managed on a corporate network.
!
Configure Method on RemotingConfiguration
RemotingConfiguration.Configure(foo.exe.config); RemotingConfiguration.Configure(foo.exe.config); !
An Application Configuration File is an XML Document #
Configuration files are case-sensitive
#
Can be specified on a machine or on an application level Application level configuration takes priority over machine level configuration
#
For more information see “Remoting Configuration File Format” in the .NET Framework SDK documentation
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Programmatic registration is a simple process, but it is not practical for use in real-life situations where large numbers of remote objects must be managed on a corporate network. Remoting configuration solves this problem by using a configuration file, a simple mechanism, to register an object. To configure an object with a configuration file, call the Configure method on RemotingConfiguration, as shown in the following example: using System; using System.IO; using System.Runtime.Remoting; public class MyHost { public static void Main(String[] args) { if (args.Length == 0) { // Perform a default configuration, throw an exception // or display usage information to the user } else { RemotingConfiguration.Configure (args[0]); } // The program should pause here till the // registered objects are no longer required } }
20
Module 13: Remoting and XML Web Services
The Configure method loads the configuration file into memory, parses the contents, and calls the relevant methods to register the channels and objects that are described in the file. You can also use configuration files to store such settings as binding policy and security. The name of the configuration file includes the full module name and the extension, with .config appended to that extension. For example, the configuration file name for Foo.exe is Foo.exe.config. Although .NET Remoting does not mandate how you name a configuration file, you should use the naming convention that is described in the preceding paragraph to ensure that specific security and binding policies are picked up when an application is executed. The Configure call on RemotingConfiguration only reads the relevant sections in the configuration file that apply to remoting, while the rest of the information is ignored.
Module 13: Remoting and XML Web Services
21
An application configuration file is an XML document that contains sections for various feature areas. The general format is a follows: Note Configuration files are case-sensitive. <system.runtime.remoting> <service> <SOAPinterop> <serverProviders>
All configuration information can be specified on a machine or on an application level. Application level configuration takes priority over machine level configuration. For more information about the .NET Remoting configuration file format, see “Remoting Configuration File Format” in the .NET Framework SDK documentation.
22
Module 13: Remoting and XML Web Services
Demonstration: Remoting Topic Objective
To demonstrate how a client application uses .NET Remoting to make a method call on an object in a server application.
Lead-in
This demonstration shows how a client application uses .NET Remoting to make a method call on an object in a server application.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This demonstration shows how a client application can use .NET Remoting to make a method call on an object in a server application. Delivery Tip
Although this demonstration includes detailed instructions, this is not a guided practice. Present the demonstration and suggest that the students try the demonstration for themselves later.
Important Before running the application, ensure that all currently running server applications that may be using the same port number have been closed. Running more than one server that uses the same port number will generate an error. Tip You can display your machine’s current TCP/IP network connections by running the program netstat in a command prompt window. The following example shows that port 7 is in use by a process with PID 1504: C:\>netstat -o -n -a Active Connections Proto TCP
Local Address 0.0.0.0:7
Foreign Address 0.0.0.0:0
State PID LISTENING 1504 …
Module 13: Remoting and XML Web Services
23
Server-Side The following sample code is the server source code, which is named Server.cs: using using using using using
System; System.Runtime.Remoting; System.Runtime.Remoting.Channels; System.Runtime.Remoting.Channels.Tcp; System.Runtime.Remoting.Channels.Http;
namespace RemotingSamples { public class HelloServer : MarshalByRefObject { public int callCounter = 0; public static int Main(string [] args) { TcpChannel chan1 = new TcpChannel(8085); HttpChannel chan2 = new HttpChannel(8086); ChannelServices.RegisterChannel(chan1); ChannelServices.RegisterChannel(chan2); /* RemotingConfiguration.RegisterWellKnownServiceType ( typeof(HelloServer), "SayHello", WellKnownObjectMode.Singleton ); */ RemotingConfiguration.RegisterWellKnownServiceType( typeof(HelloServer), "SayHello", WellKnownObjectMode.SingleCall );
System.Console.WriteLine("Press Enter key to exit"); System.Console.ReadLine(); return 0; }
(Code continued on the following page.)
24
Module 13: Remoting and XML Web Services public HelloServer() { Console.WriteLine("HelloServer activated"); } public String HelloMethod(String name, out int counter) { counter = ++callCounter; Console.WriteLine( "Server Hello.HelloMethod : {0} Counter :{1}", name, callCounter); return "Hi there " + name; } } }
The RemotingSamples.HelloServer class is derived from MarshalByRefObject, so HelloServer is remotable. When the server is started, you create and register a TCP channel that listens for clients to connect on port 8085 and an HTTP channel that listens for clients to connect on port 8086. You also register the remote object with the remoting framework by calling RegisterWellKnownServiceType. The parameters for this call include the following: !
The full type of the object that is being registered, such as RemotingSamples.HelloServer in the preceding example
!
The name of the endpoint where the object will be published To connect to the object, clients must know the name of the endpoint. Any string can be used. In the preceding example, you use SayHello. You can also connect to remote objects through ASP.NET. In the preceding example, if you were connecting to remote objects through ASP.NET, the endpoint would be RemotingSamples/HelloServer.soap.
!
The object mode, which can be SingleCall or Singleton In the preceding example, you initially specify SingleCall. The object mode specifies the lifetime of the object when it is activated on the server. In the case of SingleCall objects, a new instance of the class is created for each call that is made from a client, even if the same client calls the same method more than once. Singleton objects, on the other hand, are created only once, and all clients communicate with the same object.
Module 13: Remoting and XML Web Services
25
Client-Side The following sample code is the client source code, which is named Client.cs: using using using using using using
System; System.Runtime.Remoting; System.Runtime.Remoting.Channels; System.Runtime.Remoting.Channels.Tcp; System.Runtime.Remoting.Channels.Http; System.IO;
namespace RemotingSamples { public class Client { public static int Main(string [] args) { int counter; TcpChannel chan1 = new TcpChannel(); ChannelServices.RegisterChannel(chan1); HelloServer obj1 = (HelloServer)Activator.GetObject( typeof(RemotingSamples.HelloServer), "tcp://localhost:8085/SayHello"); if (obj1 == null) { System.Console.WriteLine( "Could not locate TCP server"); return 1; }
HttpChannel chan2 = new HttpChannel(); ChannelServices.RegisterChannel(chan2); HelloServer obj2 =(HelloServer)Activator.GetObject( typeof(RemotingSamples.HelloServer), "http://localhost:8086/SayHello"); if (obj2 == null) { System.Console.WriteLine( "Could not locate HTTP server"); return 1; }
(Code continued on the following page.)
26
Module 13: Remoting and XML Web Services try { Console.WriteLine( "Client1 TCP HelloMethod {0} Counter {1}", obj1.HelloMethod("Caveman", out counter), counter); Console.WriteLine( "Client2 HTTP HelloMethod {0} Counter {1}", obj2.HelloMethod("Caveman", out counter), counter); } catch (IOException ioExcep) { Console.WriteLine("Remote IO Error" + "\nException:\n" + ioExcep.ToString()); return 1; } return 0; } } }
When the client starts up, it registers a TCP channel and an HTTP channel and proceeds to activate an object on each channel by calling the GetObject method on the Activator class. The parameters for this call are the type of the name of the class that you need to activate, RemotingSamples.HelloServer, and the endpoint Uniform Resource Identifier. For the client’s TCP connection, the Uniform Resource Identifier is tcp://localhost:8085/SayHello. For the client’s HTTP connection, the Uniform Resource Identifier is http://localhost:8086/SayHello. Important The Uniform Resource Identifier includes the protocol, computer name, and port number, as well as the endpoint. If the server is deployed on a host that is named Sunshine, clients can connect to the server that is using TCP by specifying tcp://sunshine:8085/SayHello. When you run the client, it locates and connects to the server, retrieves a proxy for the remote objects, and calls the HelloMethod on the remote objects, passing the string Caveman as a parameter and the counter as an out parameter. The server returns Hi there Caveman and the count of the number of times that the server object’s method has been called.
Module 13: Remoting and XML Web Services
27
Building and Executing the Server and Client To build the server and the client in the same directory, type the following command at the Microsoft Visual Studio .NET Command Prompt command prompt: Important To use Microsoft Visual Studio .NET tools within a command prompt window, the command prompt window must have the proper environment settings. The Visual Studio .NET Command Prompt window provides such an environment. To run a Visual Studio .NET Command Prompt window: on the Start menu point to All Programs, point to Microsoft Visual Studio .NET, Visual Studio .NET Tools, and then click Visual Studio .NET Command Prompt. csc server.cs csc /r:server.exe client.cs
To execute the application, start the server application from a console window, and then start the client application from another console window. Note The counter values on the two client calls should have the same value because the server object’s activation mode is SingleCall.
Changing the Activation Mode To change the server’s activation mode from SingleCall to Singleton, change the RegisterWellKnownServiceType call in the server as follows: RemotingConfiguration.RegisterWellKnownServiceType( typeof(HelloServer), "SayHello", WellKnownObjectMode.Singleton);
Rebuild and execute as in the preceding example. Note the counter values on both calls. They should increase after each call.
28
Module 13: Remoting and XML Web Services
Lab 13.1: Building an Order-Processing Application by Using Remoted Servers Topic Objective
To introduce the lab.
Lead-in
In this lab, you will create an XML Web service hosted in a .NET executable file, create a TCP server, and create a client that uses .NET Remoting to access an XML Web service and a TCP server.
*****************************ILLEGAL FOR NON-TRAINER USE******************************
Objectives After completing this lab, you will be able to: !
Create an XML Web service hosted in a .NET executable file.
!
Create a TCP server.
!
Create a client that uses .NET Remoting to access both an XML Web service and a TCP server.
Lab Setup Only solution files are associated with this lab. The solution files for this lab are in the folder \Labs\Lab13.1\Solution.
Module 13: Remoting and XML Web Services
29
Scenario This lab is based on a scenario of a simple distributed order-processing application, in which a customer specifies a customer ID and an item number for an item that the customer wants to purchase, and the application processes the order. The order processing involves authenticating the customer’s ID and arranging order fulfillment that is to say, having the ordered item sent to the customer. In this lab, you will create a distributed solution that uses .NET Remoting. You will use .NET Remoting support of SOAP over HTTP to create a server application that exports an authenticate method as an open standards-based XML Web service. You will also use .NET Remoting support of TCP with binary formatting to create a remote order fulfillment server application that trades off the advantages of the open standards-based flexible XML Web services protocol for improved performance. In addition, you will create a simple test client to exercise these servers. Because the focus of this lab is on .NET Remoting, the functionality that is specific to authentication and fulfillment will be minimal.
Estimated time to complete this lab: 50 minutes
30
Module 13: Remoting and XML Web Services
Exercise 1 Creating an Authentication XML Web Service In this exercise, you will create an XML Web service that provides simple authentication. Simple authentication will consist of using a random-number generator to authenticate the user successfully 50 percent of the time.
! Create the authentication server 1. Using the editor of your choice, create a C# source file named Authenticate.cs, and save it in the Lab13.1 directory. 2. To simplify coding, add using statements to import the namespaces System, System.Runtime.Remoting, System.Runtime.Remoting.Channels, and System.Runtime.Remoting.Channels.Http. 3. Specify the namespace Lab13, and in it, create a public class named AServer that is marshal-by-reference. 4. Add a field named aRandom to hold a reference to a random number generator object of class System.Random. 5. In the Main method of AServer: a. Create and register a channel that supports the protocol and format for an XML Web service. Specify port 8086. b. Invoke the RemotingConfiguration.RegisterWellKnownServiceType method to register the authentication server whose full type name is Lab13.AServer, and whose endpoint is DoAuthentication. To retain the object’s state, that is to say, the random-number generator object, over multiple client calls, the well-known object’s mode should be stateful. c. Print to the console a message that tells the user to press any key so the server exits. The program should wait for this user action before continuing. 6. Add a public default constructor that creates a new instance of the Random class, assigns it to the field that was created in step 4, and prints a message to the console that states that AServer is running.
Module 13: Remoting and XML Web Services
31
7. Add a public method named Authenticate that takes a customer ID of type int and returns a bool value. a. Assign to a variable named aRandomNumber of type double a random number that is greater than or equal to 0 and is less than 1, by using the following code: double aRandomNumber =
aRandom.NextDouble();
b. Using the value from step a, assign to a variable of type bool named passed the value true for 50 percent of the time and the value false for 50 percent of the time. c. Print to the console a message that identifies the customer and states whether the customer is authenticated on the basis of the value of passed. d. Return passed. 8. In a Visual Studio .NET Command Prompt window, build Authenticate.exe. Important To use Microsoft Visual Studio .NET tools within a command prompt window, the command prompt window must have the proper environment settings. The Visual Studio .NET Command Prompt window provides such an environment. To run a Visual Studio .NET Command Prompt window: on the Start menu point to All Programs, point to Microsoft Visual Studio .NET, Visual Studio .NET Tools, and then click Visual Studio .NET Command Prompt.
32
Module 13: Remoting and XML Web Services
Exercise 2 Creating a Fulfillment Server In this exercise, you will create a server that provides simple order fulfillment functionality by using TCP and a binary format to provide fast communication. Simple fulfillment will consist of using a random-number generator to successfully fulfill the order 50 percent of the time.
! Create the fulfillment server 1. Using the editor of your choice, create a C# source file that is named Fulfillment.cs, and save it in the Lab13.1 directory. 2. To simplify coding, add using statements to import the namespaces System, System.Runtime.Remoting, System.Runtime.Remoting.Channels, and System.Runtime.Remoting.Channels.Tcp. 3. Specify the namespace Lab13, and in it, create a public class named FServer that is marshal-by-reference. 4. Add a field named aRandom to hold a reference to a random-number generator object of class System.Random. 5. In the Main method of FServer: a. Create and register a channel that uses the TCP protocol and binary formatting. Specify port 8085. Note You cannot have two channels on one computer that are using the same port number; therefore you cannot use port 8086. b. Invoke the RemotingConfiguration.RegisterWellKnownServiceType method to register the fulfillment server whose full type name is Lab13.FServer, and whose endpoint is DoFulfillment. To retain the object’s state, that is to say, the random-number generator object, over multiple client calls, the well-known object’s mode should be stateful. c. Print to the console a message that tells the user to press any key so the server exits. The program should wait for this user action before continuing. 6. Add a public default constructor that creates a new instance of the Random class, assigns it to the field that was created in step 4, and prints a message to the console that states that FServer is running.
Module 13: Remoting and XML Web Services
33
7. Add a public method named Fulfill that takes a customer ID of type int, an item number of type int, and returns a bool value. a. Assign to a variable named aRandomNumber of type double a random number that is greater than or equal to 0 and less than 1, by using the following code: double aRandomNumber =
aRandom.NextDouble();
b. Using the value from step a, assign to a variable of type bool named shipped the value true for 50 percent of the time and the value false for 50 percent of the time. c. Print to the console a message that identifies the customer and the item number and states whether the item was shipped on the basis of the value of shipped. d. Return shipped. 8. In a Visual Studio .NET Command Prompt window, build Fulfillment.exe.
34
Module 13: Remoting and XML Web Services
Exercise 3 Creating a Test Client In this exercise, you will create a test client application to test the orderprocessing servers. The client calls the authentication XML Web service. If the user is authenticated, the client calls the order fulfillment server.
! Create the test client 1. Using the editor of your choice , create a C# source file named Testclient.cs, and save it in the Lab13.1 directory. 2. To simplify coding, add using statements to import the namespaces System, System.Runtime.Remoting, System.Runtime.Remoting.Channels, System.Runtime.Remoting.Channels.Http, System.Runtime.Remoting.Channels.Tcp, and System.IO. 3. Specify the namespace Lab13, and in it, create a class named TestClient. In subsequent steps of this exercise, you will code the Main method. 4. In the Main method of TestClient: Create and register an HTTP channel and a TCP channel. 5. Invoke the Activator.GetObject method to create an AServer instance of type Lab13.AServer named aServer at the following URL: http://localhost:8086/DoAuthentication
6. Invoke the Activator.GetObject method to create an FServer instance of type Lab13.FServer named fServer at the URL: tcp://localhost:8085/DoFulfillment
7. In the try section of a try/catch block: a. Invoke the Authenticate method of the aServer object, and use 1234 as the customer’s ID. Print a message with the result to the console. b. If authentication passes, call the fServer object’s Fulfill method. Use 1234 as the customer’s ID and 5678 as the item number. Print a message with the result to the console. 8. In the catch section of the try/catch block: a. Catch any exceptions of type IOException, and print out their content. b. Return a value of 1. 9. In a Visual Studio .NET Command Prompt window, build Testclient.exe. Important Do not forget to reference Authenticate.exe, and Fulfillment.exe in the command line.
Module 13: Remoting and XML Web Services
35
10. Test the order system. a. Ensure that no server application is running and listening on ports 8085 and 8086. Tip You can display your machine’s current TCP/IP network connections by running the program netstat in a command prompt window. The following example shows that port 7 is in use by a process with PID 1504: C:\>netstat -o -n -a Active Connections Proto TCP
Local Address 0.0.0.0:7
Foreign Address 0.0.0.0:0
State PID LISTENING 1504 …
b. Open two separate console windows. In one window, run Fulfillment.exe, and in the other window, run Authenticate.exe. c. Open a third console window and repeatedly run Testclient.exe. The console output will vary because the random-number generator values cause authentication and fulfillment to succeed or fail 50 percent of the time. A typical series of test client invocations would produce output similar to the following: >testclient Authentication Server Customer 1234 Authorization: False >testclient Authentication Server Customer 1234 Authorization: False >testclient Authentication Server Customer 1234 Authorization: False >testclient Authentication Server Customer 1234 Authorization: True Fulfillment Server Customer 1234 Item Number 5678 Shipped: False >testclient Authentication Server Customer 1234 Authorization: True Fulfillment Server Customer 1234 Item Number 5678 Shipped: False >testclient Authentication Server Customer 1234 Authorization: True Fulfillment Server Customer 1234 Item Number 5678 Shipped: True
36
Module 13: Remoting and XML Web Services
" XML Web Services Topic Objective
To provide an overview of the topics covered in this section.
Lead-in
In this section, you will learn how to use Visual Studio .NET to implement an ASP.NET XML Web service and how to access the XML Web service from a Web browser and a client application.
!
ASP.NET XML Web Services Overview
!
ASP.NET Features
!
Consuming an XML Web Service
!
XML Web Service Discovery
*****************************ILLEGAL FOR NON-TRAINER USE****************************** XML Web services can be provided by and accessed from applications that reside in a variety of hosts. Hosts include, but are not limited to, ASP.NET, Microsoft Internet Explorer, executable files, Microsoft Windows® NT® Server, and Microsoft Windows 2000 Component Services, also known as COM+ Services. In the preceding sections, you have learned how to use .NET Remoting to create executable files that host XML Web services by using the default HTTP channel with SOAP formatting. In this section, you will learn how to use Visual Studio .NET to implement an ASP.NET XML Web service and how to access this XML Web service from both a Web browser and a client application.
Module 13: Remoting and XML Web Services
37
ASP.NET XML Web Services Overview Topic Objective
To provide an overview of ASP.NET XML Web services.
!
ASP.NET Files with .asmx Extensions Are Web Services
!
Example of HelloWorld .asmx File
<%@ <%@ WebService WebService Language="C#" Language="C#" Class="HelloWorld" Class="HelloWorld" %> %> using using System; System; using using System.Web.Services; System.Web.Services; public public class class HelloWorld HelloWorld :: WebService WebService {{ [WebMethod] [WebMethod] public public String String SayHelloWorld() SayHelloWorld() {{ return return "Hello "Hello World"; World"; }} }}
Lead-in
ASP.NET provides support for XML Web services with the .asmx file, which is a text file that is similar to an .aspx file.
!
If File Is Placed on Server Foo Inside a Virtual Directory Bar #
Access service by using:
http://Foo/Bar/HelloWorld.asmx http://Foo/Bar/HelloWorld.asmx #
Access WSDL of service by using:
http://Foo/Bar/HelloWorld.asmx?wsdl http://Foo/Bar/HelloWorld.asmx?wsdl
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This section introduces some of the basic features of ASP.NET that enable developers to host and use XML Web services. The Internet is quickly evolving from its present stage where Web sites deliver user interface (UI) pages to Web browsers to a next generation of programmable Web sites that directly link organizations, applications, services, and devices. These programmable Web sites are more than passively accessed sites; they are reusable, intelligent XML Web services.
Using .asmx Files ASP.NET provides support for XML Web services with the .asmx file. An .asmx file is a text file that is similar to an .aspx file. An .asmx file can be part of an ASP.NET application that includes .aspx files. Like .aspx files, .asmx files are automatically compiled by the ASP.NET runtime when a request to the service is made. Subsequent requests are serviced by a cached pre-compiled type object. Like .aspx files, .asmx files are then Uniform Resource Identifieraddressable. Delivery Tip
You can demonstrate the SayHelloWorld service by cutting and pasting the HelloWorld.asmx code into Notepad and saving it as a file named HelloWorld.asmx in the source directory for an existing IIS virtual directory. You then type the appropriate URL in the Internet Explorer Address bar. To make the Address bar visible, click the View menu, point to Toolbars, and then click Address Bar.
The following example shows the code that is contained in the HelloWorld.asmx file. It is a simple example of a ASP.NET-hosted XML Web service. <%@ WebService Language="C#" Class="HelloWorld" %> using System; using System.Web.Services; public class HelloWorld : WebService { [WebMethod] public String SayHelloWorld() { return "Hello World"; } }
38
Module 13: Remoting and XML Web Services
The HelloWorld.asmx file starts with an ASP.NET directive, WebService, and sets the language to C#. You can also set the language to Microsoft Visual Basic®. The class HelloWorld contains the XML Web service and therefore is derived from the base class WebService in the System.Web.Services namespace. All of the methods that will be accessible as part of the service must have the custom attribute, [WebMethod], in front of their signatures. In the preceding example, the SayHelloWorld method is the service’s only method.
Using a Virtual Directory To create an ASP.NET application, you can use an existing virtual directory or create a new one. For example, if you installed Microsoft Windows 2000 Server and Internet Information Server (IIS) on a computer, that computer probably now has the directory C:\InetPub\WWWRoot. To configure IIS, start the Internet Information Services tool. Click Start, click Control Panel, Performance and Maintenance, Administrative Tools, and then click Internet Information Services. In the Internet Information Services tool, you can create a new virtual directory or promote an existing directory. !
To create a new virtual directory, right-click an existing directory, and then click New.
!
To promote an existing directory, right-click a virtual directory, click Properties and then set the Local Path.
If you place the HelloWorld.asmx file on a server called Foo inside a virtual directory called Bar, you can use a URL in Internet Explorer to test the application. For example, if you type http://Foo/Bar/HelloWorld.asmx in the Address bar, the resulting page shows the public methods for this XML Web service and the protocols, such as SOAP or HTTP GET, that you can use to invoke these methods. The public methods for the XML Web service are marked with the [WebMethod] attribute. The Web Services Description Language XML file for this service is produced when you type http://Foo/Bar/HelloWorld.asmx?WSDL in the Internet Explorer Address bar. This WSDL file is important and can be used by clients to access the service.
Module 13: Remoting and XML Web Services
39
ASP.NET Features !
Topic Objective
To explain how to create an XML Web service by exposing a .NET class that inherits from the WebService class, and to introduce advanced ASP.NET features.
ASP.NET Can Expose Web Services Defined in a .NET Class #
Class derives from WebService, method attribute [WebMethod]
namespace namespace MyNameSpace MyNameSpace {{ public public class class HelloWorld HelloWorld :: WebService WebService {{ [WebMethod] [WebMethod] public public String String SayHelloWorld() SayHelloWorld() {{ return "Hello World"; } return "Hello World"; } }} # }}
Lead-in
#
In ASP.NET, you can create an XML Web service by simply exposing a .NET class that inherits from the WebService class.
Class source file is compiled into a library DLL and placed in \Bin
csc csc /out:bin\helloworld.dll /out:bin\helloworld.dll /t:library /t:library helloworld.cs helloworld.cs #
The .asmx file contains a single line that names the class
<%@ <%@ WebService WebService Class="MyNameSpace.HelloWorld" Class="MyNameSpace.HelloWorld" %> %> !
Advanced ASP.NET Features #
Data sets, Global.asax, Session and Application objects, pattern matching
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In ASP.NET, you can create an XML Web service by simply exposing a .NET class that inherits from the WebService class. You compile the class’s source file into a library DLL and place this DLL in the ASP.NET application’s \Bin subdirectory. You then create in the ASP.NET application’s directory an .asmx file that contains only the following single line of code: <%@ WebService Class="." %>
The HelloWorld.cs source code defines a HelloWorld service with an exported SayHelloWorld method in the MyNameSpace namespace, as shown in the following example: using System; using System.Web.Services; namespace MyNameSpace { public class HelloWorld : WebService { [WebMethod] public String SayHelloWorld() { return "Hello World"; } } }
To compile this HelloWorld.cs file in the ASP.NET application directory, you type the following command in a Visual Studio .NET Command Prompt window: >csc /out:bin\helloworld.dll /t:library helloworld.cs
40
Module 13: Remoting and XML Web Services
You then create the file named HelloWorld.asmx in the ASP.NET application directory. HelloWorld.asmx contains the following single line of code: <%@ WebService Class="MyNameSpace.HelloWorld" %>
The only methods that are exposed from a service are those class methods that are flagged with a [WebMethod] custom attribute. Without this attribute, the method is not exposed from the service. Not exposing a method from a service is useful when you want to hide implementation details that are called by public XML Web service methods or when the WebService class is also used in local applications. Note A local application can use any public method, but only [WebMethod] methods are remotely accessible through SOAP.
Advanced ASP.NET Features ASP.NET is also useful for building complex XML Web services. Advanced ASP.NET features include: !
Data sets Data sets are a powerful new XML-based technique to represent disconnected data. Data sets can be returned from an XML Web service method. Because Data sets can store complex information and relationships in an intelligent structure, they enable you to take full advantage of XML Web services. When you expose Data sets through a service, you can limit the database connections to your data server.
!
Global.asax file The Global.asax file adds application-level logic and event-handling code to Web applications.
!
Session and Application objects You can use ASP.NET intrinsics, such as the Session and Application objects, to manage an ASP.NET application’s state.
!
Text Pattern Matching Text Pattern Matching is a technology that can be used to address any Uniform Resource Identifier that returns text as if it were an XML Web service.
Further discussion of these and other advanced ASP.NET features is beyond the scope of this course.
Module 13: Remoting and XML Web Services
41
Demonstration: Using Visual Studio .NET to Create an XML Web Service Topic Objective
To demonstrate how to use Visual Studio .NET to create an XML Web service.
Lead-in
In this demonstration, you will learn how to create a service called MathService that has a single method named SalesTax.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this section, you have learned how to create an XML Web service by using ASP.NET. Visual Studio .NET provides an even easier way to build ASP.NEThosted XML Web services. In this demonstration, you will see how to create a service called MathService that has a single method named SalesTax. The SalesTax method takes two parameters: The first parameter represents the sales amount; and the second parameter represents the fractional sales tax rate. For example, a sales tax rate of 8.5 percent is represented as .085. SalesTax returns a total amount that is equal to the sales amount plus the sales tax.
! To create a service called MathService that has a single method named SalesTax
Delivery Tip
Although this demonstration includes detailed instructions, it is not a guided practice. Present the demonstration and suggest that students try the demonstration for themselves later.
1. Start Visual Studio, and create a new project. a. In the New Project dialog box, select Visual C# Projects as the type. b. Select ASP.NET Web Service as the template. You may need to scroll down to see ASP.NET Web Service. c. In the Location box, type the name of the Web server, http://localhost/MathService. The grayed out Name box will now contain the text MathService. Then click OK. 2. On the View menu, click Code. 3. Because the XML Web service in this demonstration is simple, you can work on the code directly. For more complex XML Web services, you can use the Service1.asmx.cs Design palette that is displayed when you create a new project of type XML Web service. The Service1.asmx.cs Design palette enables the drag-and-drop operation of rapid application development that can make complex services easier to set up. 4. In the Service1 class, examine the HelloWorld method in the commentedout WEB SERVICE EXAMPLE.
42
Module 13: Remoting and XML Web Services
5. In Service1.asmx.cs, implement the SalesTax method by inserting the following code after the comments for the HelloWorld method. You should leave the HelloWorld code commented out. [WebMethod] public float SalesTax( float salesAmount, float fractionalTaxRate) { return salesAmount + (salesAmount * fractionalTaxRate); }
6. On the Build menu, click Build Solution to create the XML Web service. 7. Use Internet Explorer to access the XML Web service at the following URL: http://localhost/MathService/Service1.asmx
Tip To use Internet Explorer to view an XML Web service from within the Visual Studio environment, right-click the .asmx file in the Solution Explorer window, and then click View in Browser. 8. In the Internet Explorer page that is returned in step 7, click the link for the Service1 operation labeled SalesTax. 9. In the Internet Explorer page that is returned in step 8, enter some parameters, and then click Invoke to verify that the correct value is returned and displayed in SOAP/XML format. 10. Use Internet Explorer to access and verify that the XML Web service’s description is obtained from the following URL: http://localhost/MathService/Service1.asmx?wsdl
Module 13: Remoting and XML Web Services
43
Consuming an XML Web Service Topic Objective
To explain how XML Web services are consumed and how to use the Web Services Description Language tool.
Lead-in
In addition to providing technology that enables you to create XML Web services, the .NET Framework provides a sophisticated set of tools and code to consume XML Web services.
!
To Consume a Web Service, Use Wsdl.exe #
Identify the object or service URI to be used
http://localhost/MathService/Service1.asmx http://localhost/MathService/Service1.asmx #
Run Wsdl.exe on the service’s URI
wsdl wsdl http://localhost/MathService/Service1.asmx?wsdl http://localhost/MathService/Service1.asmx?wsdl #
Write the client code that calls the remote object
Service1 Service1 salesTaxService salesTaxService == new new Service1(); Service1(); float float totalAmount totalAmount == salesTaxService.SalesTax( salesTaxService.SalesTax( amount,taxRate); amount,taxRate); #
Build the client and proxy code
csc csc salestaxclient.cs salestaxclient.cs service1.cs service1.cs
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In addition to providing technology that enables you to create XML Web services, the .NET Framework provides a sophisticated set of tools and code to consume XML Web services. Consuming an XML Web service means accessing it as a client. Because XML Web services are based on open protocols, such as SOAP and HTTP, this client technology can also be used to consume XML Web services that are not built by using the .NET Framework.
Using the Web Services Description Language Tool The .NET Framework SDK provides a tool that is called the Web Services Description Language tool (Wsdl.exe). You can use this tool to download the WSDL description of an XML Web service and create a proxy class that addresses this service. For XML Web services that are created by using ASP.NET, the proxy class is similar to the class that is defined in the .asmx file. However, the proxy class contains only methods with the [WebMethod] custom attribute. An application that uses an XML Web service creates an instance of the service’s proxy class and invokes the service’s methods as it would with any local object. You compile and build your code with this proxy class included. Alternatively, you can use features of the Visual Studio environment to build applications that consume XML Web services. These Visual Studio features are beyond the scope of this course.
44
Module 13: Remoting and XML Web Services
The following process shows how to create a simple application that uses the XML Web service that is created in the Using Visual Studio to Create an XML Web service demonstration in this module. Delivery Tip
The use of the Web Services Description Language tool (Wsdl.exe) is covered in detail because the students will need this information to do Lab 13.2, Using an XML Web service. If you feel that students may benefit from a full demonstration, you can demonstrate the steps here.
! To use the Service1 Web Service 1. Identify the object and the service that should be used. The Salestaxclient application will use the SalesTax method of the Service1 service whose URL is: http://localhost/MathService/Service1.asmx
Note Typically a client uses an XML Web service that is located on a remote computer. If Service1 were located in a MathService virtual directory on a Web server named foo, the URL for the service would be: http://foo/MathService/Service1.asmx
2. Run the WSDL tool, Wsdl.exe, and point it at the URL where the server object is located. You can request that the Web Service Utility retrieves the schema and, from it, generates a source file that contains a proxy for the service’s class. To generate a proxy for the Service1 class in a file named Service1.cs, type the following command in a Visual Studio .NET Command Prompt window: wsdl http://localhost/MathService/service1.asmx?wsdl
Module 13: Remoting and XML Web Services
45
The proxy, Service1.cs, contains code similar to the following: //... using System.Diagnostics; using System.Xml.Serialization; using System; using System.Web.Services.Protocols; using System.ComponentModel; using System.Web.Services; //... [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Web.Services.WebServiceBindingAttribute! (Name="Service1Soap", Namespace="http://tempuri.org/")] public class Service1 : System.Web.Services.Protocols.SoapHttpClientProtocol { //... public Service1() { this.Url = "http://localhost/mathservice/service1.asmx"; } //... [System.Web.Services.Protocols.SoapDocumentMethodAttribute ! ("http://tempuri.org/SalesTax", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle= System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] public System.Single SalesTax(System.Single salesAmount, System.Single fractionalTaxRate) { object[] results = this.Invoke("SalesTax", new object[] {salesAmount, fractionalTaxRate}); return ((System.Single)(results[0])); } //...
46
Module 13: Remoting and XML Web Services
3. Write the client code that calls the remote object. Create a text file named Salestaxclient.cs in the folder \DemoCode\Mod13\ Demo13.2 containing the following code: using System; public class Class1 { public Class1() {} public static int Main(string[] args) { Console.WriteLine("Enter Sales Amount:"); float amount = Single.Parse(Console.ReadLine()); Console.WriteLine( "Enter Fractional Sales Tax Rate:"); float taxRate = Single.Parse(Console.ReadLine()); Service1 salesTaxService = new Service1(); float totalAmount = salesTaxService.SalesTax(amount,taxRate); Console.WriteLine( "Total: {0}",totalAmount.ToString()); return 0; } }
4. Build the client executable file by using the client code and proxy code. You must include references to the assemblies that are used by the client and proxy code. 5. To build Salestaxclient.exe, type the following command in a Visual Studio .NET Command Prompt window: csc salestaxclient.cs service1.cs
6. Run the client, and enter parameters as prompted. Running Salestaxclient.exe produces output that should be similar to the following: salestaxclient Enter Sales Amount: 100 Enter Fractional Sales Tax Rate: .08 Total: 108
Module 13: Remoting and XML Web Services
47
XML Web Service Discovery Topic Objective
To introduce the Web Service discovery process and the tools that are available for discovery.
Lead-in
Web Service discovery is the process of dynamically locating and interrogating Web Service descriptions, which is a preliminary step for accessing an XML Web service.
!
Discovery – The Process of Locating and Interrogating XML Web Service Descriptions
!
Discovery Services Are Evolving and Changing Rapidly
!
Disco – Microsoft XML Web Services Discovery Tool #
#
!
Disco file is an XML document that links to descriptions of XML Web Services Disco file is currently created and used by Visual Studio
Universal Description, Discovery, and Integration Project #
Open framework for describing services, discovering businesses, and integrating business services that use the Internet
#
Cross-industry support and platform independence
#
For more information, see http://www.uddi.org
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Web Service discovery is the process of dynamically locating and interrogating Web Service descriptions, which is a preliminary step for accessing an XML Web service. The discovery process allows an XML Web service client that may not know the Web Service’s Uniform Resource Identifier to locate an XML Web service, to determine the capabilities of that Web Service, and to properly interact with it. Web Service discovery is currently evolving, and the information that is presented in this topic is subject to change.
Disco – Microsoft Web Services Discovery Tool In the current version of the .NET Framework, you can enable programmatic discovery when an XML Web service publishes a file with a .disco extension. Disco is the Microsoft XML Web services discovery tool. A .disco file is an XML document that contains links to other resources that describe the XML Web service. Currently, when you create an ASP.NET-hosted XML Web service with Visual Studio .NET, .disco files are automatically generated and used. For more information about Disco, see the Visual Studio .NET and .NET Framework SDK documentation.
Universal Description, Discovery, and Integration The Universal Description, Discovery and Integration (UDDI) project is a cross-industry project that is dedicated to creating a platform-independent, open framework for describing Web Services, discovering businesses, and integrating business services that use the Internet. The project is driven by all of the major platform and software providers, marketplace operators, and e-business leaders. For more information about UDDI, see the UDDI Web site at http://www.uddi.org/.
48
Module 13: Remoting and XML Web Services
Lab 13.2: Using an XML Web Service Topic Objective
To introduce the lab.
Lead-in
In this lab, you will access an XML Web service by creating a proxy using the Web Services Description Language tool.
*****************************ILLEGAL FOR NON-TRAINER USE******************************
Objectives After completing this lab, you will be able to: • Access an XML Web service by creating a proxy using the Web Services Description Language tool.
Lab Setup Starter and solution files are associated with this lab. The starter files are in the folder \Labs\Lab13.2\Starter. The solution files for this lab are in the folder \Labs\Lab13.2\Solution.
Module 13: Remoting and XML Web Services
49
Scenario This lab is similar to the scenario used in Lab 13.1, Building an OrderProcessing Application by Using Remoted Servers. That scenario was based on a simple distributed order-processing application, in which a customer specifies a customer ID and an item number for an item that the customer wants to purchase, and the application processes the order. The order processing involves authenticating the customer’s ID and arranging order fulfillment that is to say, having the ordered item sent to the customer. This lab extends Lab 13.1 by having the client application calculate the cost of the ordered item. Your client will use an XML Web service to calculate the item’s cost including sales tax. This scenario simulates the case where you do not have access to the XML Web service’s source code or assembly. You will build a MathService XML Web service to calculate the total cost of an item given the pretax cost and fractional tax rate. You will then use the Web Services Description Language tool (Wsdl.exe) to access and create a .NET proxy class for this XML Web service. Then you will build a client called Testclient2.exe that uses this proxy to access the XML Web service’s Service1.SalesTax method to calculate the cost of the ordered item.
Estimated time to complete this lab: 55 minutes
50
Module 13: Remoting and XML Web Services
Exercise 1 Creating the XML Web Service Proxy In this exercise, you will create the MathService XML Web service and use the Web Services Description Language tool (Wsdl.exe) to create a proxy class to this XML Web service.
! Create the MathService XML Web service using Visual Studio .NET 1. Start Visual Studio, and create a new project. a. In the New Project dialog box, select Visual C# Projects as the type. b. Select ASP.NET Web Service as the template. You may need to scroll down to see ASP.NET Web Service. c. In the Location box, type the name of the Web server, http://localhost/MathService. The grayed out Name box will now contain the text MathService. Then click OK. 2. On the View menu, click Code. Because the XML Web service in this demonstration is simple, you can work on the code directly. For more complex XML Web services, you can use the Service1.asmx.cs Design palette that is displayed when you create a new project of type XML Web service. The Service1.asmx.cs Design palette enables the drag-and-drop operation of rapid application development that can make complex services easier to set up. 3. In the Service1 class, examine the HelloWorld method in the commentedout WEB SERVICE EXAMPLE. 4. In Service1.asmx.cs, implement the SalesTax method by inserting the following code after the comments for the HelloWorld method. You should leave the HelloWorld code commented out. [WebMethod] public float SalesTax( float salesAmount, float fractionalTaxRate) { return salesAmount + (salesAmount * fractionalTaxRate); }
5. On the Build menu, click Build Solution to create the XML Web service. 6. Use Internet Explorer to access the XML Web service at the following URL: http://localhost/MathService/Service1.asmx
Tip To use Internet Explorer to view an XML Web service from within the Visual Studio environment, right-click the .asmx file in the Solution Explorer window, and then click View in Browser.
Module 13: Remoting and XML Web Services
51
7. In the Internet Explorer page that is returned in step 6, click the link for the Service1 operation labeled SalesTax. 8. In the Internet Explorer page that is returned in step 7, type some parameters, and then click Invoke to verify that the correct value is returned and displayed in SOAP/XML format. 9. Use Internet Explorer to access and verify that the XML Web service’s description is obtained from the following URL: http://localhost/MathService/Service1.asmx?wsdl
! Create the XML Web service proxy Important To use Microsoft Visual Studio .NET tools within a command prompt window, the command prompt window must have the proper environment settings. The Visual Studio .NET Command Prompt window provides such an environment. To run a Visual Studio .NET Command Prompt window: on the Start menu, point to All Programs, point to Microsoft Visual Studio .NET, Visual Studio .NET Tools, and then click Visual Studio .NET Command Prompt. • In a Visual Studio .NET Command Prompt window, from the Lab 13.2 Starter directory, create a proxy to the authentication XML Web service named Service1.cs by running the following command: wsdl http://localhost/MathService/service1.asmx?wsdl
52
Module 13: Remoting and XML Web Services
Exercise 2 Creating the Test Client In this exercise, you will create a test client. The client calls the authentication server. If the user is authenticated, then the client calls the order fulfillment server. If the order is fulfilled, then the client calls the MathService XML Web service to calculate the total cost including sales tax. For simplicity, the client will assume that all items cost $100 and that the sales tax rate is always 0.1.
! Create the test client 1. Use the editor of your choice to examine the C# source file that is named Testclient2.cs located in the Lab13.2 Starter directory. 2. Add code to Testclient2.cs after the following command that outputs the results from calling the fulfillment service: Console.WriteLine( "Fulfillment Server Customer 1234 Item Number 5678 Shipped: {0}", shipped.ToString() );
The code should check the status of fulfillment. If shipped is true, then perform the following steps: a. Instantiate a new Service1 object named salesTaxService. b. Call the salesTaxService.SalesTax method, passing 100.0F as its first argument and 0.1F as its second argument. c. Print out to the console the returned total cost value. 3. Build Testclient2.exe in a Visual Studio .NET Command Prompt window as follows: a. Reference Fulfillment.exe, and Authenticate.exe. b. Specify the C# source files, Service1.cs and Testclient2.cs. c. Specify that the output file should be named Testclient2.exe.
Module 13: Remoting and XML Web Services
53
4. Test the order fulfillment system, by performing the following steps: a. In a console window, from the Lab 13.2 Starter directory, run Authenticate.exe. b. In a second console window, from the Lab 13.2 Starter directory, run Fulfillment.exe. c. In a third console window, repeatedly run Testclient2.exe. The console output will vary because the random-number generator values cause authentication and fulfillment to succeed or fail 50 percent of the time. A typical series of test client invocations would produce output similar to the following: >testclient2 Authentication Server Customer 1234 Authorization: True Fulfillment Server Customer 1234 Item Number 5678! Shipped: True Total Cost: 110 >testclient2 Authentication Server Customer 1234 Authorization: True Fulfillment Server Customer 1234 Item Number 5678! Shipped: False
54
Module 13: Remoting and XML Web Services
Review Topic Objective
To reinforce module objectives by reviewing key points.
Lead-in
The review questions cover some of the key concepts taught in the module.
!
Remoting
!
Remoting Configuration Files
!
XML Web Services
*****************************ILLEGAL FOR NON-TRAINER USE****************************** 1. Can two application domains that are on the same computer each have a channel that listens on the same port number? Ports are a machine-wide resource; therefore, on one computer, it is illegal to register multiple channels that listen on the same port number, even if the channels are registered in different application domains.
2. What is the purpose of a proxy? The proxy object acts as a representative of the remote object and ensures that all calls that are made on the proxy are forwarded to the correct remote object instance. All methods that are called on the proxy are automatically forwarded to the remote class, and any results are returned to the client.
3. Can a remotely instantiated object be returned by value? No. All objects that are instantiated remotely are returned by reference.
4. What determines whether a remotely instantiated object’s parameters and return values are passed by reference or by value? Objects whose classes are marked with the SerializableAttribute are marshal-by-value, and objects that inherit from System.MarshalByRefObject are marshal-by-reference.
Module 13: Remoting and XML Web Services
55
5. What file extension is typically used by ASP.NET-hosted XML Web services? ASP.NET provides support for XML Web services with the .asmx file.
6. In ASP.NET, how do you specify that a service is defined in a prebuilt assembly, and where should that assembly’s DLL be located in relation to the ASP.NET application? The .asmx file should contain the single line: <%@ WebService Class="." %> The assembly library DLL should be in the application’s \Bin subdirectory.
7. How can a client invoke an XML Web service that is not implemented by using the .NET Framework or in which the XML Web service’s assembly or source code is not available? The Web Services Description Language tool (Wsdl.exe) can be used to read the WSDL description of an XML Web service and create a proxy class. The client can use the proxy class to invoke the methods of the XML Web service.
56
Module 13: Remoting and XML Web Services
Course Evaluation Topic Objective
To direct students to a Web site to complete a course evaluation.
Lead-in
Between now and the end of the course, you can go to the Web site listed on this page to complete a course evaluation.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Your evaluation of this course will help Microsoft understand the quality of your learning experience. To complete a course evaluation, go to http://www.metricsthatmatter.com/ survey/. Microsoft will keep your evaluation strictly confidential and will use your responses to improve your future learning experience.
THIS PAGE INTENTIONALLY LEFT BLANK
Contents
Module 14 (Optional): Threading and Asynchronous Programming
Overview
1
Introduction to Threading
2
Using Threads in .NET
9
Thread Safety
29
Special Thread Topics
51
Asynchronous Programming in .NET
74
Lab 14: Working With Multithreaded Applications
92
Review
108
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places, and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, places or events is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. 2001-2002 Microsoft Corporation. All rights reserved. Microsoft, ActiveX, BizTalk, IntelliMirror, Jscript, MSDN, MS-DOS, MSN, PowerPoint, Visual Basic, Visual C++, Visual C#, Visual Studio, Win32, Windows, Windows Media, and Window NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
Module 14 (Optional): Threading and Asynchronous Programming
Instructor Notes Module 14 Presentation: 150 Minutes Lab: 60 Minutes
This module provides students with knowledge about the support that the Microsoft® .NET Framework provides for working with multithreaded applications and asynchronous programming. After completing this module, students will be able to: !
Create and manage threads.
!
Create thread-safe code.
!
Create and use timers.
!
Create threads using thread pools.
!
Create managed threads that interact well with COM components.
!
Create Microsoft Windows® Forms applications with background threads.
!
Make asynchronous calls using delegates.
Materials and Preparation This section provides the materials and preparation tasks that you need to teach this module.
Required Materials To teach this module, you need the Microsoft PowerPoint® file 2349B_14.ppt.
Preparation Tasks To prepare for this module: !
Read all of the materials for this module.
!
Practice the demonstrations.
!
Complete the lab.
iii
iv
Module 14 (Optional): Threading and Asynchronous Programming
Demonstrations This section provides demonstration procedures that will not fit in the margin notes or are not appropriate for the student notes. The code for each of the following demonstrations is contained in project folders that are located in \Democode\Mod14. Use the debugger to step through the code while you point out features and ask students what they think will happen next.
Managing Threads In this demonstration, you will show students how to create and manage threads in the .NET Framework by using some of the classes and methods that were covered in Using Threads in .NET. The code for this demonstration is contained in one project and is located in \Democode\Mod14\Demo14.1.
Interrupt and Abort In this demonstration, you will show students how to terminate threads by using the Thread.Interrupt and Thread.Abort methods. The code for this demonstration is contained in one project and is located in \Democode\Mod14\Demo14.2.
Using Synchronization Contexts and Synchronized Code Regions to Provide Thread Safety In this demonstration, you will use a series of tests to show students how to achieve thread safety through the use of synchronization contexts and synchronized code regions. The code for this demonstration is contained in one project and is located in \Democode\Mod14\Demo14.3.
Using Synchronization Techniques and Thread Pooling In this demonstration, you will show students how to perform synchronization by using the C# lock keyword and manual synchronization primitives, and also how to obtain multiple threads by using a thread pool. The code for this demonstration is contained in one project and is located in \Democode\Mod14\Demo14.4.
Module 14 (Optional): Threading and Asynchronous Programming
Windows Forms Threading In this demonstration, you will show students how to use a background thread in a Windows Forms application. The code for this demonstration is contained in one project and is located in \Democode\Mod14\Demo14.5.
Asynchronous File Stream Read In this demonstration, you will show students how to use synchronous and asynchronous read methods on a file stream. For the asynchronous case, you will show four different ways to complete the operation: callback, poll, end method call, and wait with timeout. The code for this demonstration is contained in one project and is located in \Democode\Mod14\Demo14.6.
Using a Delegate In this demonstration, you will show students how to use a delegate object to make asynchronous calls. The code for this demonstration is contained in one project and is located in \Democode\Mod14\Demo14.7.
Multimedia Presentation This section lists the multimedia items that are part of this module. Instructions for launching and playing the multimedia are included with the relevant slides.
Asynchronous Programming This animation illustrates the .NET Framework common language runtime support for asynchronous programming using Delegate objects.
v
vi
Module 14 (Optional): Threading and Asynchronous Programming
Module Strategy Use the following strategy to present this module: !
Introduction to Threading Provide a general introduction to the concept of threads and discuss advantages and disadvantages of using them. As a simple illustration of the concept of threading, use Microsoft Internet Explorer to show how you can still do work while waiting for a download operation to complete. Provide a high level overview of the System.Threading namespace and introduce the asynchronous design pattern, which you will cover in more detail in Asynchronous Programming in .NET. Explain how application domains play a key role in .NET threading architecture.
!
Using Threads in .NET Focus on the classes in the System.Threading namespace that are used to start, manage, and terminate threads. In addition to the preceding operations, discuss the role of managed thread local storage.
!
Thread Safety Focus on the issues that students may encounter in multithreaded programming from sharing data and resources between threads, as a result of thread synchronization. Introduce strategies that the .NET Framework provides for dealing with synchronization, in particular classes and interfaces in System.Threading.
!
Special Thread Topics Introduce the Thread.Timer class, which provides a mechanism for executing methods at specified intervals. Explain how a TimerCallback delegate is used in conjunction with a Timer. Discuss the use of thread pools in making multiple threads operate more efficiently. Discuss how managed threads call into a COM object. Outline best practices for implementing thread-safe code.
Module 14 (Optional): Threading and Asynchronous Programming !
vii
Asynchronous Programming in .NET Provide a brief definition of the concept of asynchronous programming as the ability to issue method calls to other components, and to carry on with other work without waiting for the operation to complete. Introduce the asynchronous design pattern, and emphasize that one of its innovations is that the caller can decide whether a particular call should be asynchronous. Spend most of the time in this section on the Asynchronous File Stream Read Example. In addition to the code on the slides and information in the Student Notes, there is an accompanying demonstration. Discuss the use of Asynchronous delegates to call a synchronous method in an asynchronous manner. Play the Asynchronous Programming animation to illustrate the .NET Framework common language runtime support for asynchronous programming using Delegate objects.
Module 14 (Optional): Threading and Asynchronous Programming
1
Overview Topic Objective
To provide an overview of the module topics and objectives.
Lead-in
In this module, you will learn about the support that the .NET Framework provides for working with multithreaded applications and asynchronous programming.
!
Introduction to Threading
!
Using Threads in .NET
!
Thread Safety
!
Special Thread Topics
!
Asynchronous Programming in .NET
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this module, you will learn about the support that the Microsoft® .NET Framework provides for working with multithreaded applications and asynchronous programming. The common language runtime abstracts much of the threading support into classes that greatly simplify most threading tasks. Even if you do not create your own threads explicitly, you need to understand how your code should handle multiple threads if it is run in a multithreaded environment. You will also learn how to handle thread synchronization to maintain application responsiveness and avoid potential data corruption and other problems. In the .NET Framework, asynchronous programming is a feature that is supported by Remoting, Networking: HTTP, TCP, File I/O, ASP.NET, and Microsoft Message Queue Server (MSMQ). Because asynchronous programming is a core concept, the .NET Framework provides a common design pattern for handling asynchronous execution. This module introduces the .NET Framework asynchronous Design Pattern and gives examples of its use. After completing this module, you will be able to: !
Create and manage threads.
!
Create thread-safe code.
!
Create and use timers.
!
Create threads using thread pools.
!
Create managed threads that interact well with COM components.
!
Create Microsoft Windows® Forms applications with background threads.
!
Make asynchronous calls using delegates.
2
Module 14 (Optional): Threading and Asynchronous Programming
" Introduction to Threading Topic Objective
To provide an overview of the topics that you will cover in this section.
Lead-in
In this section, you will learn about the advantages and disadvantages of using threads and the support for threads that is provided by the .NET Framework.
!
Overview of Threads
!
Overview of Support for Threading in .NET
!
Thread Architecture in .NET
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this section, you will learn about the advantages and disadvantages of using threads and the support for threads that is provided by the .NET Framework.
Module 14 (Optional): Threading and Asynchronous Programming
3
Overview of Threads Topic Objective
To define threads in the context of general multitasking and state the primary advantages and disadvantages of using threads.
!
!
Threads
!
The basic unit to which an operating system allocates processor time
#
Enable multiple activities to appear to occur simultaneously
Advantages of using multiple threads
Lead-in
Whether you are developing for computers with one processor or several, you want your application to provide the most responsive interaction with the user, even if the application is currently doing other work.
#
#
Application does background processing while keeping the UI responsive
#
Distinguish tasks of varying priority
#
Communicate over a network, to a Web server, and to a database
Potential disadvantages of using threads #
#
Diminished performance due to increased operating system overhead, for example, thread context switching Controlling code execution with many threads is complex, and can be a source of many difficult to find and fix bugs
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Whether you are developing for computers with one processor or several, you want your application to provide the most responsive interaction with the user, even if the application is currently doing other work. Using multiple threads of execution is one of the most powerful ways to keep your application responsive to the user and at the same time make use of the processor in between or even during user events.
Threads Delivery Tip
You can quickly illustrate how multiple threads are used to create nonblocking UI by running the Microsoft Internet Explorer Web browser. Start the browser and type a URL address or click a link to a site that takes some time to download. Show how you can still interact with the application during this download operation, for example, canceling the current download by clicking Stop.
Threads are the basic unit to which an operating system allocates processor time, and more than one thread can execute code inside a process. Each thread maintains exception handlers, a scheduling priority, and a set of structures that the system uses to save the thread context until it is scheduled. The thread context includes all of the information that the thread needs to smoothly resume execution, including the thread’s set of CPU registers and stack, in the address space of the thread’s host process. Operating systems use processes to separate the different applications that they are executing. The .NET Framework further subdivides an operating system process into lightweight, managed subprocesses, called application domains, represented by System.AppDomain. One or more managed threads, represented by System.Threading.Thread, can run in one or any number of application domains within the same process. A preemptive multitasking operating system allocates a processor time slice to each thread that it executes. Because each time slice is small, multiple threads appear to execute at the same time.
4
Module 14 (Optional): Threading and Asynchronous Programming
Advantages of using multiple threads Threads enable multiple activities to appear to occur simultaneously in an application. For example, a user can edit a worksheet while another thread recalculates other parts of the worksheet within the same application. Threading maintains the responsiveness of the user interface while background processing is occurring. Threads can be used to enable users to receive notification of a task’s progress, and even to cancel the task at any time. You can also use threads to distinguish tasks of varying priority. For example, you can use a high-priority thread to manage time-critical tasks, and a lowpriority thread to perform other tasks. Threads are also useful when an application must wait for an event, such as user input or a read from the network or from a file, before continuing to execute. Multiple threads enable the processor to handle a separate task while waiting for the completion of the event.
Potential disadvantages of using threads In some circumstances, threading may cause application performance to degrade. On a single-processor computer, a compute-bound task, such as calculating a series of values by using multiple threads, would be slower because of the overhead caused by thread-switching. Keeping track of a large number of threads consumes significant processor time. If there are too many threads, most of them will not make significant progress. Controlling code execution with many threads is complex, and can be a source of many bugs. You run the risk of data corruption or other problems, such as deadlocks and race conditions. To protect an application’s data from possible corruption, you must ensure that access to shared data is properly synchronized. For more information about deadlocks and race conditions, see Thread Safety in this module. Note While this module focuses on threading, there are other ways of achieving concurrency that include using multiple processes/AppDomains, messaging, and database stored procedures.
Module 14 (Optional): Threading and Asynchronous Programming
5
Overview of Support for Threading in .NET Topic Objective
To provide a high-level overview of the System.Threading namespace and introduce the asynchronous design pattern.
Lead-in
Before examining how threads work in the .NET Framework, let’s briefly review important ways in which the .NET Framework provides support for threading.
!
Basic thread namespace #
!
System.Threading
Standard design pattern for asynchronous programming #
Hides thread creation and synchronization details
#
Supported by .NET Framework delegate classes and/or the IAsyncResult interface
*****************************ILLEGAL FOR NON-TRAINER USE****************************** To provide support for multithreaded programming, the .NET Framework supplies its own namespace for thread creation and management and a programming model to handle asynchronous operations.
Basic thread namespace The System.Threading namespace provides classes and interfaces that enable multithreaded programming. System.Threading includes classes that are used to create and manage threads, protect shared data, and improve system responsiveness. The System.Threading.Thread class is an abstraction of a managed thread that executes within the runtime. This includes threads that are created by the runtime and those that are created outside the runtime but that interact with the runtime environment to execute some managed code. The System.Threading namespace includes classes that assist with thread synchronization and protection of shared data. For example, the System.Threading.Interlocked class provides atomic operations for variables that are shared by multiple threads. The System.Threading.Monitor class provides a synchronization mechanism to ensure that where multiple threads access a shared resource, only one thread can access the resource at a particular time.
6
Module 14 (Optional): Threading and Asynchronous Programming
Standard design pattern for asynchronous programming The standard asynchronous design pattern in the .NET Framework provides an efficient model to manage asynchronous operations and provide a consistent programming model. The .NET Framework provides support for asynchronous programming using the IAsyncResult interface and delegate classes that enable a programmer to avoid some of the implementation details of threading. The asynchronous design pattern is covered in more detail later in this module. For more information about the System.Threading namespace and its classes, see System.Threading Namespace in the .NET Framework SDK documentation.
Module 14 (Optional): Threading and Asynchronous Programming
7
Thread Architecture in .NET Topic Objective
To explain thread architecture in .NET in terms of application domains.
Example of a Process Hosting AppDomains AppDomain A
AppDomain B
Lead-in
Let’s look at the different architectural components that make up a single .NET process.
Shared SharedData Data
Shared SharedData Data
Thread 1
Thread 2
Thread 3
Thread Thread Specific Specific Data Data
Thread Thread Specific Specific Data Data
Thread Thread Specific Specific Data Data
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Application domains, also known as AppDomains, play a key role in how threads work in the .NET Framework. An application domain is a runtime representation of a logical process within a physical process. A single process can contain multiple application domains, each of which is completely isolated from other application domains within this or any other process. One or more threads run in an application domain. Although hosting threads within application domains is conceptually similar to the COM threading model with its use of apartments, there is an important difference: application domains are managed types whereas the COM threading model is built on an unmanaged architecture. For more information about application domains, see Module 2, “Introduction to a Managed Execution Environment,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET).
8
Module 14 (Optional): Threading and Asynchronous Programming
Mapping from Win32 Threading to Managed Threading The following table maps Microsoft Win32® threading elements to their approximate runtime equivalent. Note that this mapping does not represent identical functionality. For example, TerminateThread does not execute finally clauses or free up resources, and cannot be prevented. However, Thread.Abort executes all of your rollback code, reclaims all of the resources, and can be denied by using ResetAbort. Be sure to read the .NET Framework SDK closely before making assumptions about functionality. In Win32
In the common language runtime
CreateThread
Combination of new Thread() and Thread.Start
TerminateThread
Thread.Abort
SuspendThread
Thread.Suspend
ResumeThread
Thread.Resume
Sleep
Thread.Sleep
WaitForSingleObject on the thread handle
Thread.Join
ExitThread
No equivalent
GetCurrentThread
Thread.CurrentThread
SetThreadPriority
Thread.Priority
No equivalent
Thread.Name
No equivalent
Thread.IsBackground
Close to CoInitializeEx (Ole32.dll)
Thread.ApartmentState
Module 14 (Optional): Threading and Asynchronous Programming
9
" Using Threads in .NET Topic Objective
To provide an overview of the topics that you will cover in this section.
Lead-in
Having briefly reviewed how an operating system uses threads to perform multitasking, let’s look at how threading works in the .NET Framework.
!
Starting Threads
!
Thread Properties and Parameters
!
Managing Threads
!
Thread Local Storage
!
Interrupting and Terminating Threads
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this section you will learn how to start, manage, and terminate threads. The System.Threading namespace includes classes for starting managing, and terminating threads. Being able to perform basic threading operations is only the first requirement in creating multithreaded applications. On a more advanced level, you must consider issues of data protection and performance. These issues are covered in the topic Thread Safety in this module.
10
Module 14 (Optional): Threading and Asynchronous Programming
Starting Threads Topic Objective
To explain how to instantiate and start a new thread by using the .NET Framework classes.
!
Create a new instance of a Thread Object #
Constructor takes a ThreadStart delegate as its only parameter
#
ThreadStart references the method that will be executed by the new thread
Lead-in
Creating a new instance of a Thread class creates a new managed thread. !
Thread is not executed until the Thread.Start method is invoked
Thread Thread tt == new new Thread(new Thread(new ThreadStart(MyClass.AStaticMethod)); ThreadStart(MyClass.AStaticMethod)); t.Start(); t.Start();
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Creating a new instance of a Thread class creates a new managed thread. The constructor for Thread takes, as its only parameter, a ThreadStart delegate that references the method that will be executed by the new thread. The Thread function does not begin executing until the Start method is called.
Module 14 (Optional): Threading and Asynchronous Programming
11
The following code shows how to create and start new threads: using System; using System.Threading; class MyClass { //… public static void AStaticMethod() { //… } public void AnInstanceMethod() { //… } } class App { static void Main() { // Create and start thread on an instance method MyClass aMyClass = new MyClass(); Thread t1 = new Thread(new ThreadStart(aMyClass.AnInstanceMethod)); t1.Start(); // Create and start thread on a static method Thread t2 = new Thread(new ThreadStart(MyClass.AStaticMethod)); t2.Start(); //… } }
In the preceding example, the calls to t1.Start and t2.Start place the t1 and t2 threads in the running state, and the operating system can schedule them for execution. The Start method submits an asynchronous request to the system, and the call returns immediately, possibly before the new thread has started. The thread’s execution begins at the first line of the method that is referred to by the thread delegate. Calling Start more than once on the same thread causes the runtime to throw a ThreadStateException.
12
Module 14 (Optional): Threading and Asynchronous Programming
Thread Properties and Parameters Topic Objective
To describe some key Thread class properties and how to pass parameters to threads.
!
Use the Thread.Name and Thread.Priority properties to get or set the name and priority of the thread
Lead-in
!
Designate a thread as a background or a foreground thread by setting the Thread.IsBackground property
To get or set the name of a thread and its priority, use the Thread.Name and Thread.Priority properties.
#
A background thread will not keep the managed execution environment alive
t.Name t.Name == "My "My Background Background Thread"; Thread"; t.Priority = ThreadPriority.AboveNormal; t.Priority = ThreadPriority.AboveNormal; t.IsBackground t.IsBackground == true; true; !
Encapsulate thread parameters in an object
*****************************ILLEGAL FOR NON-TRAINER USE****************************** To get or set the name of a thread and its priority, use the Thread.Name and Thread.Priority properties. Thread.Priority gets or sets the following values that indicate the scheduling priority of a thread: !
Highest
!
AboveNormal
!
Normal
!
BelowNormal
!
Lowest
For example, to set the name of a thread t to “My Background Thread” and make its priority AboveNormal, you use the following code: t.Name = "My Background Thread"; t.Priority = ThreadPriority.AboveNormal;
Note Because the details of scheduling algorithms vary with each operating system, operating systems are not required to honor the priority of a thread.
Module 14 (Optional): Threading and Asynchronous Programming
13
Background and Foreground Threads A managed thread runs as a background thread or a foreground thread. Background threads are identical to foreground threads except that a background thread does not keep the managed execution environment alive. After all foreground threads have been stopped in a managed process (where the .exe file is a managed assembly), the system stops all background threads and shuts down. You can designate a thread as a background or foreground thread by setting the Thread.IsBackground property. For example, to designate a thread as a background thread, you set Thread.IsBackground to true. Likewise, to designate a thread as foreground thread, set IsBackground to false. All threads that enter the managed execution environment from unmanaged code are marked as background threads. All threads that are generated by creating and starting a new Thread object are foreground threads. If you create a thread that you want to listen for some activity, such as a socket connection, you should set Thread.IsBackground to true, so that your process can terminate. For example, to set a thread t to be a background thread: t.IsBackground = true;
14
Module 14 (Optional): Threading and Asynchronous Programming
Encapsulating thread parameters in an object It is sometimes important to supply parameters to a thread. However, the ThreadStart delegate takes as its only parameter a ThreadStart delegate. The following code demonstrates how to use an object to encapsulate thread parameters: using System; using System.Threading; class MyClassWithThreadState { int sleepTime; public MyClassWithThreadState(int sleepTime) { this.sleepTime = sleepTime; } public void ThreadMethod() { // method can reference sleepTime Thread.Sleep(sleepTime); } } class Class1 { static void Main() { int sleepTime = 1000; // time for the thread to sleep MyClassWithThreadState myClassWithThreadState = new MyClassWithThreadState(sleepTime); Thread ts = new Thread(new ThreadStart(myClassWithThreadState.ThreadMethod)); ts.Start(); // … } }
Module 14 (Optional): Threading and Asynchronous Programming
15
Managing Threads Topic Objective
To describe the classes that the runtime uses to pause, resume, and force threads to wait.
!
!
Lead-in
Let’s look at how the runtime provides classes to manage threads.
Thread.Sleep causes the current thread to block
Thread.Sleep(3000); Thread.Sleep(3000); // // blocks blocks for for 33 seconds seconds Suspend and Resume methods are not generally useful #
!
Can result in serious application problems like deadlocks
Thread.Join waits for another thread to stop
t.Start(); t.Start(); t.Join(); t.Join(); // // Wait Wait for for the the thread thread to to exit exit !
Thread.WaitHandle methods wait for one or more events
WaitHandle.WaitAll(waitEvents); WaitHandle.WaitAll(waitEvents); ! Thread.ThreadState property - bit mask of the thread's state
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Typically, thread management includes tasks such as suspending a thread for a period of time, waiting until another thread completes, or waiting until one or more specific events occur.
Pausing and Resuming Threads After starting a thread, you sometimes need to pause that thread for a fixed period of time. Calling Thread.Sleep causes the current thread to immediately block for the number of milliseconds you pass to Sleep, yielding the remainder of its time slice to another thread. One thread cannot call Sleep on another thread. For example, to cause the current thread to suspend for three seconds, call the static method of Thread.Sleep as follows: Thread.Sleep(3000);
Calling Thread.Sleep(Timeout.Infinite) causes a thread to sleep until it is interrupted by another thread that calls Thread.Interrupt or is aborted by Thread.Abort.
Using Thread.Suspend to pause a thread You can also pause a thread by calling Thread.Suspend. When a thread calls Thread.Suspend on itself, the call blocks until the thread is resumed by another thread. When one thread calls Thread.Suspend on another thread, the call is a nonblocking call that causes the other thread to pause. Calling Thread.Resume breaks another thread out of the suspended state and causes the thread to resume execution, regardless of how many times Thread.Suspend was called. For example, if you call Thread.Suspend five consecutive times and then call Thread.Resume, the thread resumes execution immediately following the call to Resume.
16
Module 14 (Optional): Threading and Asynchronous Programming
Unlike Thread.Sleep, Thread.Suspend does not cause a thread to immediately stop execution. The runtime must wait until the thread has reached a safe point before it can suspend the thread. A thread cannot be suspended if it has not been started or if it has stopped. For more information about safe points, see the .NET Framework SDK. The Suspend and Resume methods are not generally useful for applications, and you should not confuse them with synchronization mechanisms. Because Suspend and Resume do not rely on the cooperation of the thread that is being controlled, they are highly intrusive and can cause serious application problems. For example, if you suspend a thread that holds a resource needed by another thread, this causes a deadlock condition. Some applications do need to control the priority of threads for better performance. To do this, you should use Thread.Priority rather than Thread.Suspend in your application.
Using Thread.Join to pause a thread You can force a thread to wait for another thread to stop by calling Thread.Join, as shown in the following code: using System; using System.Threading; class MyApp {//… static void MyThreadMethod() { //… } static void Main() { // create, start and join a simple background thread // MyThreadMethod is the secondary thread's entry point. Thread t = new Thread(new ThreadStart(MyThreadMethod)); // Start the thread t.Start(); // Wait for the thread to exit t.Join(); } }
Module 14 (Optional): Threading and Asynchronous Programming
17
Using the WaitHandle class to wait for events You can force a thread to wait for one or more events to occur by calling methods in the Thread.WaitHandle class. The following table describes some of the methods in the WaitHandle class. Method
Description
WaitAll
This method waits for all of the elements in the specified array to receive a signal.
WaitAny
This method waits for any of the elements in the specified array to receive a signal.
WaitOne
This method blocks the current thread until the current WaitHandle receives a signal.
The WaitHandle class encapsulates Win32 synchronization handles. While WaitHandle objects represent operating system synchronization objects and expose advanced functionality, they are less portable than the Monitor.Wait method, which is fully managed and, in some circumstances, is more efficient in its use of operating system resources.
Using Classes that are derived from WaitHandle Examples of classes that derive from WaitHandle include AutoResetEvent, ManualResetEvent, and Mutex. You use the AutoResetEvent class to make a thread wait until some event puts it in the signaled state by calling AutoResetEvent.Set. Unlike ManualResetEvent, AutoResetEvent is automatically reset to nonsignaled by the system after a single waiting thread has been released. If no threads are waiting, the event object’s state remains signaled. The AutoResetEvent corresponds to a Win32 CreateEvent call, false specified by the bManualReset argument.
18
Module 14 (Optional): Threading and Asynchronous Programming
For example, the following code shows how to force the main thread to wait until the threads that it has created signal that they have finished executing: using System; using System.Threading; class MyClassWithThreadState { AutoResetEvent done; public MyClassWithThreadState(AutoResetEvent done) { this.done = done; } public void ThreadMethod() { //… done.Set(); // signal that we are finished } } class MyApp { //… static void Main() { //… int numberOfThreads = 3; // number of threads to create AutoResetEvent[] waitEvents = new AutoResetEvent[numberOfThreads]; Thread ts; MyClassWithThreadState myClassWithThreadState; for (int i=0; i
Note You can also use multiple calls to the Thread.Join method to wait for multiple threads to exit. In this module, thread safety describes how to force a thread to wait for access to a synchronized object, such as a Mutex method call. Also in this module, Terminating Threads describes how to interrupt a waiting thread by calling the Thread.Interrupt or Thread.Abort method.
Module 14 (Optional): Threading and Asynchronous Programming
19
Thread Activity States The Thread.ThreadState property provides a bit mask that indicates a thread’s current state. A thread is always in at least one of the possible states in the ThreadState enumeration, and can be in multiple states at the same time. When you create a managed thread, it is in the Unstarted state, and remains in this state until it is moved into the started state by calling Thread.Start. Unmanaged threads that enter the managed environment are already in the started state. When in the started state, some actions can cause the thread to change states. The following table lists the actions that cause a change of state and the corresponding new state. Action
ThreadState
A thread is created within the common language runtime.
Unstarted
A thread calls Start.
Running
The thread starts running.
Running
The thread calls Sleep.
WaitSleepJoin
The thread calls Wait on another object.
WaitSleepJoin
The thread calls Join on another thread.
WaitSleepJoin
Another thread calls Interrupt.
Running
Another thread calls Suspend.
SuspendRequested
The thread responds to a Suspend request.
Suspended
Another thread calls Resume.
Running
Another thread calls Abort.
AbortRequested
The thread responds to an Abort request.
Stopped
A thread is terminated.
Stopped
Because the Running state has a value of 0, you cannot perform a bit test to discover this state. Instead, you can use the following test (in pseudo-code): if ((tState & (Unstarted | Stopped)) == 0)
// implies Running
Threads are often in more than one state at any particular time. For example, if a thread is blocked from a Wait call and another thread calls Abort on that same thread, the thread is in both the WaitSleepJoin and the AbortRequested state at the same time. In that case, as soon as the thread returns from the call to Wait or is interrupted, it receives the ThreadAbortException. After a thread leaves the Unstarted state as the result of a call to Thread.Start, it can never return to the Unstarted state. A thread can never leave the Stopped state, either.
20
Module 14 (Optional): Threading and Asynchronous Programming
Thread Local Storage Topic Objective
To show how to use managed thread local storage.
Lead-in
Let’s discuss how to manage thread local storage.
!
Managed thread local storage - data unique to a thread
!
Thread-Relative Static Fields #
Constructor code runs only on first thread, other thread’s thread static variables are initialized to null
class class AClassWithThreadState AClassWithThreadState {{ [ThreadStatic] [ThreadStatic] public public static static int int count count == 42; 42;
}}
public public void void ThreadMethod() ThreadMethod() {{ count++; count++; }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Managed thread local storage (TLS) provides dynamic data slots that are unique to a thread and an application domain combination. The two types of data slots are named slots and unnamed slots. Named slots can be convenient, because you can use a mnemonic identifier. But other components can intentionally or unintentionally modify a named slot by using the same name for the component’s own thread-relative storage. However, if you do not expose an unnamed data slot to other code, it cannot be used by any other component. To use managed TLS, create your data slot by using Thread.AllocateNamedDataSlot or Thread.AllocateDataSlot, and use the appropriate methods to set or retrieve the information that is placed there.
Module 14 (Optional): Threading and Asynchronous Programming
21
Thread-Relative Static Fields If you know that a field of your type should always be unique to a thread and application domain combination, decorate a static field with the ThreadStaticAttribute attribute. Any class constructor code runs on the first thread in the first context that accesses the field. In all other threads or contexts, the field is initialized to null, or Nothing in Microsoft Visual Basic®. Therefore, you should not rely on class constructors to initialize thread-relative static fields. Instead, you should always assume that thread-relative static fields are initialized to null. For example, the following code shows how to use thread relative static fields to store a thread specific counter: class AClassWithThreadState { // NOTE: Only the first thread instance's field is // initialized, other threads have the field zeroed [ThreadStatic] public static int count = 42; public void ThreadMethod() { count++; // … } //… }
22
Module 14 (Optional): Threading and Asynchronous Programming
Demonstration: Managing Threads Topic Objective
To demonstrate how to create and manage threads in the .NET Framework.
Lead-in
In this demonstration, you will see how to create and manage threads in .NET, by using some of the classes and methods that we have talked about in this section.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Delivery Tip
You should run this demo with breakpoints set in the Main, MyThreadMethod, and MyClassWithThreadState. ThreadMethod methods and observe the Visual Studio .NET Threads window. To make the Visual Studio .NET Threads window visible, when the application is running, on the Debug menu, click Windows, and then click Threads.
This demonstration shows first how to create, manage, and join with a simple background thread. Then a more complex example is shown where parameters and thread static values for a thread instance are encapsulated in a class, MyClassWithThreadState. After starting multiple threads, the main thread calls the WaitHandle.WaitAll method to wait until all those threads indicate that they have finished executing by setting their AutoResetEvent objects.
The code for this demonstration is located in \Democode\ Mod14\Demo14.1.
Module 14 (Optional): Threading and Asynchronous Programming
23
Interrupting and Terminating Threads Topic Objective
To explain how to terminate a thread by using the .NET Framework classes.
!
#
Lead-in
Let’s look at how to terminate a thread in the .NET Framework.
Thread.Interrupt method
!
Wakes a thread from any wait state and causes a ThreadInterruptedException
Thread.Abort method to permanently terminate thread #
Wakes a thread from any wait state and causes a ThreadAbortException
#
ThreadAbortException is a special exception that can be caught by application code, but is rethrown at the end of the catch block unless ResetAbort is called
#
Immediate abort not guaranteed, therefore use Thread.Join to block until thread terminates
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In addition to starting, pausing, and resuming threads, the .NET Framework also provides classes for terminating threads. Thread.Interrupt wakes a thread from any wait state and causes a ThreadInterruptedException to be thrown in the destination thread. Thread.Abort is used to permanently terminate a thread.
Using Thread.Interrupt When you call Thread.Interrupt, you wake a thread from any wait state and cause a ThreadInterruptedException to be thrown in the destination thread. The thread should catch the ThreadInterruptedException and do whatever is appropriate to continue working. If the thread ignores the exception, the runtime catches the exception and stops the thread. For example, you may want to terminate a thread in response to a user-initiated event, such as clicking a Cancel button. The Cancel button’s event handler may run in one thread (thread1), while the event to which the Cancel action applies may run in a separate thread (thread2). In your event handler, you can call the Interrupt method on the second thread, as follows: // inside the Cancel button's event handler if ( thread2 != null) { thread2.Interrupt(); thread2 = null; }
24
Module 14 (Optional): Threading and Asynchronous Programming
Using Thread.Abort To terminate a thread permanently, you use the Thread.Abort method. When you call Abort to terminate a thread, the system wakes the thread from any wait state and throws a ThreadAbortException. The runtime executes the catch block and any finally block. ThreadAbortException is a special exception that can be caught by application code, but is rethrown at the end of the catch block unless ResetAbort is called. Only code with the proper permissions can call ResetAbort method. ResetAbort cancels the request to abort, and prevents the ThreadAbortException from terminating the thread. If the finally blocks contain any unbounded computations, the thread’s termination may be delayed indefinitely. Because the call to Thread.Abort does not always result in the immediate termination of a thread, you can ensure the thread’s destruction by calling the Join method on the thread after you call Abort. Join blocks the calling thread until the thread stops executing. After a thread terminates, you cannot restart it by calling Start again. For example, if code creates a thread, lets it run, and then aborts the thread, any attempt to restart the thread causes the runtime to throw a ThreadStateException.
Blocking Issues If a thread makes an unmanaged call into the operating system, and the system has blocked the thread in unmanaged code, the runtime will not take control of the blocked thread for Thread.Interrupt or Thread.Abort. In the case of Thread.Abort, the runtime marks the thread for Abort and takes control of the thread when it re-enters managed code. Where possible, you should use managed blocking rather than unmanaged blocking. The following methods are all responsive to Thread.Interrupt and Thread.Abort: !
WaitHandle
!
WaitOne
!
WaitAny
!
WaitAll
!
Monitor.Enter
!
Monitor.Block
!
Thread.Join
!
GC.WaitForPendingFinalizers
Also, if a thread is in a single-threaded apartment, all of these managed blocking operations will correctly pump messages in the apartment while the thread is blocked.
Module 14 (Optional): Threading and Asynchronous Programming
25
Demonstration: Interrupt and Abort Topic Objective
To demonstrate how to terminate threads by using the Thread.Interrupt and Thread.Abort methods.
Lead-in
In this demonstration, you will see how to use the Thread.Interrupt and Thread.Abort methods to terminate threads.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This demonstration shows how to use Thread.Interrupt and Thread.Abort to terminate threads. Pay careful attention to what happens in the catch and finally blocks when the Interrupt and Abort methods are called. Notice whether the thread resumes working after the method calls as indicated by the output string “Thread - still alive and working.”
26
Module 14 (Optional): Threading and Asynchronous Programming
The following code is used in the demonstration: using System; using System.Threading; public class ThreadWork { static public bool resetAbort; public static void DoWork() { try { for(int i=0; i<100; i++) { Console.WriteLine( "Thread - working."); Thread.Sleep(100); } } catch(ThreadInterruptedException e) { Console.WriteLine( "Thread - caught ThreadInterruptedException"); Console.WriteLine( "Exception message: {0}", e.Message); } catch(ThreadAbortException e) { Console.WriteLine( "Thread - caught ThreadAbortException"); Console.WriteLine( "Exception message: {0}", e.Message); if (resetAbort == true) Thread.ResetAbort(); } finally { Console.WriteLine("Thread - finally block"); } Console.WriteLine( "Thread - still alive and working."); Thread.Sleep(1000); Console.WriteLine( "Thread - finished working."); } }
(Code continued on the following page.)
Module 14 (Optional): Threading and Asynchronous Programming class ThreadAbortTest { public static void Main() { ThreadStart myThreadDelegate = new ThreadStart(ThreadWork.DoWork); Thread myThread; Console.WriteLine( "Main - interrupting my thread."); myThread = new Thread(myThreadDelegate); myThread.Start(); Thread.Sleep(100); myThread.Interrupt(); Thread.Sleep(2000); ThreadWork.resetAbort = true; Console.WriteLine( "Main - aborting my thread with resetAbort: {0}.", ThreadWork.resetAbort); myThread = new Thread(myThreadDelegate); myThread.Start(); Thread.Sleep(100); myThread.Abort(); Thread.Sleep(2000); ThreadWork.resetAbort = false; Console.WriteLine( "Main - aborting my thread with resetAbort: {0}.", ThreadWork.resetAbort); myThread = new Thread(myThreadDelegate); myThread.Start(); Thread.Sleep(100); myThread.Abort(); myThread.Join(); Console.WriteLine("Main ending."); } }
27
28
Module 14 (Optional): Threading and Asynchronous Programming
The output should be similar to the following: Main - interrupting my thread. Thread - working. Thread - caught ThreadInterruptedException Exception message: ! Thread has been interrupted from a waiting state. Thread - finally block Thread - still alive and working. Thread - finished working. Main - aborting my thread with resetAbort: True. Thread - working. Thread - caught ThreadAbortException Exception message: Thread was being aborted. Thread - finally block Thread - still alive and working. Thread - finished working. Main - aborting my thread with resetAbort: False. Thread - working. Thread - caught ThreadAbortException Exception message: Thread was being aborted. Thread - finally block Main ending.
Module 14 (Optional): Threading and Asynchronous Programming
29
" Thread Safety Topic Objective
To provide an overview of the topics that you will cover in this section.
Lead-in
In this section, we’ll look at the importance of thread safety and the mechanisms that the .NET Framework supports to provide thread safety.
!
Overview of Thread Safety
!
Synchronization Context
!
Synchronized Code Regions
!
Manual Synchronization
!
Thread Safety and the .Net Framework Classes
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The System.Threading namespace provides classes and interfaces that enable multithreaded programming. This namespace includes classes for synchronizing access to data to provide thread safety. This section focuses on the issues that you may encounter from sharing data and resources between threads in multithreaded programming. The section also introduces .NET Framework synchronization strategies.
30
Module 14 (Optional): Threading and Asynchronous Programming
Overview of Thread Safety Topic Objective
To discuss problems that result from sharing data and resources between threads, and to introduce .NET Framework synchronization strategies.
!
Common problems sharing data and resources between threads: # #
!
Lead-in
Let’s talk about how sharing data and resources between threads can lead to compromised thread safety.
Deadlocks – threads waiting for each other so that they cannot proceed
Best approach is to avoid sharing when possible #
!
Race condition – uncontrolled order of execution causing errors
Encapsulate data into instances that are not shared across requests
The .NET Framework provides three strategies to synchronize access to instance and static methods and instance fields: #
Synchronized contexts
#
Synchronized code regions
#
Manual synchronization
*****************************ILLEGAL FOR NON-TRAINER USE****************************** When writing multithreaded applications, you may encounter common problems, such as race conditions and deadlock.
Race condition Two or more threads that simultaneously access the same data can cause undesirable and unpredictable results. For example, one thread may update the contents of a structure while another thread reads the contents of the same structure. In this scenario, it is unknown what data the reading thread will receive: the old data, the newly written data, or possibly a mixture of both. When the proper operation of a program depends on the uncontrolled order of execution of multiple threads, then a race condition exists.
Deadlock A multithreaded application can also encounter problems with thread synchronization if multiple threads are waiting for each other to release resources. This blocking of thread execution is known as a deadlock. For example, consider two threads that transfer money between accounts A and B. The first thread, Thread 1, is coded to do the following tasks in the following order: !
Wait for and acquire a lock on account A
!
Wait for and acquire a lock on account B
!
Transfer funds
!
Release both locks
The second thread, Thread 2, is coded in the same way, except that it first acquires a lock on account B, then on account A.
Module 14 (Optional): Threading and Asynchronous Programming
31
Consider the case where Thread 1 acquires lock A, but before it can acquire lock B, Thread 2 runs and acquires lock B. In this case, both threads block while waiting for the other lock. Because both threads are blocked, neither thread releases the lock that is required by the other thread to proceed. This condition is referred to as a deadlock situation. You can best achieve thread safety by not sharing data or resources between threads. If possible, use a design pattern that encapsulates data and resources into instances that are not shared across requests. When this is not possible you should use the .NET Framework thread synchronization classes to provide thread safety.
.NET Framework strategies for synchronization When you need to share data, the .NET Framework provides three strategies to synchronize access to instance methods, static methods, and instance fields: !
Synchronized contexts You can use the SynchronizationAttribute attribute to enable simple, automatic synchronization for ContextBoundObject objects.
!
Synchronized code regions You can use the MethodImplAttribute class (passing MethodImplOptions.Synchronized), the Monitor class, or compiler support to synchronize only the code blocks that need it.
!
Manual synchronization You can use the various synchronization objects to create your own synchronization mechanisms.
The runtime provides a thread model in which classes fall into a number of categories that can be synchronized in a variety of different ways, depending on the requirements.
No Synchronization No synchronization support is the default for objects. Any thread can access any method or field at any time.
Synchronized Context You can use the SynchronizationAttribute on any class that is derived from ContextBoundObject to synchronize all instance methods and fields. All objects in the same context domain share the same lock. Multiple threads are allowed to access the instance methods and fields, but only a single thread is allowed at any one time. Static members are not protected from concurrent access by multiple threads. The following table shows the support provided for fields and methods under the synchronization context category. Synchronization context supports
Synchronization context does not support
Instance fields and instance methods
Global fields, static fields, and static methods Specific code blocks
32
Module 14 (Optional): Threading and Asynchronous Programming
Synchronized Code Regions You can use the Monitor class or a compiler keyword to synchronize blocks of code, instance methods, and static methods. In Microsoft Visual C#™, the keyword is lock. You can also decorate a method with a MethodImplAttribute (passing MethodImplOptions.Synchronized), which has a similar result to using Monitor or one of the compiler keywords for a Monitor code block where the code block is the entire method. The following table shows the support provided for fields and methods under the synchronized code regions category. Synchronized code regions supports (only if marked)
Synchronized code regions does not support
Static methods, instance methods, and specific code blocks
Global fields, static fields, and instance fields
Manual Synchronization You can use the Interlocked, Mutex, ManualResetEvent, AutoResetEvent, and ReaderWriterLock classes to acquire and release a lock to protect global, static, and instance fields and global, static, and instance methods.
Module 14 (Optional): Threading and Asynchronous Programming
33
Synchronization Context Topic Objective
To describe how to synchronize access to instance fields and methods by using synchronization contexts.
!
Lead-in
Let’s look at using synchronization contexts in more detail.
SynchronizationAttribute enables simple, automatic synchronization for ContextBoundObject objects #
Only instance fields and methods are synchronized
#
Static fields and methods are not protected from concurrent access by multiple threads
[Synchronization()] [Synchronization()] class class CounterSynchronizedContext CounterSynchronizedContext :: ContextBoundObject ContextBoundObject {{ static static int int sCount sCount == 0; 0; //multiple //multiple threads threads can can access access int int iCount iCount == 0; 0; //only //only one one thread thread can can access access at at aa time time public public void void Increment() Increment() {{ //… //… only only one one thread thread can can access access at at aa time time }}
}}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** A context is an ordered sequence of properties that defines an environment for the objects within it. Contexts are created during the activation process for objects that are configured to require certain automatic services such as synchronization. Multiple objects can reside inside a context.
Using the SynchronizationAttribute attribute You can use the SynchronizationAttribute attribute to enable simple, automatic synchronization for objects whose class derives from ContextBoundObject. The attribute forces a synchronization domain for the current context and all contexts that share the same instance. When you apply this attribute to an object, only one thread can be executing in all contexts that share an instance of this property. Multiple threads may access the methods and fields, but only a single thread is allowed at any one time. Allowing only a single thread at a time to access instance fields and methods may make these fields and methods thread-safe. Caution A synchronization context does not protect static fields and methods from concurrent access by multiple threads.
34
Module 14 (Optional): Threading and Asynchronous Programming
For example, consider the CounterSynchronizedContext class. This class’s instanceCount field and Increment method are protected from concurrent access by multiple threads; only one thread at any one time can gain access to these members. However, multiple threads may access its staticCount member, as shown in the following code: using System; using System.Threading; using System.Runtime.Remoting.Contexts; [Synchronization()] class CounterSynchronizedContext : ContextBoundObject { // Caution! multiple concurrent threads allowed for // static field – can use manual synchronization to protect static int staticCount = 0; // only one thread at a time allowed for instance field int instanceCount = 0; // only one thread at a time can execute instance method public void Increment() { Console.WriteLine( "Start Object:{0} Thread:{1}! Resource writing count, static:{2} instance:{3}", this.GetHashCode(), Thread.CurrentThread.GetHashCode(), staticCount, instanceCount); int tempStaticCount = staticCount; int tempInstanceCount = instanceCount; Thread.Sleep(50); tempStaticCount++; tempInstanceCount++; staticCount = tempStaticCount; instanceCount = tempInstanceCount; Console.WriteLine( "Stop Object:{0} Thread:{1}! Resource writing count, static:{2} instance:{3}", this.GetHashCode(), Thread.CurrentThread.GetHashCode(), staticCount, instanceCount); } }
Module 14 (Optional): Threading and Asynchronous Programming
Note There are two classes named SynchronizationAttribute: one in the System.EnterpriseServices namespace, and the other in the System.Runtime.Remoting.Contexts namespace. The System.EnterpriseServices.SynchronizationAttribute class supports only synchronous calls, and can be used only with serviced components. (For more information about serviced components, see Serviced Component Overview in the .NET Framework SDK documentation.) The System.Runtime.Remoting.Contexts.SynchronizationAttribute supports both synchronous and asynchronous calls, and can be used only with context bound objects. This topic discusses only the System.Runtime.Remoting.Contexts.SynchronizationAttribute.
35
36
Module 14 (Optional): Threading and Asynchronous Programming
Synchronized Code Regions Topic Objective
To explain and contrast different options for synchronizing blocks of code.
!
[MethodImplAttribute(MethodImplOptions.Synchronized)] [MethodImplAttribute(MethodImplOptions.Synchronized)]
Lead-in
In addition to synchronizing methods and fields, we also have several options for synchronizing blocks of code: MethodImplAttribute, the Monitor class, and the lock keyword.
MethodImplAttribute with MethodImplOptions.Synchronized
!
#
Method executed by only one thread at a time
#
Can be used to synchronize instance and static methods
#
Similar to using Monitor or Visual C# lock on the entire method
Monitor - synchronize access to a region of code #
Code region can be a portion of, or the entire method
#
Monitor.Enter, Monitor.TryEnter, and Monitor.Exit
#
C# lock keyword uses Monitor class methods lock(this) to protect instance, lock(typeof(class)) to protect static lock lock (typeof(Counter)) (typeof(Counter)) {{ //block //block of of protected protected code code }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** You can decorate a method with a MethodImplAttribute, passing MethodImplOptions.Synchronized, which indicates that only one thread at a time may execute the method. You can use this attribute to synchronize both instance methods and static methods. Using this attribute provides the same result essentially as using Monitor or the Visual C# lock keyword where the synchronized code region encompasses the entire method.
Module 14 (Optional): Threading and Asynchronous Programming
37
[MethodImplAttribute(MethodImplOptions.Synchronized)] In the following example, the class CounterSynchronizedCodeRegion has both its instance method IncrementInstance and static method IncrementStatic attributed for synchronized access. This allows only one thread to execute either method at one time. using System; using System.Threading; using System.Runtime.CompilerServices; class ASynchronizedClassExample { static int staticValue = 0; int instanceValue = 0; [MethodImplAttribute(MethodImplOptions.Synchronized)] public void ChangeInstance() { // thread safe changes to instanceValue } [MethodImplAttribute(MethodImplOptions.Synchronized)] public static void ChangeStatic() { // thread safe changes to staticValue } } //…
Note You can use Thread.Interrupt to break a thread out of blocking operations, such as waiting for access to a synchronized region of code.
Monitor Class and the C# Compiler lock Keyword Using the Monitor class or Visual C# lock keyword is a little more complex than using MethodImplAttribute, but these approaches give you the flexibility to synchronize access to a specific region of code and not just to the entire method. By synchronizing the least amount of code required for thread safety, you maximize the amount of possible concurrency. The Monitor class supports the following code-block synchronization: !
Synchronized instance methods On instance methods, the current object is used for synchronization. Visual C# uses the this keyword to indicate the current object, while Visual Basic .NET use the Me keyword.
!
Synchronized static methods On static methods, the class is used for synchronization.
The Monitor class provides methods that you can use to synchronize access to a specific region of code by taking and releasing a lock on a particular object. After you obtain a lock on a code region, you can use the Monitor.Wait, Monitor.Pulse, and Monitor.PulseAll methods.
38
Module 14 (Optional): Threading and Asynchronous Programming
Monitor.Wait The Monitor.Wait method releases the lock on an object and blocks the current thread until it reacquires the lock. The thread that currently owns the lock on the specified object invokes this method to release the object so that another thread can access it. The caller is blocked while waiting to reacquire the lock. This method is called when the calling thread is waiting for a change in the state of the object that will occur as a result of another thread’s operations on the object. When a thread calls Wait, it releases the lock on the object and enters the object’s waiting queue. The next thread in the object’s ready queue, if there is one, acquires the lock and has exclusive use of the object. All threads that call Wait remain in the waiting queue until they receive a signal from Pulse or PulseAll, sent by the owner of the lock. If Pulse is sent, only the thread at the head of the waiting queue is affected. If PulseAll is sent, all threads that are waiting for the object are affected. When the signal is received, one or more threads leave the waiting queue and enter the ready queue. A thread in the ready queue is permitted to reacquire the lock. This method returns when the calling thread reacquires the lock on the object. Note that this method blocks indefinitely if the holder of the lock does not call Pulse or PulseAll. Caution You should check whether the Pulse method is ever called before its corresponding Wait method is called. A thread must have already issued a Wait for the Pulse to affect it. You can use the Monitor.Wait methods with a timeout parameter to prevent the caller of the Wait from blocking indefinitely when such a condition occurs.
Monitor.Pulse The Monitor.Pulse method notifies a thread in the waiting queue of a change in the locked object's state. Only the current owner of the lock can signal a waiting object by using Pulse. The thread that currently owns the lock on the specified object invokes this method to signal the next thread in line for the lock. Upon receiving the pulse, the waiting thread is moved to the ready queue. When the thread that invoked Pulse releases the lock, the next thread in the ready queue, which is not necessarily the thread that was pulsed, acquires the lock. Note Be aware of the distinction between the use of Monitor and WaitHandle objects. Monitor objects are purely managed, fully portable, and may be more efficient in terms of operating system resource requirements. WaitHandle objects represent operating system waitable objects, are useful for synchronizing between managed and unmanaged code, and expose some advanced operating system features like the ability to wait on many objects at once.
Module 14 (Optional): Threading and Asynchronous Programming
39
Compiler Support Both Visual Basic and Visual C# support a language keyword that uses Monitor.Enter and Monitor.Exit to lock the object. Visual Basic supports the SyncLock keyword; Visual C# supports the lock keyword. When you use the lock or SyncLock keyword, the compiler generates code. The Visual C# and Visual Basic compilers emit a try/finally block with Monitor.Enter at the beginning of the try, and Monitor.Exit in the finally block. If an exception is thrown inside of the lock or SyncLock block, the finally handler runs to allow you to perform any clean-up work. The C# statement, of the form lock(x) where x is an expression of a referencetype, is equivalent to the following except that x is only evaluated once: System.Threading.Monitor.Enter(x); try { ... } finally { System.Threading.Monitor.Exit(x); }
The lock keyword marks a statement block as a critical section. lock(expression) statement_block, where expression specifies the object that you want to lock on. expression must be a reference type. Typically, expression is this if you want to protect an instance variable, or typeof(class) if you want to protect a static variable or if the critical section occurs in a static method in the specified class. The statement_block includes the statements of the critical section.
40
Module 14 (Optional): Threading and Asynchronous Programming
For example, to synchronize access in a static method: using System; using System.Threading; class Cache { public static void Add(object x) { // method code that doesn't require exclusive access lock (typeof(Cache)) { // code requiring exclusive access to static data } // method code that doesn't require exclusive access } public static void Remove(object x) { // method code that doesn't require exclusive access lock (typeof(Cache)) { // code requiring exclusive access to static data } // method code that doesn't require exclusive access } }
For example, to synchronize access in an instance method: class Counter { public override int Read(int threadNum) { // ... // method code that doesn't require exclusive access lock(this) { // code requiring exclusive access to instance data } // method code that doesn't require exclusive access } //… }
Module 14 (Optional): Threading and Asynchronous Programming
41
Demonstration: Using Synchronization Contexts and Synchronized Code Regions to Provide Thread Safety Topic Objective
To demonstrate the use of synchronization contexts and synchronized code regions to provide thread safety.
Lead-in
In this demonstration, you will see how to achieve thread safety by using synchronization contexts and synchronized code regions.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This demonstration shows the use of synchronization contexts and synchronized code regions to provide thread safety. The demonstration consists of several test sequences, which are described in this topic. The code for this demonstration is located in \Democode\ Mod14\Demo14.3.
The Unsafe test The Unsafe test shows the race condition that occurs when multiple threads concurrently execute a method on a class that is not thread-safe. The CounterUnsafe class has a method that is named Increment, which reads an object’s instance variable that is named count, increments its value, and stores the value back in count. The class and its method do not synchronize access. Therefore, the condition can arise where one thread executing the method has read the value of count but before it can increment it and store it back in count, another concurrently executing thread executing the same method reads the same starting value as the first thread.
42
Module 14 (Optional): Threading and Asynchronous Programming
The Unsafe test should result in output that is similar to the following example. Notice that the Increment method has been called three times but the count has only been incremented by one. Starting Unsafe Test Start Resource writing count: 0 Start Resource writing count: 0 Start Resource writing count: 0 Stop Resource writing count: 1 Stop Resource writing count: 1 Stop Resource writing count: 1 All Unsafe threads have completed.
The Static Method Synchronized Context test The Static Method Synchronized Context test calls the static IncrementStatic method on the CounterSynchronizedContext class, which inherits from ContextBoundObject and has a [Synchronization()] attribute. Because synchronization contexts do not synchronize static methods, the following undesirable result occurs: Starting Static Method Synchronized Context Test Start Thread:7 Resource writing count, static:0 Start Thread:8 Resource writing count, static:0 Start Thread:9 Resource writing count, static:0 Stop Thread:7 Resource writing count, static:1 Stop Thread:8 Resource writing count, static:1 Stop Thread:9 Resource writing count, static:1 All Static Method Synchronized Context threads have completed.
The Instance Method Synchronized Context test The Instance Method Synchronized Context test calls the IncrementInstance instance method on the CounterSynchronizedContext class, which inherits from ContextBoundObject and has a [Synchronization()] attribute. Because synchronization contexts do synchronize instance methods, the following correct result occurs: Starting Instance Method Synchronized Context Test Start Object:39 Thread:31 Resource writing count, instance:0 Stop Object:39 Thread:31 Resource writing count, instance:1 Start Object:39 Thread:32 Resource writing count, instance:1 Stop Object:39 Thread:32 Resource writing count, instance:2 Start Object:39 Thread:33 Resource writing count, instance:2 Stop Object:39 Thread:33 Resource writing count, instance:3 All Instance Method Synchronized Context threads have! completed.
Note Unlike a COM STA, a synchronized context is entered by many different threads. Like a COM STA, only one thread at a time can execute the instance methods.
Module 14 (Optional): Threading and Asynchronous Programming
The Static Method Synchronized Code Region test The Static Method Synchronized Code Region test calls the CounterSynchronizedCodeRegion class’s static method, IncrementStatic, that has the [MethodImplAttribute(MethodImplOptions.Synchronized)] attribute. Because synchronized code regions can be used to synchronize static methods, the following correct result occurs: Starting Static Method Synchronized Code Region Test Start Thread:40 Resource writing count, static:0 Stop Thread:40 Resource writing count, static:1 Start Thread:41 Resource writing count, static:1 Stop Thread:41 Resource writing count, static:2 Start Thread:42 Resource writing count, static:2 Stop Thread:42 Resource writing count, static:3 All Static Method Synchronized Code Region threads have! completed.
The Instance Method Synchronized Code Region test The Instance Method Synchronized Code Region test calls the CounterSynchronizedCodeRegion class’s instance method, IncrementInstance, that has the [MethodImplAttribute(MethodImplOptions.Synchronized)] attribute. Because synchronized code regions can be used to synchronize instance methods, the following correct result occurs: Starting Instance Method Synchronized Code Region Test Start Object:47 Thread:44 Resource writing count, instance:0 Stop Object:47 Thread:44 Resource writing count, instance:1 Start Object:47 Thread:45 Resource writing count, instance:1 Stop Object:47 Thread:45 Resource writing count, instance:2 Start Object:47 Thread:46 Resource writing count, instance:2 Stop Object:47 Thread:46 Resource writing count, instance:3 All Static Method Synchronized Code Region threads have! completed.
43
44
Module 14 (Optional): Threading and Asynchronous Programming
Manual Synchronization Topic Objective
To describe the use of the Interlocked class methods, the Mutex class, and the ReaderWriterLock class in manual synchronization.
!
Lead-in
Let’s look at some options that the .NET Framework provides to create synchronization mechanisms.
Interlocked methods – synchronize access to a variable that is shared by multiple threads #
CompareExchange, Decrement, Exchange, Increment
#
Threads of different processes can use this mechanism to safely handle variables in shared memory
!
Mutex – synchronization between threads across processes
!
ReaderWriterLock – single-writer/multiple-reader
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The .NET Framework provides several synchronization classes, which you can use to create your own synchronization mechanisms. Manual synchronization may be useful for the following operations: !
Synchronizing access to a variable that is shared by multiple threads
!
Synchronizing between threads and across processes
!
Defining a lock that implements single-writer/multiple-reader semantics
Using methods of the Interlocked class The Interlocked class methods Increment, Decrement, Exchange, and CompareExchange provide a simple mechanism for synchronizing access to a variable that is shared by multiple threads. The threads of different processes can use this mechanism if the variable is in shared memory. On modern processors, the methods of the Interlocked class can often be implemented by a single instruction. Thus, the methods of the Interlocked class provide very high-performance synchronization, and can be used to build higher-level synchronization mechanisms, like spin locks.
Module 14 (Optional): Threading and Asynchronous Programming
45
Increment and Decrement The Increment and Decrement methods combine the operations of incrementing or decrementing the variable and checking the resulting value. This atomic operation is useful in a multitasking operating system, in which the system can interrupt one thread’s execution to grant a slice of processor time to another thread. Without such synchronization, one thread can increment a variable but be interrupted by the system before it can check the resulting value of the variable. A second thread can then increment the same variable. When the first thread receives its next time slice, it will check the value of the variable, which has now been incremented not once, but twice. The Interlocked variable access methods protect against this kind of error. Note The C# increment operator (++) and decrement operator (--) are not implemented by atomic operations and therefore do not provide the thread safety that the Increment and Decrement methods do.
Exchange and CompareExchange The Exchange method atomically exchanges the values of the specified variables. The CompareExchange method combines two operations: comparing the first and third parameter for equality, and if they are equal, storing the second value in the first variable. The Exchange and CompareExchange methods that are exposed by the Interlocked class take arguments of type Object that can store references. However, type safety requires that all of the arguments be typed strictly as Object; you cannot simply cast an object to Object in the call to one of these methods. Instead, you must create variables of type Object, assign a custom object to that variable, and then pass that variable.
46
Module 14 (Optional): Threading and Asynchronous Programming
The following Visual C# code example only allows a single call to set the property X. The property X is only set once because the CompareExchange method changes the value of _x only when its first and third parameters are equal, that is to say when _x is equal to null. After _x has been set to ovalue, it no longer equals null and the CompareExchange method will no longer modify it. using System; using System.Threading; namespace CompareandExchange { class PropertyTest { private Object _x; public int X { set { Object ovalue = value; Interlocked.CompareExchange( ref _x, ovalue, null); } get { return (int) _x; } } } class Class1 { static void Main(string[] args) { PropertyTest pt = new PropertyTest(); pt.X = 1; pt.X = 2; Console.WriteLine("pt.X: {0}", pt.X); } } }
This code outputs: pt.X: 1
Module 14 (Optional): Threading and Asynchronous Programming
47
Mutex You can use a Mutex object to synchronize between threads and across processes. The Mutex class provides a synchronization primitive that grants exclusive access to the shared resource to only one thread. If a thread acquires a mutex, the second thread that wants to acquire that mutex is suspended until the first thread releases the mutex. Although Mutex does not have all of the wait and pulse functionality of the Monitor class, it does offer the creation of named mutexes that can be used between processes. You call WaitOne, WaitAll, or WaitAny to request ownership of the Mutex. The state of the Mutex is signaled if no thread owns it. If a thread owns a Mutex, that thread can specify the same Mutex in repeated wait-request calls without blocking its execution. However, it must release the Mutex as many times as it called wait to release ownership. If a thread terminates normally while owning a Mutex, the state of the Mutex is set to signaled and the next waiting thread gets ownership. The Mutex class corresponds to a Win32 CreateMutex call.
ReaderWriterLock The ReaderWriterLock defines a lock that implements single-writer/multiplereader semantics. In terms of resources, ReaderWriterLock objects are sufficiently inexpensive enough to be used in large numbers such as with perobject synchronization. The ReaderWriterLock provides equity between readers and writers. After a writer-lock is requested, no new readers are accepted until the writer has access. Neither readers nor writers are perpetually denied access. The ReaderWriterLock class supports functionality for upgrading to a writer lock with a return argument that indicates intermediate writes, and for downgrading from a writer lock, which restores the state of the lock. The class recovers from most common failures such as the creation of events. The lock maintains a consistent internal state and remains usable.
48
Module 14 (Optional): Threading and Asynchronous Programming
The following example shows how a class can use a ReaderWriterLock object to allow multiple threads to execute its Read method or a single thread to execute its Write method at any time. using System; using System.Threading; class Resource { ReaderWriterLock rwl = new ReaderWriterLock(); public void Read(Int32 threadNum) { rwl.AcquireReaderLock(Timeout.Infinite); try { // many can do read processing, writers blocked … } finally { rwl.ReleaseReaderLock(); } } public void Write(Int32 threadNum) { rwl.AcquireWriterLock(Timeout.Infinite); try { // one can write processing, readers blocked … } finally { rwl.ReleaseWriterLock(); } } }
Module 14 (Optional): Threading and Asynchronous Programming
49
Thread Safety and the .Net Framework Classes Topic Objective
To introduce .NET Framework classes thread safety issues.
!
Public static members of .NET Framework classes are thread-safe – other public members see .NET SDK
Lead-in
!
.NET Framework Collections classes, by default, are generally not thread-safe for modification
Let’s look at the thread safety issues of the .NET Framework classes.
#
!
Hashtable is thread safe for one writer and multiple readers
To obtain a thread-safe collection class #
Use or implement a thread-safe wrapper object obtained from a Synchronized method
#
Use a locking mechanism
*****************************ILLEGAL FOR NON-TRAINER USE****************************** All public static members (methods, properties, and fields) within the .NET Framework support concurrent access within a multithreaded environment. Therefore, you can invoke any .NET Framework static member simultaneously from two threads without encountering race conditions, deadlocks, or crashes. To determine whether a .NET Framework class or structure is thread-safe, see the Thread Safety section for that class or structure in the .NET Framework SDK documentation. If you want to use a nonthread-safe class in a multithreaded environment, you must wrap an instance of the class with code that supplies the necessary synchronization constructs.
50
Module 14 (Optional): Threading and Asynchronous Programming
Collections By default, Collections classes are generally not thread-safe. Multiple readers can safely read the collection; however, any modification to the collection produces undefined results for all threads that access the collection, including the reader threads. You can make Collections classes thread safe by using any of the following techniques: !
Create a thread-safe wrapper by using the Synchronized method and access the collection exclusively through that wrapper. When implementing the Synchronized method, derived classes must override the IsReadOnly property to return the correct value.
!
If the class does not have a Synchronized method, derive from the class and implement a Synchronized method by using the SyncRoot property.
!
Use a locking mechanism, such as the lock statement in Visual C# (SyncLock in Visual Basic), on the SyncRoot property when accessing the collection.
By default, only the Hashtable class guarantees thread safety when one writer and multiple readers simultaneously access the collection. You can make the collection thread-safe for multiple writers by using any of the preceding techniques. The Array class does not include a Synchronized method and, although it has a SyncRoot property, you cannot derive from the class. Therefore, you can make an array thread safe only through the locking mechanism.
Module 14 (Optional): Threading and Asynchronous Programming
51
" Special Thread Topics Topic Objective
To provide an overview of the topics that you will cover in this section.
Lead-in
Having looked at threading in general and within the context of the .NET Framework, let’s focus now on some specialized thread topics.
!
Timer Class
!
Thread Pools
!
Threads and COM Interoperability
!
Using Threads with Windows Forms Controls
!
Best Practices for Working with Threads
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In addition to the basic thread creation and management operations and the thread safety support through synchronization mechanisms, the .NET Framework provides other interesting areas of threading functionality. In this section, you will learn about timers and thread pools. The System.Threading namespace includes: !
A Timer class that enables a delegate to be called after a specified amount of time
!
A ThreadPool class that manages groups of threads
!
An IOCompletionCallback class to enable notification when an I/O operation completes
You will also learn how the runtime synchronizes access to shared resources between unmanaged COM objects and managed objects in a way that ensures thread safety. In addition, you will learn about the requirements for dealing with nonthreadsafe Windows Forms controls, and how you can optimize the performance effects that are associated with marshaling method calls. Finally, you will learn the recommended best practices for implementing thread-safe code.
52
Module 14 (Optional): Threading and Asynchronous Programming
Timer Class Topic Objective
To describe how to use the Thread.Timer class with a delegate to execute methods periodically.
Lead-in
The Thread.Timer class provides functionality to execute a method at specified intervals.
!
Thread.Timer class - periodically executes a method #
Threading.TimerCallback delegate for callback method
#
Change method changes timer values
#
Dispose method frees the resources held by the timer
Timer Timer timer timer == new new Timer( Timer( new new TimerCallback(CheckStatus), TimerCallback(CheckStatus), null, null, 0, 0, 2000); 2000); // // …… static static void void CheckStatus(Object CheckStatus(Object state) state) {{ Console.WriteLine("Checking Console.WriteLine("Checking Status."); Status."); }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The Thread.Timer class provides a mechanism for executing methods at specified intervals. You cannot inherit from this class. To specify the methods associated with a Timer, use a TimerCallback delegate. Specify the timer delegate when the timer is constructed. The timer delegate cannot be changed. The method calls do not execute in the thread that creates the timer; they execute in a separate thread that is automatically allocated by the system. When you create a timer, you specify an amount of time to wait before the first invocation of the delegate method (due time), and an amount of time to wait between subsequent invocations (period). A timer invokes its methods when its due time elapses, and invokes its methods once per period thereafter. You can change these values, or you can disable the timer, by using the Change method. When a timer is no longer needed, use the Dispose method to free the resources held by the timer. Note There are multiple Timer classes in the .NET Framework. Be careful not to confuse the Thread.Timer class with the System.Timers.Timer and System.Windows.Forms.Timer classes. For more information about these classes, see the .NET Framework SDK documentation.
Module 14 (Optional): Threading and Asynchronous Programming
53
The following example shows how to use a timer and a delegate to execute methods at specified intervals of two seconds: using System; using System.Threading;
class App { public static void Main() { Console.WriteLine( "Checking for status updates every 2 seconds."); Console.WriteLine( " (Hit Enter to terminate the sample)"); Timer timer = new Timer(new TimerCallback(CheckStatus), null, 0, 2000); Console.ReadLine(); timer.Dispose(); }
// The callback method's signature MUST // match that of a System.Threading.TimerCallback // delegate (it takes an Object parameter and returns void) static void CheckStatus(Object state) { Console.WriteLine("Checking Status."); // ... } }
54
Module 14 (Optional): Threading and Asynchronous Programming
Thread Pools Topic Objective
To introduce thread pooling.
Lead-in
You can often use thread pooling to make the use of multiple threads more efficient.
!
Thread pooling is used to improve efficiency #
!
!
System optimizes based on all of computer’s processes
Do not use thread pooling when you #
Need a task to have a particular priority
#
Have a task that might run for a long time
#
Need to place threads into a single-threaded apartment
#
Need a stable identity to be associated with the thread
IOCompletionCallback delegate for asynchronous I/O completion events #
A thread from the thread pool will process data when received
*****************************ILLEGAL FOR NON-TRAINER USE****************************** You can often use thread pooling to make the use multiple threads more efficient. Many applications use multiple threads, but often those threads spend a great deal of time in the sleeping state waiting for an event to occur. Other threads may enter a sleeping state and be awakened only periodically to poll for a change or update status information before going to sleep again.
Advantages of thread pools Using thread pooling provides your application with a pool of worker threads that are managed by the system, allowing you to concentrate on application tasks rather than thread management. In fact, if you have a number of short tasks that require more than one thread, using the ThreadPool class is the easiest and typically the most efficient way to take advantage of multiple threads. Thread pooling enables the system to optimize the use of multiple threads for better throughput, not only for your application’s process but also with respect to other processes on the computer. This optimization is transparent to your application. When you use a thread pool, the system can optimize thread time slices, taking into account all of the current processes on your computer.
Module 14 (Optional): Threading and Asynchronous Programming
55
Using thread pools in the .NET Framework The .NET Framework uses thread pools for several purposes: asynchronous calls, System.Net socket connections, asynchronous I/O completion, timers, and registered wait operations, among others. To use a thread pool, call ThreadPool.QueueUserWorkItem from managed code, or CorQueueUserWorkItem from unmanaged code, and pass a WaitCallback delegate that wraps the method that you want to add to the queue. You can also queue work items that need to be executed when a wait operation completes. You queue these work items by using ThreadPool.RegisterWaitForSingleObject and passing a WaitHandle and a WaitOrTimerCallback delegate. When the WaitHandle is signaled or when timed out, it raises a call to the method wrapped by the WaitOrTimerCallback delegate. In both cases the thread pool uses or creates a background thread to invoke the callback method.
Reasons Not to Use Thread Pools There are several scenarios in which it is appropriate to create and manage your own threads instead of using the ThreadPool class. You should do so when: !
You need a task to have a particular priority.
!
You have a task that may run a long time and, therefore, block other tasks.
!
You need to place threads into a single-threaded apartment (all ThreadPool threads are in the multithreaded apartment).
!
You need a stable identity to be associated with a particular thread. For example, you may want to use a dedicated thread to abort that thread, suspend it, or discover it by name.
56
Module 14 (Optional): Threading and Asynchronous Programming
The following example shows one way to use the ThreadPool class methods to start multiple threads and monitor their completion: using System; using System.Threading; class Counter { public void Read(int threadNum) { //… } public void Increment(int threadNum) { //… } } class App { static Counter counter = null; static int totalNumberOfAsyncOps = 10; static int numAsyncOps = totalNumberOfAsyncOps; static AutoResetEvent asyncOpsAreDone = new AutoResetEvent(false); public static void Main() { counter = new Counter(); for (int threadNum = 0; threadNum < totalNumberOfAsyncOps; threadNum++) { ThreadPool.QueueUserWorkItem( new WaitCallback(UpdateResource), threadNum); } asyncOpsAreDone.WaitOne(); } //… // The callback method's signature MUST match that of a // System.Threading.TimerCallback delegate // (it takes an Object parameter and returns void) static void UpdateResource(Object state) { int threadNum = (int) state; if ((threadNum % 2) != 0) counter.Read(threadNum); else counter.Increment(threadNum); if (( Interlocked.Decrement(ref numAsyncOps)) == 0) asyncOpsAreDone.Set(); } }
Module 14 (Optional): Threading and Asynchronous Programming
57
IOCompletionCallback Using asynchronous I/O completion events, a thread from the thread pool processes data only when the data is received. After the data has been processed, the thread returns to the thread pool. To make an asynchronous I/O call, you must associate an operating system I/O handle with the thread pool and specify a callback method. When the I/O operation completes, a thread from the thread pool invokes the callback method. For more information about using asynchronous I/O completion events, see the .NET Framework SDK documentation.
58
Module 14 (Optional): Threading and Asynchronous Programming
Demonstration: Using Synchronization Techniques and Thread Pooling Topic Objective
To demonstrate various synchronization techniques and the use of the thread pool.
Lead-in
In this demonstration, we’ll look at various synchronization techniques and the use of the thread pool.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This demonstration shows how to perform synchronization using the C# lock keyword and manual synchronization primitives and how to use the thread pool to obtain multiple threads. The demonstration consists of several test sequences, which are described in this topic. The classes in the demonstration derive from the class Counter and differ by the technique for synchronization that they implement. For each class’s test, the even number threads execute the class’s Read method, which simply examines and outputs the counter’s value, but does not modify it. The odd number threads execute the class’s Write method, which reads the counter, increments the value, and stores the new value back in the counter. The code for this demonstration is located in \Democode\ Mod14\Demo14.4.
Module 14 (Optional): Threading and Asynchronous Programming
The Unsafe test The Unsafe test uses an instance of the CounterUnsafe class which does no synchronization. As a result of the race condition, the five Write method invocations fail to increase the value of count by 5. The test results in output that is similar to the following: Unsafe test: Start Resource writing (Thread=0) count: 0 Start Resource reading (Thread=1)count: 0 Stop Resource reading (Thread=1) count: 0 Start Resource writing (Thread=2) count: 0 Start Resource reading (Thread=3)count: 0 Stop Resource writing (Thread=0) count: 1 Start Resource writing (Thread=4) count: 1 Stop Resource reading (Thread=3) count: 1 Start Resource reading (Thread=5)count: 1 Stop Resource reading (Thread=5) count: 1 Start Resource writing (Thread=6) count: 1 Start Resource reading (Thread=7)count: 1 Stop Resource reading (Thread=7) count: 1 Start Resource writing (Thread=8) count: 1 Stop Resource writing (Thread=2) count: 1 Start Resource reading (Thread=9)count: 1 Stop Resource reading (Thread=9) count: 1 Stop Resource writing (Thread=4) count: 2 Stop Resource writing (Thread=6) count: 2 Stop Resource writing (Thread=8) count: 2 All Unsafe threads have completed.
59
60
Module 14 (Optional): Threading and Asynchronous Programming
The Interlocked test The Interlocked test uses an instance of the CounterUsingInterlocked class, which uses an Interlocked.Increment instruction to atomically update the count. The five Write method invocations now increment count by 5. The test results in output that is similar to the following: Interlocked test: Start Resource writing (Thread=0) count: 0 Stop Resource writing (Thread=0) count: 1 Start Resource reading (Thread=1)count: 1 Start Resource writing (Thread=2) count: 1 Stop Resource writing (Thread=2) count: 2 Start Resource reading (Thread=5)count: 2 Start Resource reading (Thread=3)count: 2 Start Resource writing (Thread=4) count: 2 Stop Resource writing (Thread=4) count: 3 Start Resource writing (Thread=6) count: 3 Stop Resource writing (Thread=6) count: 4 Start Resource reading (Thread=7)count: 4 Start Resource writing (Thread=8) count: 4 Stop Resource writing (Thread=8) count: 5 Start Resource reading (Thread=9)count: 5 Stop Resource reading (Thread=1) count: 5 Stop Resource reading (Thread=5) count: 5 Stop Resource reading (Thread=3) count: 5 Stop Resource reading (Thread=7) count: 5 Stop Resource reading (Thread=9) count: 5 All Interlocked threads have completed.
Note In the preceding example, the Interlock.Increment method synchronized the concurrent thread’s Write method updates the count. However, this approach does not prevent the value of count from changing its value during another thread’s execution of the Read method. The manual synchronization techniques that are demonstrated in the remaining test sequences will synchronize execution to provide a consistent value for count during each Read operation.
Module 14 (Optional): Threading and Asynchronous Programming
61
The Lock test The Lock test uses an instance of the CounterUsingLock class, which uses the Visual C# lock keyword to synchronize access to the object so that only one thread at a time executes either of the instance methods. The five Write method invocations now increment count by 5, and all of the Read operations have a consistent value for count. The test results in output that is similar to the following: Lock test: Start Resource writing (Thread=0) count: 0 Stop Resource writing (Thread=0) count: 1 Start Resource writing (Thread=6) count: 1 Stop Resource writing (Thread=6) count: 2 Start Resource reading (Thread=9)count: 2 Stop Resource reading (Thread=9) count: 2 Start Resource reading (Thread=7)count: 2 Stop Resource reading (Thread=7) count: 2 Start Resource writing (Thread=4) count: 2 Stop Resource writing (Thread=4) count: 3 Start Resource writing (Thread=2) count: 3 Stop Resource writing (Thread=2) count: 4 Start Resource reading (Thread=3)count: 4 Stop Resource reading (Thread=3) count: 4 Start Resource reading (Thread=1)count: 4 Stop Resource reading (Thread=1) count: 4 Start Resource reading (Thread=5)count: 4 Stop Resource reading (Thread=5) count: 4 Start Resource writing (Thread=8) count: 4 Stop Resource writing (Thread=8) count: 5 All Lock threads have completed.
62
Module 14 (Optional): Threading and Asynchronous Programming
The Mutex test The Mutex test uses an instance of the CounterUsingMutex class, with the Mutex class to synchronize access to the object so that only one thread at a time executes either of the instance methods. The five Write method calls now increment count by 5, and all the Read operations have a consistent value for count. The test results in output that is similar to the following: Mutex test: Start Resource writing (Thread=0) count: 0 Stop Resource writing (Thread=0) count: 1 Start Resource reading (Thread=3)count: 1 Stop Resource reading (Thread=3) count: 1 Start Resource writing (Thread=4) count: 1 Stop Resource writing (Thread=4) count: 2 Start Resource reading (Thread=5)count: 2 Stop Resource reading (Thread=5) count: 2 Start Resource writing (Thread=6) count: 2 Stop Resource writing (Thread=6) count: 3 Start Resource reading (Thread=7)count: 3 Stop Resource reading (Thread=7) count: 3 Start Resource writing (Thread=8) count: 3 Stop Resource writing (Thread=8) count: 4 Start Resource writing (Thread=2) count: 4 Stop Resource writing (Thread=2) count: 5 Start Resource reading (Thread=1)count: 5 Stop Resource reading (Thread=1) count: 5 Start Resource reading (Thread=9)count: 5 Stop Resource reading (Thread=9) count: 5 All Mutex threads have completed.
Module 14 (Optional): Threading and Asynchronous Programming
The ReadWriteLock test The ReadWriteLock test uses an instance of the CounterUsingReaderWriterLock class, which uses the ReaderWriterLock class to synchronize access to the object. Note that multiple readers or a single writer can execute concurrently. The five Write method calls now increment count by 5, and all of the Read operations have a consistent value for count. Because a thread that is executing a Read does not block other threads that are executing a Read, this technique enables the Read operations to complete sooner than in the previous tests. The test results in output that is similar to the following: ReadWriteLock test: Start Resource writing (Thread=0) count: 0 Stop Resource writing (Thread=0) count: 1 Start Resource reading (Thread=1)count: 1 Start Resource reading (Thread=3)count: 1 Start Resource reading (Thread=5)count: 1 Start Resource reading (Thread=7)count: 1 Start Resource reading (Thread=9)count: 1 Stop Resource reading (Thread=1) count: 1 Stop Resource reading (Thread=3) count: 1 Stop Resource reading (Thread=5) count: 1 Stop Resource reading (Thread=7) count: 1 Stop Resource reading (Thread=9) count: 1 Start Resource writing (Thread=2) count: 1 Stop Resource writing (Thread=2) count: 2 Start Resource writing (Thread=4) count: 2 Stop Resource writing (Thread=4) count: 3 Start Resource writing (Thread=6) count: 3 Stop Resource writing (Thread=6) count: 4 Start Resource writing (Thread=8) count: 4 Stop Resource writing (Thread=8) count: 5 All ReadWriteLock threads have completed.
63
64
Module 14 (Optional): Threading and Asynchronous Programming
Threads and COM Interoperability Topic Objective
To explain how managed threads call into a COM component.
!
#
!
Lead-in
COM components use apartments to synchronize access to resources.
Thread calls to compatible COM apartment objects reduce costs Calls go directly to the COM objects, costly proxy-stub is eliminated
A managed thread can indicate it will host an MTA or STA COM apartment using #
Thread.ApartmentState property or [STAThread] [MTAThread] attributes, for example:
[STAThread] [STAThread] public public static static void void Main(string[] Main(string[] args) args) {//…} {//…} !
The finalizer thread and all ThreadPool threads are MTA
!
Managed objects can be called from any COM apartment in a free-threaded manner #
!
Except managed objects derived from ContextBoundObject
Managed code calls out to COM objects following COM rules
*****************************ILLEGAL FOR NON-TRAINER USE****************************** COM components use apartments to synchronize access to resources. In contrast, managed objects use synchronized regions, synchronization primitives such as mutexes, locks and completion ports, and synchronized contexts to ensure that all shared resources are used in a thread-safe manner.
Calling a COM object from a managed thread For interoperability, the runtime creates and initializes an apartment when a managed thread creates a COM object. A managed thread can create and enter a single-threaded apartment (STA) that contains only one thread, or a multithreaded apartment (MTA) that contains one or more threads. When a COM apartment and a thread-generated apartment are compatible, COM allows the calling thread to make calls directly to the COM object. If the apartments are incompatible, COM creates a compatible apartment and marshals all calls through a proxy in the new apartment. On the first call to unmanaged code, the runtime calls CoInitializeEx to initialize the COM apartment as an MTA or STA apartment. As long as the proxy and stub are registered or the type library is registered, you do not have to set this property.
Module 14 (Optional): Threading and Asynchronous Programming
65
Setting the Thread.ApartmentState property You can mark a managed thread to indicate that it will host a single-threaded or multithreaded apartment. The Thread.ApartmentState property returns and assigns the apartment state of a thread. If the property has not been set, the property returns ApartmentState.Unknown. Whenever the COM object and the managed thread are in incompatible apartments, all calls on the object are made through a COM-created proxy. The following example shows how to create an STA apartment-threaded COM object, AptSimple, from managed code: using System.Threading; using APTOBJLib; //… AptSimple obj = new AptSimple (); obj.Counter = 1;
To eliminate the proxy and stub mechanism and significantly enhance performance, set the ApartmentState on the thread before creating the object, as follows: using System.Threading; using APTOBJLib; //… Thread.CurrentThread.ApartmentState = ApartmentState.STA; AptSimple obj = new AptSimple (); obj.Counter = 1;
You can set the ApartmentState property when the thread is in the ThreadState.Unstarted or ThreadState.Running state; however, you can only set the property once for a thread. The two valid property states are singlethreaded apartment (STA) or multithreaded apartment (MTA).
66
Module 14 (Optional): Threading and Asynchronous Programming
Using the [STAThread] and [MTAThread] attributes In some situations, a thread may already have called into unmanaged code before the ApartmentState could be set. As an alternative to setting the ApartmentState enumeration, you can apply the System.STAThreadAttribute or System.MTAThreadAttribute to the main entry point of the application. By applying these attributes, you ensure that the main thread of an application is in the proper state. For example, to ensure that the main thread of a Windows Form is apartment compatible with the Form’s control objects that must be created in a single threaded COM apartment, you can use the following code: //… [STAThread] public static void Main(string[] args) { //… }
Other COM vs. managed threading issues Either apartment state has little effect on the managed portion of your application. The finalizer thread and all threads that are controlled by ThreadPool are MTA. Managed objects that are exposed to COM behave as if they had aggregated the free-threaded marshaler. In other words, they can be called from any COM apartment in a free-threaded manner. The only managed objects that do not exhibit this free-threaded behavior are those objects that derive from the ContextBoundObject class. In the managed execution environment, there is no support for the SynchronizationAttribute, unless you use contexts and context-bound managed instances. If you are using EnterpriseServices, your object must derive from ServicedComponent, which is itself derived from ContextBoundObject. When managed code calls out to COM objects, it always follows COM rules. In other words, it calls through COM apartment proxies and COM+ 1.0 context wrappers as dictated by OLE32.
Module 14 (Optional): Threading and Asynchronous Programming
67
Using Threads with Windows Forms Controls Topic Objective
To describe how to use a Windows Forms control’s thread-safe method to make cross thread calls on the control.
!
#
!
Lead-in
Because Windows Forms controls can only execute on the thread on which they are created, they are not considered thread-safe.
Controls can only execute methods on the thread on which they are created
!
Except for calls to their thread-safe methods
Background thread calls to a control must be marshaled #
Use thread-safe methods - Invoke, BeginInvoke, and EndInvoke
#
Methods take a reference to a delegate, typically an instance of the MethodInvoker delegate
For example, a call to the aFormControl object’s UpdateProgress method
MethodInvoker MethodInvoker mi mi == new new MethodInvoker(aFormControl.UpdateProgress); MethodInvoker(aFormControl.UpdateProgress); aFormControl.BeginInvoke(mi); aFormControl.BeginInvoke(mi);
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Windows Forms controls can only execute on the thread on which they are created. That is, they are not thread-safe. If you want to get or set properties, or call methods, on a control from a background thread, you must marshal the call to the thread that created the control. There are five methods on a control that are safe to call from any thread: InvokeRequired, Invoke, BeginInvoke, EndInvoke, and CreateGraphics. For all other method calls on a control, you should pass a delegate to that method as a parameter to the control’s Invoke or BeginInvoke methods. The Invoke and BeginInvoke methods ensure that their delegate parameter’s method is executed on the thread that owns this control’s underlying window handle.
68
Module 14 (Optional): Threading and Asynchronous Programming
The following table describes the invoke methods and specifies the method signatures for those methods. Method
Signature and description
InvokeRequired
public bool InvokeRequired { get ; }
Returns true if the caller must call Invoke when making method calls to this control. BeginInvoke
public IAsyncResult BeginInvoke(Delegate method) public IAsyncResult BeginInvoke(Delegate method, Object[] args)
Executes the specified delegate on the thread that owns this control’s underlying window handle. The delegate is called asynchronously, and this method returns immediately. You can call this method from any thread, even the thread that owns the control’s handle. If the control’s handle does not exist yet, this method will follow up the control’s parent chain until it finds a control or form that has a window handle. If no appropriate handle can be found, BeginInvoke throws an exception. Exceptions within the delegate method are considered untrapped and are sent to the application’s untrapped exception handler. EndInvoke
public Object EndInvoke(IAsyncResult asyncResult)
Retrieves the return value of the asynchronous operation represented by the IAsyncResult interface that is passed. If the asynchronous operation has not completed, this method blocks until the result is available. Invoke
public Object Invoke(Delegate method) public Object Invoke(Delegate method, Object[] args)
Executes the specified delegate on the thread that owns this control’s underlying window handle. The delegate is called synchronously and this method returns after the invoked method has returned. The return value is the result of the invoked method. It is an error to call this method on the same thread to which the control belongs.
The following code demonstrates how to create a background thread that uses a MethodInvoker to update a ProgressBar control at regular intervals:
Module 14 (Optional): Threading and Asynchronous Programming //… //Start the background thread timerThread = new Thread(new ThreadStart(ThreadProc)); timerThread.IsBackground = true; timerThread.Start(); //… //This function is executed on a background thread – // it marshalls calls to update the UI back to // the foreground thread public void ThreadProc() { try { MethodInvoker mi = new MethodInvoker(this.UpdateProgress); while (true) { //Call BeginInvoke on the Form this.BeginInvoke(mi); Thread.Sleep(500) ; } } //Thrown when the thread is interupted by the main // thread - exiting the loop catch (ThreadInterruptedException e) { //Simply exit.... } catch (Exception we) { } } //This function is called from the background thread private void UpdateProgress() { //Reset to start if required if (progressBar1.Value == progressBar1.Maximum) { progressBar1.Value = progressBar1.Minimum ; } progressBar1.PerformStep() ; } //… //Make sure to clean up the background thread in Dispose public override void Dispose() { if (timerThread != null) { timerThread.Interrupt(); timerThread = null; } }
69
70
Module 14 (Optional): Threading and Asynchronous Programming
Note If you are making multiple cross-thread calls to a control, it is much more efficient to create a new method that executes those calls and make a single cross-thread call to the new method.
Module 14 (Optional): Threading and Asynchronous Programming
71
Demonstration: Windows Forms Threading Topic Objective
To demonstrate how to use a background thread in a Windows Forms application.
Lead-in
In this demonstration, you will see how to use a background thread in a Windows Forms application.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This demonstration shows how to use a background thread in a Windows Form application. The background thread will periodically advance the Windows Form progress bar. The code for this demonstration is located in \Democode\ Mod14\Demo14.5. You should run this demonstration with breakpoints set on the ThreadProc method’s statement: this.BeginInvoke(mi);
and on the UpdateProgress method’s statement: progressBar1.PerformStep();
Start the application and observe the Microsoft Visual Studio® .NET Threads window. Note To make the Visual Studio .NET Threads window visible, while the application is running, on the Debug menu, click Windows, and click Threads. Click the form’s Start button, and when the program breaks, note the active thread. Continue to the next breakpoint and again note the active thread. You should observe that the BeginInvoke method marshaled the call to the Windows Forms main thread.
72
Module 14 (Optional): Threading and Asynchronous Programming
Best Practices for Working with Threads Topic Objective
To provide a brief review of guidelines for working with threads.
Lead-in
If you need to implement threading in your applications, there are some guidelines and best practices of which you should be aware.
!
Avoid providing static methods that alter static state
!
Static state must be thread safe
!
Instance state does not need to be thread safe
!
Avoid taking locks whenever possible
!
Be aware that deadlocks can result from calls in locked sections
!
Use the System.Threading.Interlocked classes in preference to the lock statement where possible
!
Avoid the need for synchronization if possible
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Use the following best practices and rules as design guidelines for implementing threading in your applications: !
Avoid providing static methods that alter static state. In common server scenarios, static state is shared across requests, which means that multiple threads can execute requests that access the same static state at the same time. This creates the potential for threading bugs. Consider using a design pattern that encapsulates data into instances that are not shared across requests.
!
Static state must be thread-safe.
!
Instance state does not need to be thread safe. By default, a library is not thread safe. Adding locks to create thread-safe code decreases performance, increases lock contention, and creates the possibility for deadlock bugs to occur. In common application models, only one thread at a time executes user code, which minimizes the need for thread safety. For this reason, the .NET Framework is not thread safe by default. In cases where you want to provide a thread-safe version of a class, use a GetSynchronized method to return a thread-safe instance of a type. For examples, see the System.Collections namespace in the .NET Framework SDK documentation.
!
Design your library with consideration for the efficiency costs of running in a server scenario. Avoid taking locks whenever possible.
!
Be aware of method calls in locked sections. Deadlocks can result when a static method in class A calls static methods in class B and vice versa. If A and B both synchronize their static methods, this will cause a deadlock. You may discover this deadlock only under heavy threading stress.
Module 14 (Optional): Threading and Asynchronous Programming !
73
Performance issues can result when a static method in a class calls a static method in the same class. If these methods are not factored correctly, performance will suffer because there will be a large amount of redundant synchronization. Excessive use of synchronization may negatively affect performance. In addition, it may have a significant negative effect on scalability.
!
Be aware of issues with the lock statement (SyncLock in Visual Basic). It is tempting to use the lock statement to solve all threading problems. However, the System.Threading.Interlocked class is superior for updates that must be made automatically. It executes a single lock prefix if there is no contention. In a code review, you should look for instances like the one shown in the following example. lock(this) { myField++; }
!
Avoid the need for synchronization if possible. For high traffic pathways, it is best to avoid synchronization. Sometimes you can adjust the algorithm to tolerate race conditions rather than eliminate them.
74
Module 14 (Optional): Threading and Asynchronous Programming
" Asynchronous Programming in .NET Topic Objective
To provide an overview of the topics that you will cover in this section.
Lead-in
In this section, we will look at how the .NET Framework enables asynchronous programming.
!
Support for Asynchronous Programming in .NET
!
Design Pattern for Asynchronous Programming
!
Asynchronous File Stream Read Example
!
Asynchronous Delegates
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The central idea behind asynchronous programming is to be able to issue method calls to other components and to carry on with other work, without waiting for the operation to complete. The runtime provides rich support for asynchronous programming and handles the details of threading and data exchange. In this section, you will learn about how the .NET Framework supports asynchronous programming.
Module 14 (Optional): Threading and Asynchronous Programming
75
Support for Asynchronous Programming in .NET Topic Objective
To provide a high level overview of the .NET Framework support for asynchronous programming.
!
Supported in Many Areas of .NET #
Lead-in
In addition to support for asynchronous programming in many areas of .NET, the .NET Framework offers an asynchronous programming design pattern.
!
I/O, sockets, networking, remoting, ASP.NET and Web Services, messaging (MSMQ), delegates
Offers a design pattern for asynchronous programming #
Consistent and type-safe programming model
#
User-created classes should conform to this design pattern
*****************************ILLEGAL FOR NON-TRAINER USE****************************** With asynchronous programming in the .NET Framework, you make a call to a .NET Framework class method while the program continues execution until a specified callback is made. If no callback is specified, the program continues execution until it encounters blocking, polling, or waiting for the call to complete. For example, a program can call a method which enumerates a large list, while the main program continues to execute. When the enumeration is complete, a callback is made and the program addresses it. Many areas of the .NET Framework support asynchronous programming, including: !
File IO, Stream IO, Socket IO
!
Networking: HTTP, TCP
!
Remoting channels (HTTP, TCP) and proxies
!
XML Web services created by using ASP.NET
!
ASP.NET Web Forms
!
Messaging message queues over MSMQ
!
Asynchronous delegates
The .NET Framework provides a design pattern that makes asynchronous calls uniform across the different parts of the framework. This pattern is very useful for making complex calls that take a considerable amount of time to complete. User-created classes that support asynchronous calls should conform to this design pattern.
76
Module 14 (Optional): Threading and Asynchronous Programming
Design Pattern for Asynchronous Programming Topic Objective
To provide details of the asynchronous design pattern.
Lead-in
One of the innovations that is provided by the asynchronous design pattern is that the caller can decide whether a particular call should be asynchronous.
!
Caller decides whether to make a synchronous or asynchronous call #
!
Caller does asynchronous operation in two parts #
#
!
Server may explicitly implement asynchronous methods, or client can use delegate object’s asynchronous support Call begin method to supply parameters and start operation - an object implementing IAsyncResult is returned When operation is done, call end method to obtain the results
Caller has several options to know when operation is done #
Callback method - if specified in the begin method call, callback is invoked
#
Poll – check IAsyncResult.IsCompleted property
#
Call end method – if called before operation is done, automatically waits
#
Wait – wait on IAsyncResult.WaitHandle property, can use timeouts
*****************************ILLEGAL FOR NON-TRAINER USE****************************** One of the innovations that is provided by the asynchronous design pattern is that the caller can decide whether a particular call should be asynchronous. You do not need to do additional programming for the called object for it to support asynchronous client calls. The client can make an asynchronous call on another object whose class does not explicitly support such calls by instantiating a delegate object that refers to the object’s method. The runtime and language compilers encapsulate methods that provide asynchronous operation into delegate classes. The called object can choose to explicitly support asynchronous behavior; either because it can implement asynchronous behavior more efficiently than by using a general architecture, or because the called object is designed to support only asynchronous behavior by its callers. The level of asynchronous support of the called object is up to the object’s author. However, if an object is to explicitly support asynchronous methods, you should follow the asynchronous design pattern for exposing these methods.
Details of the asynchronous design pattern In the .NET Framework asynchronous design pattern, the asynchronous operation is split into two logical parts: the part where the client calls a begin operation method and provides input to start the asynchronous operation, and the part where the results of the asynchronous operation are obtained by the client through a call to an end operation method.
Module 14 (Optional): Threading and Asynchronous Programming
77
Beginning the asynchronous operation In the first part of an asynchronous call, the caller upon invoking the begin operation method can also supply an optional AsyncCallback delegate, in addition to providing the input needed for the operation. This delegate refers to the method to be called when the asynchronous operation is completed. The begin method synchronously returns an object that implements the IAsyncResult interface. The caller can use the methods of this interface to determine the status of the asynchronous operation. The server typically maintains any state that is associated with an asynchronous operation in this waitable object. The IAsyncResult interface specifies the properties that are listed in the following table. IAsyncResult interface property
Description
AsyncState
AsyncState returns the object that was provided as the last parameter, as part of the begin operation method call.
AsyncWaitHandle
The AsyncWaitHandle property returns the WaitHandle that is set after the server has completed processing of the call.
CompletedSynchronously
The CompletedSynchronously property is set to true if the begin operation call completes synchronously.
IsCompleted
The IsCompleted property is set to true after the server completes processing of the call.
Obtaining the results of the asynchronous operation In the second part of an asynchronous call, the caller obtains the results of the operation in one of the four following ways: !
Callback Method If the caller supplied the optional AsyncCallback delegate, the callback method referred to by this delegate will be called when the operation completes.
!
Polling The caller can poll the returned IAsyncResult interface’s IsCompleted property to determine if the call has completed.
!
End Operation Method The caller can attempt to complete the operation by calling the end operation method, thereby blocking until the operation completes.
!
WaitHandle, End Operation Method The caller can wait on the IAsyncResult interface’s WaitHandle property. One difference between this and the previous option is that the client can use time outs to wake up periodically. Another difference is that the caller can choose to wait for multiple events to occur by calling the WaitAll method in the Thread.WaitHandle class and specifying an array of events, see the topic, Using the WaitHandle class to wait for events in this module.
78
Module 14 (Optional): Threading and Asynchronous Programming
Note Cancel is not provided on IAsyncResult, because in many implementations, there is no guarantee that an asynchronous operation will be canceled.
Exceptions If the begin operation method throws an exception, the caller can assume that an asynchronous operation was not started and that the callback delegate will not be called. After an asynchronous operation starts, the client is notified of any exceptions that are raised by the server when the client calls the end operation method.
Module 14 (Optional): Threading and Asynchronous Programming
79
Asynchronous File Stream Read Example Topic Objective
To show the first part of the Asynchronous File Stream Read example by explaining how to do an asynchronous read using a callback.
Lead-in
Let’s first look at how to do an asynchronous read by using a callback.
!
Asynchronous read with callback #
Create callback method delegate
AsyncCallback AsyncCallback myCallback myCallback == new new AsyncCallback(this.OnReadDone); AsyncCallback(this.OnReadDone); #
Begin the operation
IAsyncResult IAsyncResult ar ar == aStream.BeginRead(buffer,0, aStream.BeginRead(buffer,0, buffer.Length, buffer.Length, myCallback,(object)myState); myCallback,(object)myState); #
In the callback method, complete the operations
int int byteCount byteCount == aStream.EndRead(ar); aStream.EndRead(ar); //data //data in in buffer buffer
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The examples in this topic show how to implement a client that uses the System.IO.Stream class’s methods to read a sequence of bytes from a file. The System.IO.Stream class’s synchronous read method is named Read and the asynchronous methods are named BeginRead and EndRead. For the complete running code of these examples, see \ Democode\Mod14\Demo14.6. The following code uses a byte array named buffer to store the bytes returned from the read operation: byte[] buffer = new byte [512]; string filename = "test.txt" Stream aStream = File.OpenRead(filename);
The following code uses a synchronized call to read data from the file: int byteCount = aStream.Read(buffer, 0, buffer.Length); aStream.Close();
Details of the asynchronous read operation The asynchronous operation consists of two logical parts: the part that takes input from the client and calls the asynchronous operation, and the part that supplies results of the asynchronous operation to the client.
80
Module 14 (Optional): Threading and Asynchronous Programming
Asynchronous Read With Callback In this example the callback method is an instance method named OnReadDone, which returns void and takes a single parameter of type IAsyncResult. The System namespace defines a delegate class AsyncCallback that matches the signature for the instance method so that there is no need to declare a new delegate type. You create the callback delegate object as follows: AsyncCallback myCallback = new AsyncCallback(this.OnReadDone);
If the caller may issue multiple asynchronous calls, the caller must be able to correlate each of the results to the original call. You can use the state parameter of the method that makes the asynchronous call to store this information. In this example, you store the name of the file being read in an instance of MyState: class MyState { public string filename; public MyState(string filename) { this.filename = filename; } } // ... MyState myState = new MyState(filename);
You can now make the asynchronous call as follows: IAsyncResult ar = aStream.BeginRead( buffer, 0, buffer.Length, myCallback, (object)myState); // continue to execute
The BeginRead method returns and the thread can continue to execute while the read operation proceeds. When the read operation completes, the OnReadDone method is called, possibly on a separate thread. This method performs the second part of the asynchronous operation, obtaining the results of the operation as follows: int byteCount = aStream.EndRead(ar); // data now in buffer Console.WriteLine("Filename read: {0}, ((MyState)(ar.AsyncState)).filename); aStream.Close();
The next topic, Asynchronous File Stream Read Example (Continued), completes the example, by showing how to do an asynchronous read with polling.
Module 14 (Optional): Threading and Asynchronous Programming
81
Asynchronous File Stream Read Example (continued) Topic Objective
To continue the Asynchronous File Stream Read example by explaining how to do an asynchronous read with polling.
Lead-in
Now let’s see how to do an asynchronous read with polling.
!
Asynchonous read with polling #
Begin the operation
IAsyncResult IAsyncResult ar ar == aStream.BeginRead(buffer,0, aStream.BeginRead(buffer,0, buffer.Length, buffer.Length, null,(object)myState); null,(object)myState); #
Poll and complete
while while (!ar.IsCompleted){ (!ar.IsCompleted){ // // do do whatever whatever }} int int byteCount byteCount == aStream.EndRead(ar); aStream.EndRead(ar); // // data data in in buffer buffer
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The preceding topic showed how to complete an asynchronous read operation by using a callback delegate. In this topic, you will see how to obtain results of the asynchronous read with polling.
Asynchronous Read with Polling To handle the second part of the asynchronous operation by polling for completion rather than using a callback method, you make the initial asynchronous call by supplying a null value for the callback method. IAsyncResult ar = aStream.BeginRead( buffer, 0, buffer.Length,null, (object)myState);
The BeginRead method returns and the thread can continue to execute while the read operation proceeds. In this case, the thread periodically polls the IsCompleted property of the IAsyncResult to determine when the read operation is complete. while (!ar.IsCompleted) { // do whatever } int byteCount = aStream.EndRead(ar); // data now in buffer Console.WriteLine("Filename read: {0}, ((MyState)(ar.AsyncState)).filename); aStream.Close();
82
Module 14 (Optional): Threading and Asynchronous Programming
Demonstration: Asynchronous File Stream Read Topic Objective
To demonstrate how to use synchronous and asynchronous read methods on a file stream.
Lead-in
In this demonstration, you will see how to use synchronous and asynchronous read methods on a file stream.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This demonstration shows how to use synchronous and asynchronous read methods on a file stream. For the asynchronous case, the four different ways to complete the operation are shown: callback, poll, end method call, and wait with timeout. Notice that in the asynchronous case that uses a callback method, the callback method may be executed on a different thread than the main code. The code for this demonstration is located in \Democode\ Mod14\Demo14.6.
Module 14 (Optional): Threading and Asynchronous Programming
The output that is produced by running the code is similar to the following: Doing Synchronous Read Synchronous Read Data:! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 01234567890123456789012345678901 Doing Callback Asynchronous Read Asynchronous Callback Read Filename: test.txt Data:! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 01234567890123456789012345678901 Doing Poll Asynchronous Read Asynchronous Poll Read Filename: test.txt Data:! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 01234567890123456789012345678901
(Code continued on the following page.)
83
84
Module 14 (Optional): Threading and Asynchronous Programming Doing End Method Asynchronous Read Asynchronous End Read Filename: test.txt Data:! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 01234567890123456789012345678901 Doing Wait Asynchronous Read Asynchronous Wait Read Filename: test.txt Data:! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 0123456789012345678901234567890123456789! 01234567890123456789012345678901
Module 14 (Optional): Threading and Asynchronous Programming
85
Asynchronous Delegates Topic Objective
To describe how to use delegate objects to make asynchronous calls.
!
Lead-in
You can make an asynchronous method call on an object even if the object’s class does not provide explicit support for asynchronous operations by using the asynchronous methods of a delegate object.
Delegate object provides the ability to call a synchronous method in an asynchronous manner #
!
Compiler generates synchronous invoke method and asynchronous methods: BeginInvoke and EndInvoke
Asynchronous calls follow the design pattern #
Start operation with BeginInvoke
#
Use callback, polling, call to EndInvoke, or wait to determine completion
#
Call EndInvoke to get results
*****************************ILLEGAL FOR NON-TRAINER USE****************************** You can make an asynchronous method call on an object even if the object’s class does not provide explicit support for asynchronous operations by using the asynchronous methods of a delegate object.
Calling a synchronous method asynchronously For each delegate class the Visual C# compiler generates BeginInvoke and EndInvoke methods that provide the ability to call a synchronous target method in an asynchronous manner. If the BeginInvoke method is called, the runtime queues the request and returns immediately to the caller. The target method will be called on a thread from the thread pool. The original thread that submitted the request is free to continue executing in parallel to the target method, which is running on a thread pool thread. If a callback has been specified on BeginInvoke, it is called when the target method returns. In the callback, the EndInvoke method is used to obtain the return value and the in/out parameters. If the callback is not specified on BeginInvoke, you can use the other asynchronous design pattern techniques, for example polling, on the original thread that submitted a request.
86
Module 14 (Optional): Threading and Asynchronous Programming
Using an asynchronous delegate In the following example, which factorizes a number, you can see how to use a delegate to make an asynchronous call. The code for this demonstration is located in \Democode\ Mod14\Demo14.7. In this example, an asynchronous call is invoked on the Factorize method in the PrimeFactorizer class, which is declared as follows: public class PrimeFactorizer { public bool Factorize( int factorizableNum, ref int primefactor1, ref int primefactor2) { // code to factorize the number } }
First a delegate for the Factorize is defined: public delegate bool FactorizeDelegate( int factorizableNum, ref int primefactor1, ref int primefactor2);
When the compiler emits the FactorizeDelegate delegate class after parsing its definition, it generates the BeginInvoke and EndInvoke methods, in addition to the Invoke method. The BeginInvoke and EndInvoke methods follow the asynchronous design pattern’s method signatures, as follows: public class FactorizeDelegate: delegate { public bool Invoke( int factorizableNum, ref int primefactor1, ref int primefactor2); // The following code was supplied by the compiler. public IAsyncResult BeginInvoke( int factorizableNum, ref int primefactor1, ref int primefactor2, AsyncCallback cb, Object AsyncState ); // The following code was supplied by the compiler. public bool EndInvoke( ref int primefactor1, ref int primefactor2, IAsyncResult ar); }
Module 14 (Optional): Threading and Asynchronous Programming
Next, the method to be called when the operation completes is defined: public class ProcessFactorizedNumber { private int _ulNumber; public ProcessFactorizedNumber(int number) { _ulNumber = number; } // the qualifier is one-way, see Note [OneWayAttribute()] public void FactorizedResults(IAsyncResult ar) { int factor1=0, factor2=0; // Extract the delegate from the AsyncResult. FactorizeDelegate fd = (FactorizeDelegate)((AsyncResult)ar).AsyncDelegate; // Obtain the result. fd.EndInvoke(ref factor1, ref factor2, ar); // Output results. Console.WriteLine( "On CallBack: Factors of {0} : {1} {2}", _ulNumber, factor1, factor2); } }
Note The OneWayAttribute marks a method as one way, without a return value and out or ref parameters. When one-way methods are called, no reply message, status, or other information is expected.
87
88
Module 14 (Optional): Threading and Asynchronous Programming
The asynchronous operation is started by calling BeginInvoke as follows: public void FactorizeNumber1() { //create the necessary arguments // delegate to method to be called asynchronously PrimeFactorizer pf = new PrimeFactorizer(); FactorizeDelegate fd = new FactorizeDelegate(pf.Factorize); // arguments for the Factorize method int factorizableNum = 1000589023, temp=0; // create the callback delegate // to be called when the call completes // first create an instance of the class which // contains the callback method ProcessFactorizedNumber fc = new ProcessFactorizedNumber(factorizableNum); // then create an AsyncCallback delegate to the method AsyncCallback cb = new AsyncCallback(fc.FactorizedResults); // Asynchronously invoke the Factorize method. IAsyncResult ar = fd.BeginInvoke( factorizableNum, ref temp, ref temp, cb, null); // // Do some other useful work. //. . . }
Module 14 (Optional): Threading and Asynchronous Programming
89
An alternative approach to using a callback function is to wait for completion, as shown in the following code: public void FactorizeNumber2() { PrimeFactorizer pf = new PrimeFactorizer(); FactorizeDelegate fd = new FactorizeDelegate(pf.Factorize); int factorizableNum = 1000589023, temp=0; // Asynchronously invoke the Factorize method on pf. IAsyncResult ar = fd.BeginInvoke( factorizableNum, ref temp, ref temp, null, null); ar.AsyncWaitHandle.WaitOne(10000, false); if (ar.IsCompleted) { int factor1=0, factor2=0; // Obtain the result. fd.EndInvoke(ref factor1, ref factor2, ar); // Output results. Console.WriteLine( "After Using Wait Handle: Factors of {0}: {1} {2}", factorizableNum, factor1, factor2); } }
90
Module 14 (Optional): Threading and Asynchronous Programming
Multimedia: Asynchronous Programming Topic Objective
To illustrate asynchronous programming using Delegate objects.
Lead-in
This animation illustrates the .NET Framework common language runtime’s support for asynchronous programming using Delegate objects.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** To launch the animation, click the button in the lowerleft corner of the slide.
This animation illustrates the .NET Framework common language runtime support for asynchronous programming using Delegate objects.
Module 14 (Optional): Threading and Asynchronous Programming
91
Demonstration: Using a Delegate Topic Objective
To demonstrate how to use a delegate object to make asynchronous calls.
Lead-in
In this demonstration, you will see how to use a delegate object to make asynchronous calls.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This demonstration shows how to use a delegate object to make asynchronous calls. The asynchronous call is made by using a callback method and by waiting for the completion event. The code for this demonstration is located in \Democode\ Mod14\Demo14.7. The output is similar to the following: On CallBack: Factors of 1000589023 : 7 142941289 After Using Wait Handle : Factors of 1000589023 : 7 142941289
92
Module 14 (Optional): Threading and Asynchronous Programming
Lab 14: Working With Multithreaded Applications Topic Objective
To introduce the lab.
Lead-in
In this lab, you will create thread-safe code that has both System.Thread.Timer and background thread objects. In addition you will make asynchronous calls using delegates.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Explain the lab objectives.
Objectives After completing this lab, you will be able to: !
Create thread-safe methods using the C# lock keyword.
!
Create, start, and dispose of a System.Thread.Timer object to periodically perform an action.
!
Create, start, and interrupt a background thread.
!
Make an asynchronous method call by using delegates and a callback method according to the .NET Framework asynchronous design pattern.
Lab Setup Starter and solution files are associated with this lab. The starter files are in the folder \Labs\Lab14\Starter. The solution files for this lab are in the folder \Labs\Lab14\Solution.
Module 14 (Optional): Threading and Asynchronous Programming
93
Scenario In this scenario, you use a Windows Forms client to invoke methods on a logger server that writes messages to a log file. When starting the logger server the client specifies the filename to use as a log file. After the logger server starts, it opens the log file and begins two concurrent operations. In the first operation, the server periodically writes the date and time to the log file; the server does this by using a System.Thread.Timer object that uses a thread from the thread pool. In the second operation, the server periodically updates the client’s progress bar to indicate to the user that the logger service is running. The server does this by creating and starting a background thread. When the user stops the logger server, the server terminates both concurrent operations and closes the log file. While the logger server is running, the client can invoke a synchronous or an asynchronous write of a text string to the log file. The asynchronous write is done according to the .NET Framework asynchronous design pattern. The client starts the asynchronous write by calling the BeginInvoke method of a delegate to the logger server’s write method. The client, as a parameter to this call, supplies an AsyncCallback delegate object that refers to the client’s method that is called when the write operation completes. Because multiple client calls to the server can execute concurrently, the server’s methods must be implemented in a thread-safe manner.
Estimated time to complete this lab: 50 minutes
94
Module 14 (Optional): Threading and Asynchronous Programming
Exercise 1 Creating Thread-safe Methods In this exercise, you will add thread-safe code to the MyLogger static methods to start the logger, stop the logger, and write to the log file.
! Implement the StartLogger method 1. In Visual Studio .NET, open the Multithreading project which is located in \Labs\Lab14\Starter. 2. Open the file Form1.cs and locate the following comment for the StartLogger method of the MyLogger class: // TODO - Exercise 1.1: Add code to start logger Note You can use the Visual Studio .NET Task List window to quickly locate this code. To display this window, on the View menu, click Show Tasks, and click All. 3. Use a lock statement to ensure that only one thread at a time executes the method’s code. Because StartLogger is a static method, you lock on the System.Type object of the MyLogger class. You can obtain this object by using the following expression: typeof(MyLogger)
Place the remainder of the StartLogger method’s code within the lock statement block. 4. Create a conditional expression that evaluates whether the logger is running. If the logger is running, throw an InvalidOperationException exception. If the logger is not running (loggerRunning is false): a. Initialize numberOfWrites to zero. b. Create a FileStream object for the file that is specified in the filename parameter of the StartLogger method. The new FileStream object also takes the filename, FileMode.OpenOrCreate and FileAccess.Write parameters. c. Create a StreamWriter object and pass in the new FileStream object. This new object should be assigned to the existing static member variable w. d. Set the file pointer to the end of the new stream by calling the StreamWriter object’s seek method as follows: w.BaseStream.Seek(0, SeekOrigin.End);
e. Write to the stream by using the following format and supplying the current time and date as the parameters: "\nStartLogger – Time: {0} {1} \n\n"
Module 14 (Optional): Threading and Asynchronous Programming
95
f. Flush the stream. g. Set the existing static member variable form to the value of the parameter aForm in the StartLogger method. h. Set the existing static member variable mi to a new delegate object of type MethodInvoker that refers to the form object’s UpdateProgress method. i. Set loggerRunning to true.
! Implement the StopLogger method 1. In Form1.cs, locate the following comment for the StopLogger method of the MyLogger class: // TODO - Exercise 1.2: Add code to stop logger 2. Use a lock statement to ensure that only one thread at a time executes the static method’s code. Place the remainder of the StopLogger method’s code within the lock statement block. 3. Create a conditional expression that evaluates whether the logger is running. If the logger is not running, throw an InvalidOperationException exception. If the logger is running (loggerRunning is true): a. Write to the stream by using the following format and supplying the current time and date as the parameters: "\nStopLogger – Time: {0} {1} \n\n"
b. Close the stream. c. Set loggerRunning to false.
96
Module 14 (Optional): Threading and Asynchronous Programming
! Implement the WriteLog method 1. In Form1.cs, locate the following comment for the WriteLog method of the MyLogger class: // TODO - Exercise 1.3: Add code to write to logger 2. Use a lock statement to ensure that only one thread at a time executes the static method’s code. Place the remainder of the WriteLog method’s code within the lock statement block. 3. Create a variable of type int that is named writeCount and initialize it to the value of numberOfWrites. 4. Create a conditional expression that evaluates whether the logger is running. If the logger is not running, throw an InvalidOperationException exception. If the logger is running (loggerRunning is true): a. Make the thread sleep for 2 seconds. b. Increment the value of writeCount by 1. c. Write to the stream by using the following format and supplying writeCount and logText as the parameters: "\nCount: {0} Text: {1} - Time:"
d. Write to the stream by using the following format and supplying the current time and date as the parameters: "{0} {1} \n\n"
e. Flush the stream. f. Set numberOfWrites to the value of writeCount. 5. Return writeCount.
Module 14 (Optional): Threading and Asynchronous Programming
97
! Test the application 1. Build and run the application by using Visual Studio .NET. Ignore warning messages about fields that are never used. 2. In the Text To Log field, type Hello World. 3. To check the error reporting, click Synchronous Write, and note the following message in the status bar at the bottom of the form: “Invalid Operation - Write failed because logger was stopped” 4. Click Start Logger, and note the Logger State Indicator progress bar and the following message in the status bar: “Logger started” 5. Click Synchronous Write, and note that after a pause of 2 seconds the following message is displayed in the status bar: “Log written, count = 1” 6. Click Synchronous Write, and note that after a pause of 2 seconds the following message is displayed in the status bar: “Log written, count = 2” 7. Click Stop Logger, and note that the message in the status bar now says: “Logger stopped” 8. Use Visual Studio .NET to view the log file Log.txt, that was created in the same folder as the multithreading application’s executable program: \Labs\Lab14\Starter\Multithreading\bin\Debug You should see output that is similar to the following: StartLogger - Time: 11:09:48 AM Wednesday, September 19, 2001
Count:1 Text: Hello World - Time:11:10:40 AM Wednesday, September 19, 2001
Count:2 Text: Hello World - Time:11:11:30 AM Wednesday, September 19, 2001
StopLogger - Time:11:11:56 AM Wednesday, September 19, 2001
9. Stop the application and delete Log.txt.
98
Module 14 (Optional): Threading and Asynchronous Programming
Exercise 2 Periodically Writing to a Log File In this exercise, you will add code to the MyLogger class to use a System.Thread.Timer object. This object is used to periodically write a message to the log file indicating that the logger is alive when the logger is running.
! Create the Timer object 1. In Form1.cs, locate the following comment for the StartLogger method of the MyLogger class: // TODO - Exercise 2.1: Add code to periodically call ! a method using a System.Threading.Timer object
2. Locate the line of code that sets loggerRunning to true, and after this line assign to the static member variable timer a new object of type System.Threading.Timer. The new object should call the MyLogger class’s LogStatus method every 5 seconds. LogStatus is a static method whose delegate type is TimerCallback.
! Dispose of the Timer object 1. In Form1.cs, locate the following comment for the StopLogger method of the MyLogger class: // TODO - Exercise 2.2: Add code to dispose of the! System.Threading.Timer object
2. Locate the line of code that writes the string "\nStopLogger - Time:" to the log file and before this line add code that invokes the Dispose method on the timer object.
! Add the Timer object that writes logger alive messages periodically to the log file
1. In Form1.cs, locate the following comment for the LogStatus method of the MyLogger class: // TODO - Exercise 2.3: Add Timer callback method code to! write the date and time to the log
2. Write to the stream the following string: "\nLogger Alive: "
3. Once again, write to the stream by using the following format and supplying the current time and date as the parameters: "{0} {1} \n\n"
Module 14 (Optional): Threading and Asynchronous Programming
99
! Test the application 1. Build and run the application by using Visual Studio .NET. Ignore warning messages about fields that are never used. Ensure that you have deleted the file Log.txt that was created in the preceding exercise. 2. In the Text To Log field, type Hello World. 3. Click Start Logger, and note the following message in the status bar: “Logger started” 4. Click Synchronous Write, and note that after a pause of 2 seconds the following message is displayed in the status bar: “Log written, count = 1” 5. Click Synchronous Write, and note that after a pause of 2 seconds the message in the status bar changes to: “Log written, count = 2” 6. Click the Stop Logger button and note that the message in the status bar now says; “Logger stopped” 7. Use Visual Studio .NET to view the file Log.txt that was created in the same folder as the multithreading application’s executable program: \Labs\Lab14\Starter\Multithreading\bin\Debug You should see output that is similar to the following: StartLogger - Time: 11:47:21 AM Wednesday, September 19, 2001
Logger Alive: 11:47:21 AM Wednesday, September 19, 2001
Count:1 Text: Hello World - Time:11:47:24 AM Wednesday, September 19, 2001
Logger Alive: 11:47:26 AM Wednesday, September 19, 2001
Count:2 Text: Hello World - Time:11:47:27 AM Wednesday, September 19, 2001
StopLogger - Time:11:47:28 AM Wednesday, September 19, 2001
8. Stop the application and delete Log.txt.
100
Module 14 (Optional): Threading and Asynchronous Programming
Exercise 3 Providing the User with Status In this exercise, you will add code to the MyLogger class to use a background thread that periodically advances the Progress Bar in the user interface whenever the logger is running.
! Create the background thread 1. In Form1.cs, locate the following comment for the StartLogger method of the MyLogger class: // TODO - Exercise 3.1: Add code to start a background! thread
2. Locate the line of code that assigns to the static member variable timer a new object of type System.Threading.Timer and after this line, add code to do the following steps. 3. Create a new object of type Thread The Thread object is initialized with a delegate object of type ThreadStart that refers to the ThreadProc method of the MyLogger class. This new object should be assigned to the existing static member variable timerThread. 4. Make timerThread a background thread by using its IsBackground property. 5. Start the timerThread background thread.
! Interrupt the background thread 1. In Form1.cs, locate the following comment for the StopLogger method of the MyLogger class: // TODO - Exercise 3.2: Add code to stop a background! thread
2. Locate the line of code that invokes the Dispose method on the timer object and before this line, set up a conditional expression that checks whether the background thread is not null. If the background thread named timerThread is not null: a. Call the timerThread Interrupt method. b. Assign timerThread, form, and mi to null.
Module 14 (Optional): Threading and Asynchronous Programming
101
! Advance the form’s progress bar periodically 1. In Form1.cs, locate the following comment for the ThreadProc method of the MyLogger class: // TODO - Exercise 3.3: Add code to update the client's! progress indicator
2. In a try/catch block’s try section, insert a while(true) statement whose block contains the following steps: a. Call the form object’s BeginInvoke method. Pass as the BeginInvoke method’s parameter the delegate object of type MethodInvoker that refers to the form object’s UpdateProgress method. This object was created in the StartLogger method and is named mi. b. Make the thread sleep for ½ second. 3. In the try/catch block’s catch section add code to do the following: a. Catch exceptions of the type ThreadInterruptedException. b. Within the catch block, do nothing. This will resolve the exception and the thread will terminate gracefully.
! Test the application 1. Build and run the application by using Visual Studio .NET. Ensure that you have deleted the file Log.txt that was created in the preceding exercise. 2. In the Text To Log field, type Hello World. 3. Click Start Logger. Note that the Logger State Indicator progress bar is advancing and note the following message in the status bar. “Logger started” 4. Click Synchronous Write, and note that the progress bar stops advancing. After a pause of 2 seconds, the progress bar jumps ahead and the following message is displayed in the status bar. “Log written, count = 1” 5. Click Synchronous Write, and note that the progress bar stops advancing. After a pause of 2 seconds, the progress bar jumps ahead and the following message is displayed in the status bar. “Log written, count = 2” 6. Click Stop Logger. Note that the Logger State Indicator progress bar has stopped advancing and note that the message in the status bar now says: “Logger stopped”
102
Module 14 (Optional): Threading and Asynchronous Programming
7. Use Visual Studio .NET to view the file Log.txt that was created in the same folder as the multithreading application’s executable program: \Labs\Lab14\Starter\Multithreading\bin\Debug You should see output that is similar to the following: StartLogger - Time: 11:56:44 AM Wednesday, September 19, 2001
Logger Alive: 11:56:44 AM Wednesday, September 19, 2001
Count:1 Text: Hello World - Time:11:56:47 AM Wednesday, September 19, 2001
Logger Alive: 11:56:49 AM Wednesday, September 19, 2001
Count:2 Text: Hello World - Time:11:56:50 AM Wednesday, September 19, 2001
StopLogger - Time:11:56:53 AM Wednesday, September 19, 2001
8. Stop the application and delete the Log.txt.
Module 14 (Optional): Threading and Asynchronous Programming
103
Exercise 4 Making an Asynchronous Method Call In this exercise, you will add code that uses a delegate object to begin an asynchronous invocation of the WriteLog method of the MyLogger class. The code will specify a method to be called when the WriteLog method completes. You will add code to this method to obtain the results of the WriteLog method and post the results to the form’s status bar.
! Create the event handler code that initiates the asynchronous write 1. In Form1.cs, locate the following comment for the button4_Click method of the Form1 class: // TODO - Exercise 4.1: Add code to begin asynchronous! log write
2. Set the statusBar1.Text property to an empty string. 3. Create a delegate named wld of type WriteLogDelegate that refers to the WriteLog static method of the MyLogger class. 4. Create a delegate object named cb of type AsyncCallback that refers to the current Form1 object’s AsynchronousWriteResults method. 5. Initialize a variable named ar of type IAsyncResult to the value that is returned by invoking the wld object’s BeginInvoke method. BeginInvoke should include the following three parameters: a. The textBox2 Text property b. The cb delegate object c. null
104
Module 14 (Optional): Threading and Asynchronous Programming
! Create the code that is called when the asynchronous method call completes
1. In Form1.cs, locate the following comment for the AsynchronousWriteResults method of the Form1 class: // TODO - Exercise 4.2: Add code to this callback method to handle ! the results of asynchronous log write 2. Create a delegate object name wld of type WriteLogDelegate and set wld to refer to the method that was previously specified in the button4_Click method’s BeginInvoke method call by doing the following: a. Explicitly cast the parameter that is named ar of type IAsyncResult to type AsyncResult and get its AsyncDelegate property. b. Explicitly cast the result of the preceding step to type WriteLogDelegate and assign this value to wld. 3. Create a try/catch block and in the try section, add code to do the following: a. Initialize a variable named count of type int to the result of calling the wld object’s EndInvoke method with the single parameter named ar. b. Assign to the existing variable named resultString of type string the following value: "Log written asynchronously, " + count.ToString()
4. In the try/catch block’s catch section, add code to do the following: a. Catch exceptions of the type InvalidOperationException. b. Assign to the existing variable named resultString of type string the following value: "Invalid Operation - Asynchronous write failed because ! logger was stopped"
5. To update the form’s control on the thread that originally created the control, call the BeginInvoke method of the current Form1 object. BeginInvoke takes a single parameter that is a delegate of type MethodInvoker that refers to the current Form1 object’s UpdateAsynchronousWriteStatus method.
! Test the application 1. Build and run the application by using Visual Studio .NET. Ensure that you have deleted the file Log.txt that was created in the preceding exercise. 2. In the Text To Log field, type Hello Asynchronous World. 3. Click Start Logger. Note that the Logger State Indicator progress bar is advancing and note the following message in the status bar. “Logger started”
Module 14 (Optional): Threading and Asynchronous Programming
105
4. Click Asynchronous Write, and note that the progress bar continues to advance. After a pause of 2 seconds, the following message is displayed in the status bar: “Log written asynchronously, 1” 5. Click Asynchronous Write, and note that after a pause of 2 seconds the message in the status bar changes to: “Log written asynchronously, 2” 6. Click Asynchronous Write twice in quick succession, and after the message “Log written asynchronously, 4” is displayed in the status bar. Click Stop Logger. Note that the Logger State Indicator progress bar stops advancing and note that the following message is displayed in the status bar: “Logger stopped” 7. Use Visual Studio .NET to view the file Log.txt that was created in the same folder as the multithreading application’s executable program: \Labs\Lab14\Starter\Multithreading\bin\Debug You should see output that is similar to the following: StartLogger - Time: 12:04:27 PM Wednesday, September 19, 2001
Logger Alive: 12:04:27 PM Wednesday, September 19, 2001
Count:1 Text: Hello Asynchronous World - Time:12:04:30 PM Wednesday, September 19, 2001
Logger Alive: 12:04:32 PM Wednesday, September 19, 2001
Count:2 Text: Hello Asynchronous World - Time:12:04:33 PM Wednesday, September 19, 2001
Count:3 Text: Hello Asynchronous World - Time:12:04:36 PM Wednesday, September 19, 2001
Logger Alive: 12:04:37 PM Wednesday, September 19, 2001
Count:4 Text: Hello Asynchronous World - Time:12:04:38 PM Wednesday, September 19, 2001
StopLogger - Time:12:04:41 PM Wednesday, September 19, 2001
8. Stop the application and delete Log.txt.
106
Module 14 (Optional): Threading and Asynchronous Programming
If Time Permits Demonstrating a Race Condition In this exercise, you will remove the lock in the WriteLog method of the MyLogger class. You will note the errors resulting from the existence of a race condition that occurs when multiple asynchronous calls cause multiple threads to concurrently access the static variables of the MyLogger class.
! Create the potential for a race condition by removing the WriteLog method lock
1. In Form1.cs, locate the WriteLog method of the MyLogger class and comment out the line(s) that contain the lock statement and the opening brace of the lock statement block. 2. Just before the end of the method, comment out the closing brace of the lock statement.
! Test the application 1. Build and run the application by using Visual Studio .NET. Ensure that you have deleted the file Log.txt that was created in the preceding exercise. 2. In the Text To Log field, type Hello Asynchronous World. 3. Click Start Logger. Note that the Logger State Indicator progress bar is advancing and note the following message in the status bar: “Logger started” 4. Click Asynchronous Write, and note that the progress bar continues to advance. After a pause of 2 seconds, the following message is displayed in the status bar: “Log written asynchronously, 1” 5. Click Asynchronous Write, and note that after a pause of 2 seconds the message in the status bar changes to: “Log written asynchronously, 2” 6. Click Asynchronous Write twice in quick succession, wait for about 10 seconds, and then click Stop Logger. Note that the Logger State Indicator progress bar stops advancing and note the following message is displayed in the status bar: “Logger stopped”
Module 14 (Optional): Threading and Asynchronous Programming
107
7. Use Visual Studio .NET to view the file Log.txt that was created in the same folder as the multithreading application’s executable program: \Labs\Lab14\Starter\Multithreading\bin\Debug You should see output that is similar to the following: StartLogger - Time: 12:40:24 PM Wednesday, September 19, 2001
Logger Alive: 12:40:24 PM Wednesday, September 19, 2001
Count:1 Text: Hello Asynchronous World - Time:12:40:28 PM Wednesday, September 19, 2001
Logger Alive: 12:40:30 PM Wednesday, September 19, 2001
Count:2 Text: Hello Asynchronous World - Time:12:40:30 PM Wednesday, September 19, 2001
Logger Alive: 12:40:35 PM Wednesday, September 19, 2001
Count:3 Text: Hello Asynchronous World - Time:12:40:35 PM Wednesday, September 19, 2001
Count:3 Text: Hello Asynchronous World - Time:12:40:35 PM Wednesday, September 19, 2001
Logger Alive: 12:40:39 PM Wednesday, September 19, 2001
Logger Alive: 12:40:44 PM Wednesday, September 19, 2001
StopLogger - Time:12:40:44 PM Wednesday, September 19, 2001
Note Because of the race condition during the last two asynchronous writes, the write counter is the same for both of these writes. 8. Stop the application and delete Log.txt.
108
Module 14 (Optional): Threading and Asynchronous Programming
Review Topic Objective
To reinforce module objectives by reviewing key points.
Lead-in
The review questions cover some of the key concepts taught in the module.
!
Introduction to Threading
!
Using Threads in .NET
!
Thread Safety
!
Special Thread Topics
!
Asynchronous Programming in .NET
*****************************ILLEGAL FOR NON-TRAINER USE****************************** 1. State one advantage and one disadvantage of using multiple threads in an application. Advantages: Threading maintains the responsiveness of the user interface while background processing is occurring, allows for subtasks to be assigned different priorities, and allows a long-running task to be done in the background. Disadvantages: Overhead with thread creation and running may decrease performance; sharing data between threads is complex and prone to errors.
2. Write the code to execute a thread on the static method in a class named MyClass that is declared: static void MyMethod(). Thread t1 = new Thread(new ThreadStart(MyClass.MyMethod)); t1.Start();
3. Name the three .NET Framework categories of techniques for providing thread safety when data must be shared between concurrently executing threads. Synchronized Context, Synchronized Code Regions, Manual Synchronization.
Module 14 (Optional): Threading and Asynchronous Programming
4. If you want to get or set properties, or call methods on a control from a background thread, what must you do? You must marshal the call to the thread on which the control was created by using one of the control’s thread safe methods: Invoke, BeginInvoke, and EndInvoke.
5. In the second part of an asynchronous call operation, name the four ways that the caller can know when the asynchronous operation has completed. Callback method, poll, call end method, wait for event.
109
THIS PAGE INTENTIONALLY LEFT BLANK
Contents
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
Overview
1
Integration Services
2
Platform Invoke
7
Lab 15.1: Calling Win32 APIs
16
Calling COM Objects from Managed Code
20
Lab 15.2: Calling COM Objects
39
Calling .NET Objects from COM Objects
43
Review
55
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places, and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, places or events is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. 2001-2002 Microsoft Corporation. All rights reserved. Microsoft, ActiveX, BizTalk, IntelliMirror, Jscript, MSDN, MS-DOS, MSN, PowerPoint, Visual Basic, Visual C++, Visual C#, Visual Studio, Win32, Windows, Windows Media, and Window NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
iii
Instructor Notes Presentation: 90 Minutes Lab: 60 Minutes
After completing this module, students will be able to: !
State the need for interoperability between Microsoft® .NET assemblies and COM components.
!
Use platform invoke to call a function in a dynamic-link library (DLL).
!
Expose the methods and properties of a COM object to an assembly.
!
Describe the three ways to generate runtime callable wrappers.
!
Use Microsoft Visual Studio® .NET to call a COM object.
!
Use the Type Library Importer to generate metadata from a type library.
!
Expose the methods and properties of a .NET Framework class to a COM client.
!
Use the Type Library Exporter to generate a type library for an assembly.
!
Use the ClassInterfaceAttribute to control and modify the type of interface that is generated for a .NET Framework class.
Materials and Preparation This section provides the materials and preparation tasks that you need to teach this module.
Required Materials To teach this module, you need the Microsoft PowerPoint® file 2349B_15.ppt.
Preparation Tasks To prepare for this module, you should: !
Read all of the materials for this module.
!
Complete the lab.
iv
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
Demonstrations The following demonstrations are used to show how to create a managed assembly from an unmanaged DLL file, and how to create a COM component from a managed assembly. In this module, use a Visual Studio .NET Command Prompt window to obtain a command prompt window with the appropriate environment values that are required for .NET tools.
Demonstration: Using the Type Library Importer Before running the demo, the COM object needs to be registered in the system registry. Important To use Microsoft Visual Studio .NET tools within a command prompt window, the command prompt window must have the proper environment settings. The Visual Studio .NET Command Prompt window provides such an environment. To run a Visual Studio .NET Command Prompt window, click Start, All Programs, Microsoft Visual Studio .NET, Visual Studio .NET Tools, and Visual Studio .NET Command Prompt.
! To register the COM object 1. At the command prompt, change the current folder to \ Democode\Mod15\Demo15.1. 2. Run regsvr32 TLBIMPDemo.dll to register the COM object. 3. A dialog box appears informing you that the registration succeeded. To dismiss the dialog box, click OK. In this demonstration, students will see how to use the Type Library Importer to generate metadata for a DLL, and then use the metadata to create a managed wrapper for the component.
! To create a managed wrapper for the TLBIMPDemo component and generate metadata by using the Type Library Importer
1. Run the Type Library Importer on TLBIMPDemo.dll, by typing the following command in a Visual Studio .NET Command Prompt window: tlbimp TLBIMPDemo.dll /out:Calculator.dll
The resulting DLL is a .NET assembly and is named Calculator.dll. 2. Build the calculator application by typing the following command: csc /reference:Calculator.dll CalcUI.cs
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
! To run the calculator application and view the metadata of Calculator.dll
1. Run CalcUI.exe from the command prompt by typing the following command: CalcUI
2. Exit CalcUI.exe. 3. In a Visual Studio .NET Command Prompt window, run the Microsoft intermediate language (MSIL) Disassembler on Calculator.dll by typing the following command: ildasm /adv Calculator.dll
4. To display the contents of the manifest, double-click MANIFEST. 5. Notice the assembly Calculator that appears near the end of the manifest. It should look similar to the following example: .assembly Calculator { .custom instance void! [mscorlib]System.Runtime.InteropServices.GuidAttribute:: .ctor(string) = (… .custom instance void! [mscorlib]System.Runtime.InteropServices.! ImportedFromTypeLibAttribute::.ctor(string) = …! // ...TLBIMPDemo.. .hash algorithm 0x00008004 .ver 1:0:0:0 }
6. This entry in the manifest demonstrates the referencing of an unmanaged COM component from managed code through the interoperability services provided by the .NET Framework.
v
vi
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
Demonstration: Using the Type Library Exporter In this demonstration, students will see how to use the Type Library Exporter to generate a type library that describes the types defined in a common language runtime assembly.
! To generate a type library from an assembly by using the Type Library Exporter
1. In a Visual Studio .NET Command Prompt window, change the current folder to \Democode\Mod15\Demo15.2. 2. To create a type library for Calculator.dll, type the following command: tlbexp Calculator.dll /out:Calculator.tlb
3. The Type Library Exporter creates the Calculator.tlb file.
! To view the contents of the type library 1. Start a Visual Studio .NET Command Prompt. 2. Type oleview and press Enter. 3. In the OLE/COM Object Viewer window, on the File menu, click View TypeLib. 4. In the Open dialog box, move to \Democode\ Mod15\Demo15.2, select Calculator.tlb, and then click Open. The ITypeLib Viewer window appears with the information that is found in the type library.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
vii
Module Strategy Use the following strategy to present this module: !
Integration Services This module provides an introduction to COM interoperability and platform invoke. Ensure that students understand the difference between calling an API function that is implemented in a DLL and calling a COM component.
!
Platform Invoke Concentrate on the information in the slides titled “How Platform Invoke Works” and “Calling Unmanaged Functions”.
!
Calling COM Objects from Managed Code Concentrate on the procedure for generating runtime callable wrappers, the use of the type library importer, and the process for signature translation and error handling.
!
Calling .NET Objects from COM Objects Ensure that students understand the export process when the Type Library Exporter is used to generate a type library.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
1
Overview Topic Objective
To provide an overview of the module topics and objectives.
Lead-in
In this module, you will learn about interoperating between managed and unmanaged code.
!
Integration Services
!
Platform Invoke
!
Calling COM Objects from Managed Code
!
Calling .NET Objects from COM Objects
*****************************ILLEGAL FOR NON-TRAINER USE****************************** After completing this module, you will be able to: !
State the need for interoperability between Microsoft® .NET assemblies and COM components.
!
Use platform invoke to call a function in a dynamic-link library (DLL).
!
Expose the methods and properties of a COM object to an assembly.
!
Describe the three ways to generate runtime callable wrappers.
!
Use Microsoft Visual Studio® .NET to call a COM object.
!
Use the Type Library Importer to generate metadata from a type library.
!
Expose the methods and properties of a .NET Framework class to a COM client.
!
Use the Type Library Exporter to generate a type library for an assembly.
!
Use the ClassInterfaceAttribute to control and modify the type of interface that is generated for a .NET Framework class.
2
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
" Integration Services Topic Objective
To introduce the services provided by the .NET Framework that allow managed code to interoperate with unmanaged code.
!
Introduction to Platform Invoke
!
Introduction to COM Interoperability
!
Interop Marshaling Overview
Lead-in
The .NET Framework provides two services that enable managed code to interoperate with unmanaged code. This section provides an overview of these two services.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** When you adopt the .NET Framework to develop your applications, you may still have a lot of unmanaged code libraries implemented as DLLs and COM components. Code executing under the control of the runtime is called managed code. Conversely, code that runs outside the runtime is called unmanaged code. COM components, Microsoft ActiveX® interfaces, and Microsoft Win32® API functions are examples of unmanaged code. You do not have to convert all of your existing unmanaged code to managed code before you can use it with .NET Framework applications. The Microsoft .NET Framework promotes interaction with COM components, COM+ services, external type libraries, and many operating system services. Data types, method signatures, and error-handling mechanisms vary between managed and unmanaged object models. The .NET Framework offers two services—platform invoke and COM interoperability services—that allow managed code to interoperate with existing unmanaged code. Note The ActiveX Control Importer (Aximp.exe) can be used to convert type definitions in a COM type library for an ActiveX control into a Microsoft Windows® Forms control. For additional information, see “Tools and Debuggers; Windows Forms ActiveX Control Importer (Aximp.exe)” in the .NET Framework SDK documentation.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
3
Introduction to Platform Invoke Topic Objective
To provide an overview of platform invoke.
Lead-in
Platform invoke allows managed code to interoperate with unmanaged APIs.
!
Allows Managed Code to Call Unmanaged Functions That Are Implemented in a DLL
!
Provides the Mechanism for #
Finding and invoking unmanaged functions
#
Marshaling managed arguments to and from unmanaged code
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Platform invoke allows managed code to call unmanaged functions that are implemented in a DLL. Platform invoke provides the mechanism for finding and invoking unmanaged functions, as well as marshaling managed arguments to and from unmanaged code. When managed code calls an unmanaged function that is implemented in a DLL, platform invoke locates the DLL that implements the function, loads the DLL into memory, and locates the function address in memory. It then pushes the function’s arguments onto the stack, marshals any data that has to be marshaled, enables pre-emptive garbage collection, and transfers control to the address of the unmanaged code.
4
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
Introduction to COM Interoperability Topic Objective
To provide an overview of COM interoperability services provided by the .NET Framework.
!
COM and .NET Differences Include: #
Lead-in
COM interoperability services allow managed code to interoperate with COM components.
!
#
Service discovery - QueryInterface versus Reflection
#
Object memory – fixed versus movable
Runtime Callable Wrapper (RCW) #
!
Lifetime management - reference counting versus garbage collection
Used by managed clients to call a method on a COM object
COM Callable Wrapper (CCW) #
Used by COM clients to call a method on a managed object
*****************************ILLEGAL FOR NON-TRAINER USE****************************** COM differs from the .NET Framework object model in several important ways: !
Clients of COM objects must manage the lifetime of those objects; the common language runtime manages the lifetime of objects in its environment.
!
Clients of COM objects discover whether a service is available by requesting an interface that provides that service and getting back an interface pointer, or not. Clients of .NET objects can obtain a description of an object's functionality using Reflection.
!
Managed objects reside in memory managed by the .NET Framework execution environment. The execution environment can move objects around in memory for performance reasons and update all references to the objects it moves. Unmanaged clients, having obtained a pointer to an object, rely on the object to remain at the same location. These clients have no mechanism for dealing with an object whose location is not fixed.
To overcome these differences, the runtime provides wrapper classes to make both managed and unmanaged clients think they are calling objects within their respective environment. Whenever your managed client calls a method on a COM object, the runtime creates a runtime callable wrapper (RCW). RCWs abstract the differences between managed and unmanaged reference mechanisms, among other things. The runtime also creates a COM callable wrapper (CCW) to reverse the process, enabling a COM client to call a method on a .NET object.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
5
Interop Marshaling Overview Topic Objective
To provide an overview of interop marshalling.
!
Lead-in
Interop marshaling governs how data is passed between managed and unmanaged memory during calls #
Interop marshaling governs how data is passed in method arguments and return values between managed and unmanaged memory during calls.
!
Is a run time activity performed by the common language runtime marshaling service
Blittable data types are common to managed and unmanaged memory and require no conversion – data types (C# keyword) : #
System.Byte (byte)
System.SByte (sbyte)
#
System.Int16 (short)
System.UInt16 (ushort)
#
System.Int32 (int)
System.UInt32 (uint)
#
System.Int64 (long) System.IntPtr System.UIntPtr
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Interop marshaling governs how data is passed in method arguments and return values between managed and unmanaged memory during calls. Interop marshaling is a run-time activity performed by the common language runtime's marshaling service. Most data types have a common representation in both managed and unmanaged memory and do not require special handling by the interop marshaler. These types are called blittable types because they do not require conversion when passed between managed and unmanaged code. The following types from the System namespace are blittable types: !
System.Byte
!
System.SByte
!
System.Int16
!
System.UInt16
!
System.Int32
!
System.UInt32
!
System.Int64
!
System.IntPtr
!
System.UintPtr
6
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
Interop Marshaling Overview (continued) Topic Objective
To complete the overiew of interop marshalling.
Lead-in
Let’s discuss how nonblittable types differ from blittable types with regard to Interop marshaling and how custom attributes may be used to in place of the standard marshaling operations.
!
Non-blittable types have different or ambiguous representations in managed and unmanaged languages and may require conversion #
!
For example, managed strings are non-blittable types
The standard RCW or CCW usually provides adequate marshaling for calls that cross the boundary between COM and the .NET Framework #
Custom attributes can optionally adjust the way the runtime represents managed and unmanaged code
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Non-blittable types have different or ambiguous representations in managed and unmanaged languages. These types may require conversion when they are marshaled between managed and unmanaged code. For example, managed strings are non-blittable types because they can have several different unmanaged representations, some of which can require conversion. The following table lists non-blittable types from the System namespace. Delegates, which are data structures that refer to a static method or to a class instance, are also non-blittable. Non-blittable type
Description
System.Array
Converts to a C-style or a SAFEARRAY.
System.Boolean
Converts to a 1, 2, or 4-byte value with true as 1 or -1.
System.Char
Converts to a Unicode or ANSI character.
System.Class
Converts to a class interface.
System.Object
Converts to a variant or an interface.
System.Mdarray
Converts to a C-style array or a SAFEARRAY.
System.String
Converts to a null-terminated string or to a BSTR.
System.Valuetype
Converts to a structure with a fixed memory layout.
System.Szarray
Converts to a C-style array or a SAFEARRAY.
In most cases, the standard RCW or CCW that is generated by the runtime provides adequate marshaling for calls that cross the boundary between COM and the .NET Framework. Using custom attributes, you can optionally adjust the way the runtime represents managed and unmanaged code.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
7
" Platform Invoke Topic Objective
To introduce platform invoke.
Lead-in
In this section you will learn how platform invoke can be used to call unmanaged code.
!
How Platform Invoke Works
!
Calling a Win32 API From Managed Code
!
Calling Unmanaged Functions
!
Pinning
!
Marshaling
!
Performance Considerations and Limitations of Platform Invoke
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Platform invoke allows managed code to call unmanaged functions that are implemented in a DLL. Platform invoke is primarily used to call C functions that are part of the Win32 API. To use these functions, you must import the DLL that hosts the functions. The .NET Framework provides default marshaling to marshal data between managed and unmanaged code. You can, however, override the marshaling provided by the .NET Framework whenever you require custom marshaling to be performed.
8
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
How Platform Invoke Works Topic Objective
To describe how platform invoke executes methods that are implemented in a DLL.
Lead-in
This is how platform invoke loads and executes a method that is implemented in a DLL.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** When platform invoke calls an unmanaged function, it performs the following sequence of actions: 1. Platform invoke locates the DLL containing the function. 2. It loads the DLL into memory. 3. It locates the address of the function in memory and pushes its arguments onto the stack, marshaling data as required. 4. Platform invoke then transfers control to the unmanaged function. You provide the DLL and function information to platform invoke by annotating your code. This information is used by platform invoke to locate the DLL and load it into the memory of the process, after which the address of the function is located and control is transferred.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
9
Calling a Win32 API From Managed Code Topic Objective
To describe how to call a Win32 API from managed code.
!
Declare the Method with the static and extern C# Keywords
!
Import the DLL That Implements the Unmanaged Function That You Wish to Call
Lead-in
To use an unmanaged function that is implemented in a DLL, you must first import the DLL into your code.
[DllImport(, name>, EntryPoint=, name>, CharSet=] characterset>] !
Optionally, Specify Custom Marshaling Information to Override the .NET Framework Default Marshaling
*****************************ILLEGAL FOR NON-TRAINER USE****************************** To call an unmanaged function, the first step is to annotate your code by importing the DLL file in which the function is implemented. You use the DllImport attribute to import the DLL. The DllImport attribute requires the DLL_name argument, and also takes any of the following additional arguments. Argument
Description
EntryPoint
Specifies the DLL entry point to be called.
CharSet
Controls name mangling and the way that string arguments should be marshaled to the function. The default is CharSet.Ansi.
ExactSpelling
Prevents an entry point from being modified to correspond to the character set. If CharSet.Auto is enabled, the default is False. Otherwise, the default is True.
CallingConvention
Specifies the calling-convention values used in passing method arguments. The default is Winapi.
PreserveSig
Indicates that the managed method signature should not be transformed into an unmanaged signature that returns an HRESULT, and might have an additional [out, retval] argument for the return value. The default is True (the signature should not be transformed).
SetLastError
Enables the caller to use the GetLastError Win32 API function to determine whether an error occurred while executing the method. In Microsoft Visual Basic®, the default is True; in C# and C++, the default is False.
If the DLL has both an ANSI and a Unicode implementation of the same function, then the CharSet parameter specifies which version of the function is called.
10
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
Calling Unmanaged Functions Topic Objective
To describe how to annotate unmanaged method calls so that they can be called from .NET Framework applications.
Lead-in
Unmanaged methods that are implemented as a DLL and called from managed code must be annotated before they can be executed by the runtime.
!
To Call a Function That Is Implemented in an Unmanaged DLL # #
#
Add a using statement for System.Runtime.InteropServices Add a DllImport attribute to a method, specifying the name of the unmanaged DLL that exports the function to be called Declare with the static and extern C# keywords the method that is used to call unmanaged code without providing any implementation for the method
using using System.Runtime.InteropServices; System.Runtime.InteropServices; …… [DllImport("msvcrt.dll", [DllImport("msvcrt.dll", CharSet=CharSet.Ansi)] CharSet=CharSet.Ansi)] public public static static extern extern int int puts(String puts(String str); str);
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Delivery Tip
Refer to the .NET SDK for more information about DllImport, calling conventions, marshaling, and character sets.
When you call unmanaged functions from .NET Framework applications, you must add a DllImport attribute to a method that is to be used by platform invoke. The DllImport attribute is defined in System.Runtime.InteropServices. Platform invoke uses the information in the attribute to call the appropriate underlying DLL function, marshal parameters to the function, and return values from the function when the method is called. The example on the slide shows the use of the DllImport attribute. To annotate a method in managed code that is used to call a function in an unmanaged DLL, you must specify the name of the DLL that exports the unmanaged function. You must also specify with the static and extern C# keywords the name of the exported function or its equivalent entry point ordinal, if the exported function name is different than the name of the method that you declared in the managed module. Optionally, you can specify a flag to indicate the underlying calling convention and to automatically marshal string data types correctly. The calling convention flag can be set to Cdecl, FastCall, StdCall, ThisCall, or Winapi. If no calling convention is specified, Winapi is the default. You can also optionally specify the character set to use as part of the function call. The values that you can specify for the character set are ansi, unicode, or auto.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
11
There are two general approaches when declaring methods that match unmanaged functions: !
You can declare the method to call the unmanaged DLL without providing any implementation for the method, as shown in the following example: using System; using System.Runtime.InteropServices; … [DllImport("msvcrt.dll", CharSet=CharSet.Ansi)] public static extern int puts(String str); … static void Main() { String Str = "Hello World!"; puts(Str); }
The compiler uses the method declaration to generate the metadata that the runtime needs to locate and correctly marshal the call to the DLL. When declaring the method in your managed code, you can substitute the equivalent runtime types for the function’s types. In the previous example, a string object is substituted for a character string. !
You can also declare a .NET Framework class that is a collection of declarations for an API exported by an unmanaged DLL. The advantage of creating such a class is that you do not have to declare and annotate the methods for the unmanaged DLL that you want to call in all the applications that you create. After it is created, you can include the class in your application, and you can call the unmanaged functions that are declared in the class as if they were .NET Framework methods.
Annotated methods behave like managed .NET Framework methods. When an annotated method is called, platform invoke calls the unmanaged function, marshaling any data that needs to be marshaled to and from the unmanaged function.
12
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
Pinning Topic Objective
To define pinning.
Lead-in
When data is marshaled between managed and unmanaged code, it can either be copied from one memory location to another, or it can be pinned.
!
Data Is Temporarily Locked in Its Current Memory Location to Keep It from Being Relocated by the Common Language Runtime’s Garbage Collector
!
Pinning Is Performed When Data Has to Be Passed Between Managed and Unmanaged Code and #
Has a fixed layout and common data representation in both managed and unmanaged memory - or -
#
Has a fixed layout but the data representation is different in managed and unmanaged memory and the class is marshaled by reference
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Pinning is a technique in which data is temporarily locked in its current memory location to keep it from being relocated during garbage collection. Whether data is copied or pinned during the marshaling process depends on the type of the data and its InAttribute and OutAttribute. When classes that have a fixed layout and common data representation in both managed and unmanaged memory require marshaling, a pointer to the actual object is passed to the called function. The called function is then free to change the contents of the memory location being referenced by the pointer. When classes have a fixed layout but the data representation is different in managed and unmanaged memory and the class is marshaled by reference, the called function receives a pointer to a copy of the data structure. If the InAttribute is set, the copy is always initialized with the instance’s state. If the OutAttribute is set, the state is always copied back in to the instance upon return. If both the InAttribute and OutAttribute are set, the copy is always initialized with the instance’s state, and the last state is always copied back into the instance on return. If either attribute is omitted, the runtime may choose to eliminate either copy as an optimization. When classes that do not have a fixed layout—such as System.String and System.Text.StringBuilder—have to be marshaled by value or by reference to unmanaged code, the runtime typically copies the data of either type to a secondary buffer and passes a reference to the buffer to the called function. The reference is always allocated with CoTaskMemAlloc.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
13
Marshaling Topic Objective
To describe how marshalling is performed by platform invoke.
!
Platform Invoke Marshals Simple Data Types Between Managed and Unmanaged Code
!
Custom Marshalling Can Be Specified by Using the MarshalAs Attribute
Lead-in
Platform invoke automatically marshals data between unmanaged types and managed types.
public public static static extern extern int int MessageBoxW( MessageBoxW( int h, int h, [MarshalAs(UnmanagedType.LPWStr)] [MarshalAs(UnmanagedType.LPWStr)] string string m, m, …); …);
*****************************ILLEGAL FOR NON-TRAINER USE****************************** For arguments that are simple data types—such as bytes or integers—platform invoke marshals the managed argument to the corresponding unmanaged data type. The following table shows how the data types used in the Win32 API (wtypes.h) should be expressed in C#. Data Type in wtypes.h
C# Data Type
HANDLE
int
BYTE
byte
SHORT
short
WORD
ushort
INT
int
UINT
uint
LONG
int
ULONG
uint
BOOLEAN
int
CHAR
char
LPSTR (and most other string types)
String for in, StringBuilder for inout
FLOAT
float
DOUBLE
double
If any parameter is passed by reference, the ref keyword in C# should be added to the method declaration used to call unmanaged code.
14
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
For example, the GetUserNameEx Win32 API function has the following declaration: BOOLEAN GetUserNameEx( EXTENDED_NAME_FORMAT NameFormat, LPTSTR lpNameBuffer, PULONG nSize );
// name format // name buffer // size of name buffer
To call this from managed code, you would use the following declaration: [DllImport("secur32.dll", CharSet=CharSet.Auto)] public static extern int GetUserNameEx (int nameFormat, StringBuilder userName, ref uint userNameSize);
Notice that an integer is substituted for the enumeration parameter, a StringBuilder object is substituted for the string parameter, and the ref keyword is added to the last parameter, because it is passed by reference. Your choice of data types may affect applications when the data types are marshaled to and from managed code. For example, in Win32 APIs, a LONG data type is 32 bits, whereas the C# long and Visual Basic .NET Long data types are 64 bits. When calling API functions, you can specify custom marshaling attributes by adding a MarshalAsAttribute to the parameters of the function. You can also use the MarshalAsAttribute with structures. When using the MarshalAsAttribute with structures, you must also use the StructLayout attribute to set the native layout of the structure.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
15
Performance Considerations and Limitations of Platform Invoke Topic Objective
To list some performance considerations and limitations of using platform invoke.
!
Platform Invoke Supports Only Integer Arguments to Callback Functions
Lead-in
!
Platform Invoke Does Not Support All Data Types
!
Platform Invoke Supports Calling Only Global Functions Exported from the DLL
!
Array Arguments That Are Passed by Reference and Copied Back Are Resized to 1
When calling unmanaged code from managed code by using platform invoke, you must keep the following limitations and performance considerations in mind.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Some of the limitations that you must keep in mind when calling unmanaged functions by using platform invoke are: !
Platform invoke supports the callback mechanism. However, only integers are currently permitted as the arguments to the callback function.
!
Platform invoke does not support all data types.
!
Platform invoke supports calling only global functions exported from the DLL.
!
When you call an unmanaged function, passing a reference to an array argument, all the array elements are copied to an unmanaged buffer. When the values are copied back, the size of that unmanaged buffer is no longer known. Therefore, only one element is copied back. The array is resized to 1.
16
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
Lab 15.1: Calling Win32 APIs Topic Objective
To introduce the lab.
Lead-in
In this lab, you will call a Win32 API from managed code.
*****************************ILLEGAL FOR NON-TRAINER USE******************************
Objectives After completing this lab, you will be able to call a Win32 API from managed code.
Estimated time to complete this lab: 30 minutes
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
17
Exercise 1 Calling a Win32 API from Managed Code In this exercise, you will call Win32 APIs from managed code. This includes adding DllImport attributes to methods in your class, and then invoking those methods. The first unmanaged function you call retrieves the user name, and the second unmanaged function you call displays the user name in a message box.
! Open the Visual Studio .NET project for this exercise and examine the application UI
1. In Microsoft Windows® Explorer, move to \Labs\ Lab15\Lab15.1\Starter. 2. Double-click CallingUnmanagedCode.csproj to open the project for this exercise in Visual Studio .NET. 3. In the Solution Explorer pane, double-click ManagedCode.cs. The UI design for the Windows Forms application appears. Examine the UI for a moment to become familiar with it. 4. Press F7 to edit the code that implements the Windows Form application.
! Add the DllImport attribute for the method used to retrieve the user name
• GetUserNameEx is the Win32 API to be called. The signature for this function is as follows: BOOLEAN GetUserNameEx( EXTENDED_NAME_FORMAT NameFormat, LPTSTR lpNameBuffer, PULONG nSize );
// name format // name buffer // size of name buffer
In the ManagedCode.cs file, notice the ‘TODO’ comment near the beginning, instructing you to add the declaration for a method that is used to call GetUserNameEx. Add the declaration for the method, including the DllImport attribute. Your method should also be named GetUserNameEx. Some key information you need in order to add the declaration is listed in the following table. Information
Value
DLL containing the GetUserNameEx function
secur32.dll
Character set to use
CharSet.Auto
For information about the managed data types to use in your method declaration, see Marshaling earlier in this module. Notice that you can use the task list tab in the lower-left hand pane to find the ‘TODO’ comments. Click View, click Show Tasks, and then click All if the ‘TODO’ comments do not appear in the task list.
18
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
! Add the DllImport attribute for the method used to display the user name
• MessageBox is the Win32 API to be called. The signature for this function is as follows: int MessageBox( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType
// // // //
handle to owner window text in message box message box title message box style);
In the ManagedCode.cs file, notice the next ‘TODO’ comment near the beginning, instructing you to add the declaration for a method that is used to call MessageBox. Add the declaration for the method, including the DllImport attribute. Your method should also be named MessageBox. Some key information you need in order to add the declaration is listed in the following table. Information
Value
DLL containing the MessageBox function
user32.dll
Character set to use
CharSet.Auto
For information about the managed data types to use in your method declaration, see Marshaling earlier in this module. Notice that the strings passed to MessageBox are input-only parameters.
! Implement the methods that call unmanaged code 1. Scroll down in the ManagedCode.cs file to find the next ‘TODO’ comment near the end of the file in the buttonRetrieveUserName_Click method. The comment instructs you to add the code to retrieve the user name and display it in a message box. Replace the comment with a call to GetUserNameEx followed by a call to MessageBox. The SamCompatibleNameFormat constant represents the format of the username that will be returned from GetUserNameEx. Note that GetUserNameEx returns a non-zero value if it succeeds. Otherwise it returns a value of zero. For the uType parameter to MessageBox, you should pass in (uint)MessageBoxIcon.Information if GetUserNameEx succeeds, or (uint)MessageBoxIcon.Error if GetUserNameEx fails. MessageBox returns a value for the button clicked on the message box, but this should not affect the application. 2. Scroll down to the catch block to find the next ‘TODO’ comment instructing you to add a MessageBox call to report the exception as an error. Replace the comment with a call to MessageBox after the exception message is created. Pass in (uint)MessageBoxIcon.Error for the uType parameter.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
! Build and test your application 1. On the File menu, click Save All. 2. On the Build menu, click Build Solution. Your application should build successfully. The following statement should appear as the last line of the output window: Build: 1 succeeded, 0 failed, 0 skipped.
3. On the Debug menu, click Start, and then click Retrieve User Name. The user name should be displayed in a message box. Click OK to dismiss the message box. 4. To close the application, click Exit.
19
20
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
" Calling COM Objects from Managed Code Topic Objective
To introduce how COM objects are exposed to managed code.
Lead-in
In this section you will learn how to expose COM objects to managed code by using runtime callable wrappers.
!
Runtime Callable Wrappers
!
Generating Runtime Callable Wrappers
!
Threading Models
!
Signature Translation and Error Handling
!
Marshaling
!
Performance and Security Issues
!
Best Practices
*****************************ILLEGAL FOR NON-TRAINER USE****************************** COM objects are exposed to managed code by creating runtime callable wrappers for the objects. The purpose of the wrappers is to marshal calls between a .NET Framework client and a COM object.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
21
Runtime Callable Wrappers Topic Objective
To describe the purpose of runtime callable wrappers.
Lead-in
The primary goal of runtime callable wrappers is to hide the differences between the managed and unmanaged programming models.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In order to call a COM object, the runtime uses a runtime callable wrapper. Runtime callable wrappers contain all of the information needed to call the COM object. The runtime callable wrapper maintains a cache of interface pointers on the COM object it wraps and releases its reference on the COM object when the wrapper is no longer needed. The runtime performs garbage collection on the runtime callable wrapper after the COM object has been released. One of the primary functions of the runtime callable wrapper is to marshal data between managed and unmanaged code. The runtime callable wrapper provides marshaling for method arguments and method return values whenever the client and server have different representations of the data passed between them. For example, when a .NET Framework client passes a String type as part of an argument to a managed object, the wrapper converts the String type to a BSTR type. Should the COM object return a BSTR type to its managed caller, the caller receives a String type. Both the client and the server send and receive data types that are familiar to each of them. Some other types require no conversion. For instance, a standard wrapper will always pass a 4-byte integer between managed and unmanaged code without converting the type. The runtime callable wrapper implements the interfaces that the COM object implements and exposes the methods, properties, and events from the object’s interfaces. In the illustration, the wrapper exposes the INew interface and all its methods and properties to the client, but consumes the IUnknown and IDispatch interfaces. The IUnknown interface is used by the runtime to identify the COM object, provide type coercion, and control lifetime management. The runtime distinguishes between COM objects by comparing the value of the IUnknown interface for each object. The wrapper uses the QueryInterface method to get and hold a reference to an unmanaged object. The reference is retained until the runtime performs garbage collection on the wrapper. The wrapper releases the unmanaged object during garbage collection.
22
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
The runtime callable wrapper also consumes the interfaces listed in the following table. Interface
Description
IErrorInfo
If the COM object being wrapped implements the IErrorInfo interface, the exceptions generated by the wrapper contain the information provided by the interface
IProvideClassInfo
If the COM object being wrapped implements the IProvideClassInfo interface, the wrapper extracts the type information from this interface to provide better type identity
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
23
Generating Runtime Callable Wrappers Topic Objective
To define the three ways to generate runtime callable wrappers.
!
Lead-in
Runtime callable wrappers are generated in one of three ways.
Runtime Callable Wrappers Are Generated in One of Three Ways: #
Adding a reference to a COM component in a Visual Studio .NET project
#
Employing the Type Library Importer
#
Creating custom wrappers
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Delivery Tip
You may want to create an RCW and use the Microsoft Intermediate Language disassembler to show what the metadata looks like.
Runtime callable wrappers are generated in one of three ways: !
Adding a reference to a COM component in a Visual Studio .NET project This automatically converts COM types in a type library to metadata in an assembly.
!
By using the Type Library Importer This provides command-line switches to adjust metadata in the resulting assembly file, imports types from an existing type library, and generates an assembly and a namespace.
!
Creating custom wrappers As a less-desirable option, you can create type definitions. This requires advanced programming skills.
Visual Studio .NET generates an assembly containing metadata when you add a reference to a specific type library.
! To add a reference to a type library in Visual Studio .NET 1. Install the COM DLL or EXE file manually on your computer and use Regsrv32.exe to add a component to the registry, unless a Microsoft Windows installation program performs the installation for you. 2. In Visual Studio .NET, on the Project menu, click Add Reference. 3. Click the COM tab. 4. In the Component Name list, double-click the type library, and then click OK.
24
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
The Type Library Importer (Tlbimp.exe) is a command-line tool that converts the coclasses and interfaces contained in a COM type library to metadata. This tool creates an assembly and namespace for the type information automatically. After the metadata of a class is available, managed clients can create an instance of the COM type and call its methods, as if it were a .NET Framework instance. The Type Library Importer converts an entire type library to metadata at once but cannot generate type information for a subset of the types defined in a type library.
! To generate an assembly from a type library • To produce the Loanlib.dll assembly in the Loanlib namespace, type the following command: tlbimp Loanlib.tlb
Adding the /out: switch produces an assembly with an altered name, such as Loanlib_RCW.dll. Altering the runtime callable wrapper assembly name can help distinguish it from the original COM DLL. The following example uses the /out: switch of the Type Library Importer to alter the generated assembly’s name: tlbimp Loanlib.dll /out: Loanlib_RCW.dll
When a type library is unavailable or incorrect, one option is to create a duplicate definition of the class or interface in managed source code. You then compile the source code with a compiler that targets the runtime in order to produce metadata in an assembly. To define COM types manually, you must have access to the following items: !
Precise descriptions of the coclasses and interfaces being defined.
!
A compiler, such as the C# compiler, that can generate the appropriate .NET Framework class definitions.
!
Knowledge of the rules for conversion from a type library to an assembly.
Writing a custom wrapper is an advanced technique that you seldom perform. However, if you need to create custom wrappers, there are two methods to do this: !
If you have access to the Interface Definition Language (IDL) source, you can modify the source by applying type library file attributes and import the type library.
!
You can apply interop-specific attributes to imported types and generate a new assembly.
For additional information on generating a custom wrapper, see “Customizing Standard Wrappers” in the .NET Framework SDK documentation.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
25
Demonstration: Using the Type Library Importer Topic Objective
To demonstrate how to use the Type Library Importer to convert the type definitions found within a COM type library into equivalent definitions in a .NET assembly.
Lead-in
In this demonstration you will learn how to use the Type Library Importer to convert the type definitions found within a COM type library into equivalent definitions in a .NET assembly.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The Type Library Importer (Tlbimp.exe) can be used to convert the coclasses and interfaces found within a COM type library into equivalent definitions in a .NET assembly. The output of the Type Library Importer is an assembly that contains metadata for the types defined within the original type library. The Type Library Importer performs the following tasks: !
Converts unmanaged COM coclasses to C# classes with a constructor (without parameters) and no other methods.
!
Converts unmanaged COM vtable interfaces to C# interfaces.
!
Converts unmanaged COM structures to C# structures with public fields.
The syntax for using the Type Library Importer is as follows: tlbimp inputFile [options]
In the previous example, inputFile is the name of the file that contains the COM type library. This file can be either a stand-alone type library (.TLB) file or a DLL. The following command imports a type library file and generates a .NET assembly named myTest.dll: tlbimp myTest.tlb
26
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
The /out: option specifies the name of the assembly that is created as a result of running the Type Library Importer. The following command creates myNewTest as a DLL: tlbimp myTest.tlb
/out:myNewTest.dll
If the output assembly’s file name is equivalent to the input type library’s file name, the Type Library Importer will generate an error, because the input file cannot be overwritten. The /delaysign option delays signing of the assembly. The /delaysign option must be used with the /keycontainer:, /keyfile:, or /publickey: option. For a complete list of all the options that can be used with the Type Library Importer, see “Type Library Importer (Tlbimp.exe)” in the .NET Framework SDK documentation.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
27
Threading Models Topic Objective
To describe the threading models in managed and unmanaged code.
Lead-in
The threading models that are supported by COM components and the .NET Framework are different. While managed objects use synchronized regions…
!
COM Components Use Apartments to Synchronize Access to Managed Resources
!
Runtime Creates/Initializes Apartment When Calling a COM Object
using using System.Threading; System.Threading; using using APTOBJLib; APTOBJLib; …… Thread.CurrentThread.ApartmentState Thread.CurrentThread.ApartmentState == ApartmentState.STA; ApartmentState.STA; AptSimple AptSimple obj obj == new new AptSimple AptSimple (); (); obj.Counter obj.Counter == 1; 1; !
To ensure that the main thread of an application is STA
…… [STAThread] [STAThread] static static void void Main() Main() ……
*****************************ILLEGAL FOR NON-TRAINER USE****************************** COM components use apartments to synchronize access to managed resources. In contrast, managed objects use synchronized regions; synchronization primitives such as mutexes, locks, and completion ports; and synchronized contexts to ensure that all shared resources are used in a thread-safe manner. For interoperability, the common language runtime creates and initializes an apartment when calling a COM object. A managed thread can create and enter a single-threaded apartment (STA) that contains only one thread, or a multithreaded apartment (MTA) that contains one or more threads. When a COM apartment and a thread-generated apartment are compatible, COM permits the calling thread to make calls directly to the COM object. If the apartments are incompatible, COM creates a compatible apartment and marshals all calls through a proxy in the new apartment. On the first call to unmanaged code, the runtime calls CoInitializeEx to initialize the COM apartment as either an MTA or an STA. You can control the type of apartment created by setting the System.Threading.ApartmentState property on the thread to MTA, STA, or Unknown. As long as the proxy and stub are registered or the type library is registered, you do not have to set this property. If neither the proxy and stub nor the type library is registered, an InvalidCastException can occur when calling a COM object from managed code.
28
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
The following table lists the ApartmentState enumeration values and shows the comparable COM apartment initialization call. ApartmentState enumeration value
COM apartment initialization
MTA
CoInitializeEx(NULL, COINIT_MULTITHREADED)
STA
CoIntializeEx(NULL, COINIT_APARTMENTTHREADED)
Unknown
CoInitializeEx(NULL, COINIT_MULTITHREADED)
Whenever the COM object and the managed thread are in incompatible apartments, all calls on the object are made through a COM created proxy. For example, calls between Windows Forms controls whose COM objects must reside in an STA and managed code whose thread’s ApartmentState is MTA. The following example shows how to create an STA apartment-threaded COM object, AptSimple, from managed code: using System.Threading; using APTOBJLib; … AptSimple obj = new AptSimple (); obj.Counter = 1;
To eliminate the use of proxies and stubs and significantly enhance performance, set the ApartmentState on the thread before creating the object, as shown in the following example: using System.Threading; using APTOBJLib; … Thread.CurrentThread.ApartmentState = ApartmentState.STA; AptSimple obj = new AptSimple (); obj.Counter = 1;
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
29
A thread can only initialize a COM apartment once. Since the runtime initializes the apartment before making the very first call to unmanaged code on that thread, you should set the ApartmentState as early as possible. Changing the ApartmentState after the apartment has been initialized has no effect. You can neither un-initialize nor re-initialize an apartment. In some situations, the thread may already have called into unmanaged code before the ApartmentState could be set. In such cases, you cannot change the apartment type after the thread is initialized. Your only option is to create a new thread. As an alternative to setting the ApartmentState enumeration, you can apply the System.STAThreadAttribute or System.MTAThreadAttribute to the main entry point of the application. By applying these attributes you ensure that the main thread of an application is in the proper state. For example: … [STAThread] static void Main() …
After setting the apartment state, you can check the state programmatically, as in the following example: Thread.CurrentThread.ApartmentState = ApartmentState.STA; if (Thread.CurrentThread.ApartmentState == ApartmentState.STA) //STA apartment state else //incompatible apartment state
30
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
Signature Translation and Error Handling Topic Objective
To describe how signature translation works.
Lead-in
Signature translation is the mechanism by which managed return values are converted into COM HRESULTs.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** COM components accept COM data types and return HRESULTs. However, classes in the .NET Framework do not use HRESULTs. In order to satisfy both models, every method of a managed type has a .NET Framework signature and an implied COM signature. When COM interoperability services imports a COM type, it produces a .NET Framework method signature equivalent to the original COM method signature. This is referred to as signature translation. During signature translation, COM parameters, return values, and HRESULTs are mapped to corresponding entities in the .NET Framework method signature, as shown in the illustration on the slide. When the COM object returns an error HRESULT, the runtime converts the HRESULT to an exception, which is thrown to the caller. The type of exception that is generated depends on the error returned from the COM object. Note that any non-error HRESULTs will be lost in this conversion. For example, if a COM object returns any success HRESULT other than S_OK, such as S_FALSE, no exception is thrown, but the managed caller does not receive the HRESULT. You must manually generate the runtime callable wrappers to avoid this issue. A managed signature is converted to an unmanaged signature by changing the managed return value to an [out, retval] parameter and changing the type of the unmanaged return value to HRESULT. The COM signature returns an HRESULT and has an additional output parameter for the return value. The return value from the managed implementation always returns as an [out, retval] parameter added to the end of the unmanaged signature, whereas the unmanaged signature always returns an HRESULT. If the managed method has a void return, the runtime omits the [out, retval] parameter.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
Under some circumstances, it is preferable to leave the managed signature unchanged. You can use the PreserveSig attribute to do this. The following examples show the translation from a managed signature to an unmanaged signature. Managed signature: [PreserveSig] short DoSomething(short i);
Unmanaged signature: short DoSomething ([in] short i);
31
32
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
Marshaling Topic Objective
!
To describe how COM types relate to .NET Framework types and classes.
Wrappers perform data marshaling #
Lead-in
Types used in COM have corresponding .NET Framework built-in value types or classes.
Support for marshaling data to and from COM almost always provides the correct marshaling behavior
!
Table shows corresponding COM and C# types
!
MarshalAsAttribute to change the marshaling behavior
public public void void M1 M1 ([MarshalAs(UnmanagedType.LPWStr)]String ([MarshalAs(UnmanagedType.LPWStr)]String msg); msg);
Direct students to the Student Notes and explain that the table that is referred to in the slide may be found in the Student Notes.
!
Two ways to customize the RCW to handle types #
Edit the interop assembly
#
Create a wrapper manually
*****************************ILLEGAL FOR NON-TRAINER USE****************************** When marshaling data between managed and unmanaged code, the interop marshaler must recognize the representations of the data being passed. !
For blittable types, managed and unmanaged representations are always the same: a 4-byte integer is always marshaled to a 4-byte integer. The interop marshaler uses the managed signature to determine the data representation.
!
For non-blittable types, the interop marshaler recognizes the managed representation from its method signature, but is unable to do the same for the unmanaged representation. To marshal non-blittable types, you can use one of the following techniques: • Allow the marshaler to infer the representation from the managed representation. • Supply the unmanaged data representation explicitly.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
33
The following table shows data types used in COM and their corresponding .NET Framework built-in value types or classes. Any type not explicitly identified in this table is converted to an Int32 system type. COM value type
COM reference type
C# Data Type
bool
bool *
int
char, small
char *, small *
System.SByte
short
short *
short
long, int
long *, int *
int
Hyper
hyper *
long
unsigned char, byte
unsigned char *, byte *
byte
wchar_t, unsigned short
wchar_t *, unsigned short *
ushort
unsigned long, unsigned int
unsigned long *, unsigned int *
uint
unsigned hyper
unsigned hyper *
ulong
float
float *
float
double
double *
double
VARIANT_BOOL
VARIANT_BOOL *
bool
void *
void **
System.IntPtr
HRESULT
HRESULT *
System.IntPtr
SCODE
SCODE *
int
BSTR
BSTR *
string
LPSTR or [string, …] char *
LPSTR *
string
LPWSTR or [string, …] wchar_t *
LPWSTR *
string
VARIANT
VARIANT *
object
DECIMAL
DECIMAL *
System.Decimal
DATE
DATE *
System.DateTime
GUID
GUID *
System.Guid
CURRENCY
CURRENCY *
System.Decimal
IUnknown *
IUnknown **
object
IDispatch *
IDispatch **
object
SAFEARRAY(type)
SAFEARRAY(type) *
type[ ]
34
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
The following table lists COM value and reference types that convert to corresponding element types. For example, a COM coclass automatically maps to a managed class with the same name. COM value type
COM Reference type
Element type
typedef BaseType
ByRef BaseType
BaseType
MyStruct
ByRef VALUETYPE<MyStruct>
valuetype<MyStruct>
MyEnum
ByRef VALUETYPE<MyEnum>
valuetype<MyEnum>
MyInterface *
ByRef CLASS <MyInterface>
Class<MyInterface>
MyCoClass
ByRef CLASS <_Class>
class <_Class>
MyType
You can apply the System.Runtime.InteropServices.MarshalAsAttribute to a method parameter, class field, or return value to change the marshaling behavior. For example, a string is converted to a BSTR type when marshaled from managed to unmanaged code, unless you explicitly apply the MarshalAsAttribute to marshal the string to another type, such as LPWSTR. You can apply this attribute to a parameter, field, or return value within the source of the type definition, as shown in the following examples where it is specified that msg is to be marshaled as a null-terminated buffer of Unicode characters (LPWStr). Apply the MarshalAsAttribute to a parameter: public void M1 ([MarshalAs(UnmanagedType.LPWStr)]String msg);
Apply the MarshalAsAttribute to a field within a class: class MsgText { [MarshalAs(UnmanagedType.LPWStr)] Public String msg; }
Apply the MarshalAsAttribute to a return value: [return: MarshalAs(UnmanagedType.LPWStr)] public String GetMessage();
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
35
Although the import process generally produces accurate interop assemblies, there are cases when you can (or must) modify the interop assembly to produce a custom RCW. There are several reasons why you might customize an RCW, such as: !
One or more types require additional marshaling information.
!
A type library contains many specialized types that are unrecognizable to the marshaler.
!
A large type library can include types that are unnecessary for an application. You can eliminate the need to deploy unnecessary types by creating an interop assembly from managed source code.
If you must customize the runtime callable wrapper with additional or different marshaling instructions, you can either: 1. Edit the interop assembly, searching for problematic syntax and replacing it with alternative syntax. This option is best for minor marshaling changes. 2. Create a wrapper manually, based on an existing Interface Definition Language (IDL) file or type library. Declaring COM types manually is a difficult activity that requires working knowledge of the Type Library Importer (Tlbimp.exe), the default behavior of the interop marshaler, and COM. This approach is best used when you have an entire library of specialized types or require the RCW source code. For more information about implementing these choices, see the .NET Framework Software Development Kit (SDK) documentation.
36
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
Performance and Security Issues Topic Objective
To list some performance issues that arise from running unmanaged code from managed code.
!
Lead-in
Running unmanaged code from managed code decreases the performance of your application.
Recognize that transitions that occur when managed code must interact with unmanaged code result in an overhead of 10 to 40 instructions per call. #
Limit the number of transitions that appear in your code
#
Instead, use API calls that perform several actions at once
!
Eliminate the creation of the proxy and stub by setting the ApartmentState on the thread before creating a COM object
!
For trusted code, suppress security checks that are performed when managed code calls unmanaged code
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Transitions occur when managed code must interact with unmanaged code. A slight overhead is associated with every transition. These overheads require 10 to 40 instructions per call. The best practice is to limit the number of transitions that appear in your code. If you must use transitions, use API calls that perform several actions from the same call instead of ones that only perform few actions and must be repeatedly called. Threading models that are used in managed and unmanaged code affect the performance of applications. Whenever a COM object and a managed thread are in incompatible apartments, all calls on the object are made through a COM-created proxy. To eliminate the use of proxies and stubs, and significantly enhance performance, set the ApartmentState on the thread before creating the object. Security checks that are performed when unmanaged code is run, also lead to a decrease in application performance. You can suppress these security checks and increase the performance of your application. When managed code calls unmanaged COM code by using COM interoperability services, a demand for the UnmanagedCode permission is made to ensure that all callers have the necessary permission to allow the call to proceed. You can suppress the demand for the UnmanagedCode permission at run time by using the SuppressUnmanagedCodeSecurityAttribute attribute. The demand for the UnmanagedCode permission will still occur at link time.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
37
The SuppressUnmanagedCodeSecurityAttribute attribute helps improve code performance by suppressing asserts and demands at run time. These run time asserts and demands are the usual security checks performed when calling unmanaged code. Only code that has been granted the UnmanagedCode permission can use the SuppressUnmanagedCodeSecurityAttribute attribute, however. Using this attribute in a class or module applies to all methods contained in the class or module. When you use the SuppressUnmanagedCodeSecurityAttribute attribute in a class, code that does not have permission to access unmanaged code can call methods in the class to access unmanaged code. Therefore, you must make sure that when you use the SuppressUnmanagedCodeSecurityAttribute attribute, you write secure code. Otherwise, your code can be misused by malicious code.
38
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
Best Practices Topic Objective
To list some best practices to be followed when calling COM components from managed code.
!
The MSIL Disassembler can be used on the generated assembly to determine the exact names of the namespace and classes that are generated
!
Any parameter passed by reference in C++ or Visual Basic 6.0 will generate a parameter that needs to be passed by using the ref keyword in C#
Lead-in
Let’s look at some best practices that you should use when calling COM components from managed code.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** If you create your runtime callable wrapper assembly by adding a reference to a COM object in Visual Studio .NET or by using the Type Library Importer, the namespace and class names that are generated might not always be obvious. To view these names, you can run the MSIL Disassembler on the particular assembly. If you create the runtime callable wrapper assembly with Visual Studio .NET, you must attempt to build the project once to generate the assembly. Parameters passed by reference in unmanaged code frequently require the use of the ref keyword when that code is called by using C#. For C or C++, this includes most parameters passed as pointers. For Visual Basic 6.0, keep in mind that, by default, all parameters are passed by reference. The most notable exception to this is for passing string types, which can usually be passed as String or StringBuilder objects from managed code. For an example in C or C++ of passing a string type, see Marshaling in this module.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
39
Lab 15.2: Calling COM Objects Topic Objective
To introduce the lab.
Lead-in
In this lab, you will call a COM object from managed code.
*****************************ILLEGAL FOR NON-TRAINER USE******************************
Objectives After completing this lab, you will be able to call a COM object from managed code.
Estimated time to complete this lab: 30 minutes
40
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
Exercise 1 Calling a COM Object From Managed Code In this exercise, you will call a COM object from managed code. This includes registering the COM DLL, adding a reference to the COM object in a Visual Studio .NET project, and then adding code to call the COM object. Important To use Microsoft Visual Studio .NET tools within a command prompt window, the command prompt window must have the proper environment settings. The Visual Studio .NET Command Prompt window provides such an environment. To run a Visual Studio .NET Command Prompt window, click Start, All Programs, Microsoft Visual Studio .NET, Visual Studio .NET Tools, and Visual Studio .NET Command Prompt.
! Register the COM object 1. At the command prompt, change the current folder to \Labs\ Lab15\Lab15.2\Starter. 2. To register the COM object, run regsvr32 VBUnmanagedCalculator.dll. 3. A dialog box appears informing you that the registration succeeded. Click OK to dismiss the dialog box.
! Open the Visual Studio .NET project for this exercise and examine the application UI
1. In Windows Explorer, move to \Labs\Lab15\Lab15.2\ Starter. 2. Double-click CallingComFromDotNet.csproj to open the project for this exercise in Visual Studio .NET. 3. In the Solution Explorer pane, double click CalcUI.cs. The UI design for the Windows Forms application appears. Examine the UI for a moment to become familiar with it. 4. Press F7 to edit the code that implements the Windows Form application.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
41
! Add the reference to the COM object and attempt to build the project 1. On the Project menu, click Add Reference. 2. In the Add Reference dialog box, click the COM tab. In the list of registered COM objects, click VBUnmanagedCalculator. 3. To add VBUnmanagedCalculator to the Selected Components window, click Select. 4. To add the reference, click OK. Notice that the VBUnmanagedCalculator DLL is added to the project references. 5. On the Build menu, click Build Solution. Your application does not build successfully yet, but the runtime callable wrapper assembly for the VBUnmanagedCalculator.dll is generated. 6. In a Visual Studio .NET Command Prompt window, change the current folder to \Labs\Lab15\Lab15.2\ Starter\bin\Debug. 7. To view the metadata in the runtime callable wrapper assembly, at the command prompt, type ildasm /adv Interop.VBUnmanagedCalculator.dll Use the MSIL Disassembler to view the namespace and classes created for the COM object.
! Modify the code in the project to call the COM object 1. In the CalcUI.cs file, find the first ‘TODO’ comment that instructs you to add a using statement for VBUnmanagedCalculator. Add the correct using statement. Notice that you can use the Task List tab in the lower-left hand pane to find the ‘TODO’ comments. Click View, select Show Tasks, and then click All if the ‘TODO’ comments do not appear in the task list. 2. Find the next ‘TODO’ comment that instructs you to instantiate a CalcEngine object. Add the code to instantiate a CalcEngine object and assign it to the calcEngine private member variable. 3. Scroll down to the next ‘TODO’ comment that instructs you to call GetVersion in CalcEngine. Add the code to make the call described and assign the results of the call to the VersionInfo text box. 4. Scroll down to the KeyDate_Click method. Complete the code in this method by replacing the ‘TODO’ comment with code to obtain the date by calling the GetDate method in CalcEngine and assign the results of the call to the OutputDisplay text box. Then, add code to reset the engine for the next equation by calling the CalcReset method in CalcEngine. 5. In the KeyClear_Click method, complete the code by replacing the ‘TODO’ comment with code that calls the CalcReset method in CalcEngine and clears the OutputDisplay text box.
42
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
! Build and test your application 1. On the File menu, click Save All. 2. On the Build menu, click Build Solution. Your application should build successfully. The following statement should appear as the last line of the output window: Build: 1 succeeded, 0 failed, 0 skipped.
3. On the Debug menu, click Start. When the calculator appears, check the version number. It should read “Calculator – VB6 Unmanaged Component”. Enter some equations to test the application. 4. Close the application. Note If you wish to test the solution project, you will first need to follow the steps in the “Register the COM object”, “Open the Visual Studio .NET project for this exercise and examine the application UI”, and “Add the reference to the COM object and attempt to build the project” procedures above.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
43
" Calling .NET Objects from COM Objects Topic Objective
To introduce how COM callable wrappers are used to expose .NET Framework classes to COM clients.
Lead-in
In this section, you will learn how to create COM callable wrappers to use .NET Framework types for a COM client.
!
COM Callable Wrappers
!
The Export Process
!
Registering a .NET Framework Class with the System Registry
!
Using the ClassInterfaceAttribute
!
Using .NET Framework Types from COM
!
Managed and Unmanaged Events
!
Best Practices
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The .NET Framework allows existing COM applications to use managed code transparently by generating COM callable wrappers for .NET Framework classes. These wrappers are generated at run time. COM callable wrappers are proxy objects that are generated from the metadata that is stored in an assembly, so COM applications can use the .NET Framework classes in the same manner as they would use COM components.
44
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
COM Callable Wrappers Topic Objective
To describe how the runtime uses COM callable wrappers to expose managed classes to COM clients.
Lead-in
To allow COM clients to use a managed class, the .NET Framework creates a COM callable wrapper.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** COM clients cannot reference a .NET Framework object directly. When a COM client calls a .NET Framework object, the common language runtime creates the managed object and a proxy for the managed object called the COM callable wrapper. The purpose of a COM callable wrapper is to marshal calls between managed and unmanaged code, and to manage the identity and lifetime of the managed object it wraps. COM callable wrappers are invisible to other classes running within the .NET Framework. Unlike the managed object it wraps, the COM callable wrapper is allocated memory from a non-collected heap. This makes it possible for COM clients to reference the wrapper directly. COM callable wrappers are reference-counted in the traditional COM fashion. When the reference count on the wrapper reaches zero, the wrapper releases its reference on the managed object. A managed object with no remaining references is collected during the next garbage-collection cycle. The runtime creates one COM callable wrapper for a managed object, regardless of the number of COM clients requesting its services. The COM callable wrapper, in turn, holds a single reference to the managed object that implements the interface. Both COM clients and .NET Framework clients can make requests on the same managed object simultaneously. When you compile your managed code into an assembly, metadata describing each type in the assembly is generated. The Type Library Exporter can be used to create a type library file from the metadata for your assembly. COM clients can then import the type library and use the managed object like any other COM object. When the COM client tries to create and access a managed object from the assembly, the runtime uses the assembly’s metadata to generate a COM callable wrapper. The wrapper marshals the call between the COM client and the managed object.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
45
You can customize the COM callable wrapper by applying interop-specific attributes to your managed source code and compiling the source code into an assembly. Caution If you plan to export a managed object, be aware that the COM client determines the apartment of the object. A managed object called by a COM client initialized in an MTA must ensure thread safety.
46
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
Demonstration: Using the Type Library Exporter Topic Objective
To describe the use of the Type Library Exporter.
Lead-in
The Type Library Exporter is a command-line tool that exports the type information to a type library so that it can be used from a COM client.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The Type Library Exporter (Tlbexp.exe ) is a command-line tool that builds a type library for .NET Framework classes so that they can be used from unmanaged clients. The Type Library Exporter uses the metadata that is stored in the assembly to generate the type library. The type library contains definitions of the types defined in an assembly. Applications that use unmanaged code can use the generated type library to bind to the .NET Framework types defined in the assembly. Note You cannot use the Type Library Exporter to produce a type library from an assembly that was imported by using the Type Library Importer (Tlbimp.exe). Instead, you should refer to the original type library that was imported with the Type Library Importer. You can export a type library from an assembly that references assemblies that were imported by using the Type Library Importer. You must use the Type Library Exporter to generate a type library for an entire assembly and not just a subset of the types that are defined in the assembly. A single assembly may cause several type libraries to be generated, and these type library files are placed in the current working directory or the directory specified for the output file when running the Type Library Exporter. The Type Library Exporter generates a type library but does not register the types it exports with the system registry. To generate and register a type library with COM, use the Assembly Registration tool (Regasm.exe). For information about using the Assembly Registration tool to register a type library, see Registering a .NET Framework class with the System Registry in this module.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
47
The Export Process Topic Objective
To describe how the export process converts classes and interfaces.
Lead-in
The export process creates definitions for the classes and interfaces that are defined in an assembly.
!
Every public class in an assembly is converted to a coclass in a type library
!
Interfaces that are explicitly implemented are exported to the type library
Managed Code public public interface interface IShape IShape {{ void Draw(); void Draw(); void void Move(int Move(int x, x, int int y); y); }} class class Circle Circle :: IShape IShape {{ void Draw(); void Draw(); void void Move(int Move(int x, x, int int y); y); void void Enlarge(int Enlarge(int x); x); }}
Type Library [[ uuid(…), uuid(…), dual, dual, odl, odl, oleautomation oleautomation ]] interface interface IShape IShape :: IDispatch IDispatch {{ HRESULT HRESULT Draw(); Draw(); HRESULT HRESULT Move(int Move(int x, x, int int y); y); }} [[ uuid(…) uuid(…) ]] coclass coclass Circle Circle {{ interface interface IShape; IShape; }} }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** When you run the Type Library Exporter on your assembly to generate a type library, the export process converts the classes and interfaces defined in your assembly, and creates representations for them in the type library. The export process converts each public class in an assembly to a coclass in a type library, and gives the coclass the same name as the managed class. All the interfaces that are explicitly implemented by the managed class are exported to the type library. Methods and properties are not exported. When the class in the example in the slide is exported, the type library will contain a representation for the IShape interface and a coclass definition for the Circle class. The Draw, Move, and Enlarge methods of the Circle class will not be exported. When an assembly is exported, all managed interfaces and their methods and properties are converted to COM interfaces. By default, the COM interface that is generated in the type library file is a dual interface. You can use the ClassInterfaceAttribute to determine the type of interface that is defined when the type library is exported. Each coclass can implement one other interface, called the class interface, which the export process generates automatically. The class interface exposes all methods and properties available in the original managed class, thereby enabling COM clients to access them by calling through the class interface.
48
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
Registering a .NET Framework Class with the System Registry Topic Objective
To describe the procedure for registering a .NET Framework class with the system registry.
!
To register a .NET Framework class with the system registry, use the Assembly Registration tool (Regasm.exe)
!
Registering a .NET Framework class creates the following entries in the system registry:
Lead-in
Before you can use a .NET Framework class from unmanaged code, you must register the class with the system registry.
#
#
The CLSID of the .NET Framework class under the Hkey_Classes_Root\CLSID registry key Two sub keys: Implemented Categories and InprocServer32 under the HKCR\CLSID\ key
!
The default value of the HKCR\CLSID\ key is set to the ProgID of the class
!
The default value of the InprocServer32 is set to the name of the DLL that contains the common language runtime (Mscoree.dll)
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Before you can use a .NET Framework class from unmanaged code, you must register the class with the system registry. To register a .NET Framework class with the system registry, you use the command-line Assembly Registration tool (Regasm.exe). The Assembly Registration tool adds information about the class to the system registry so COM clients can use the .NET Framework class transparently. You can also use the RegistrationServices class from within your managed code, to register your .NET Framework class with the system registry. When you register a .NET Framework class, the following entries are made in the system registry: !
A new entry for the class identifier (CLSID) of the .NET Framework class is created under the HKEY_CLASSES_ROOT\CLSID registry key. After the CLSID is generated for the .NET Framework class, the Assembly Registration tool always uses the same CLSID, regardless of the number of times it is used on the class.
!
Under the HKEY_CLASSES_ROOT\CLSID\ key, are two sub keys: ImplementedCategories and InprocServer32. The default value of the HKEY_CLASSES_ROOT\CLSID\ key is set to the ProgID of the class. The default value of the InprocServer32 is set to the name of the DLL that contains the common language runtime (Mscoree.dll).
When a COM client attempts to load the assembly in which the class is implemented, the runtime reads the assembly value from the registry and passes it on to the runtime assembly resolver. The assembly resolver attempts to locate the assembly based on the assembly information, such as the name and version number. Before the assembly resolver can locate the assembly, however, the assembly must be signed and installed in the global assembly cache, or it must be in the application’s root directory or a subdirectory of the application’s root directory.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
49
Using the ClassInterfaceAttribute Topic Objective
To describe the use of the ClassInterfaceAttribute to control the type of interface that is created for a managed class.
!
The class interface is an interface that exposes all public methods, properties, fields, and events that are explicitly exposed on the .NET Framework object
Lead-in
!
The [ClassInterface(ClassInterfaceType.None)] attribute prevents the class interface from being created
!
The [ClassInterface(ClassInterfaceType.AutoDispatch)] attribute creates a dispatch interface
!
The [ClassInterface(ClassInterfaceType.AutoDual)] attribute creates a dual interface
When used with a .NET Framework class, the ClassInterfaceAttribute can be used to control and modify the type of interface that is generated when that .NET Framework class is exported.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The class interface is an interface that exposes all public methods, properties, fields, and events that are explicitly exposed on a .NET Framework object. This interface can be a dual or dispatch-only interface. The class interface receives the name of the .NET Framework class, preceded by an underscore. When you use the Type Library Exporter to generate a type library for a managed class, a dispatch-only interface for each class you export is created by default. You can use the ClassInterfaceAttribute with your class to prevent or modify the automatic creation of this interface. When you use the ClassInterfaceAttribute, you must add the Sytem.Runtime.InteropServices namespace to your code. You use the ClassInterfaceAttribute and pass the value ClassInterfaceType.None to the constructor, to prevent the class interface from being generated when the class metadata is exported to a type library, as shown in the following example: [ClassInterface(ClassInterfaceType.None)] public class LoanApp : IExplicit { void M(); }
In the preceding example, COM clients can access the LoanApp class only through the IExplicit interface.
50
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
To avoid breaking late-bound COM clients when using the class interface, set the Value property of the ClassInterfaceAttribute to ClassInterfaceType.AutoDispatch. This value implements a dispatch-only class interface, but omits the interface description from the type library. Without an interface description, clients are unable to cache dispatch identifiers (DISPIDs) at compile time. Although this is the default interface type for the class interface, you can apply the attribute value explicitly, as shown in the following example: [ClassInterface(ClassInterfaceType.AutoDispatch)] public class LoanApp : IAnother { void M(); }
To get the DISPID of an interface member at run time, COM clients can call IDispatch.GetIdsOfNames. To invoke a method on the interface, pass the returned DISPID as an argument to IDispatch.Invoke. A dual interface that is automatically generated (ClassInterface.AutoDual) might be appropriate in rare cases. However, a dual interface that is automatically generated often creates version-related complexities. For example, COM clients that are using the class interface of a derived class can easily break when there are changes to the base class. When a third party provides the base class, the layout of the class interface is out of your control. Further, unlike a dispatch-only interface, a dual interface provides a description of the class interface in the exported type library. Such a description encourages late-bound clients to cache DISPIDs at run time.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
51
Using .NET Framework Types from COM Topic Objective
To describe the procedure for using .NET Framework types from COM clients.
!
#
Lead-in
After you have generated a type library for your .NET assembly, you can use the assembly from unmanaged code.
To Use a .NET Framework Class from Unmanaged Code Set a reference to the type library file that describes the interferences of the .NET Framework class
!
In Visual Basic 6.0, Use the References Dialog Box to Set the Reference
!
In Visual C++ 6.0, Use the #import Directive to Reference to the Type Library
!
Create and Use Objects of the Classes That Are Defined in the Type Library
*****************************ILLEGAL FOR NON-TRAINER USE****************************** After you have exported the metadata from an assembly to a type library file, you can use the .NET Framework classes that are implemented in the assembly as you would create and use a COM object.
! To use a .NET Framework class that has been exported to a type library from a Visual Basic 6.0 application
1. In Visual Studio 6.0, on the Project menu, click References. The References dialog box appears. 2. To add the reference to your project, scroll down Available References list, click the reference you want to add, and then click OK. 3. Create an object of the .NET Framework class (as you would create a COM object), as shown in the following example: Dim Server As New ExcelServer.ExcelServer
4. Use the methods and properties of the class, as shown in the following example: Dim Count As Integer Count = Server.Count
To use a .NET Framework class from a Microsoft Visual C++® 6.0 application, use the #import directive to reference the type library, as shown in the following example: #import "c:\Samples\SampleClassLibrary\SampleClassLib.tlb"
52
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
Managed and Unmanaged Events Topic Objective
To understand at a high level interoperability between .NET and COM events
!
.NET event model differs from COM event model #
Lead-in
Let’s look at how the NET Framework event model differs from the traditional COM event model.
!
Managed events are based on delegates, whereas unmanaged events (in COM) are based on connection points.
See the .NET SDK for details on the topics: #
Handling Events Raised by a COM Source
#
Raising Events Handled by a COM Sink
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The .NET Framework event model differs from the traditional COM event model. Managed events are based on delegates, whereas unmanaged events in COM are based on connection points. Both models are tightly coupled event systems, because the client (event receiver) and server (event sender) must run simultaneously. The SDK explains in detail how to bridge the managed and unmanaged event systems, enabling objects to send and receive events across the interoperation boundary. Topics include Handling Events Raised by a COM Source and Raising Events Handled by a COM Sink.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
53
Best Practices Topic Objective
To list some best practices when using managed classes from COM clients.
!
Define an Explicit Interface for COM Clients to Use Rather Than Generating the Class Interface
!
Avoid Caching Dispatch Identifiers
!
Restrict Using the Dual Interface Option for the Class Interface
Lead-in
When exposing managed code to COM clients, follow the best practices listed on the slide.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** When exposing managed code to COM clients, use the following best practices: !
Define an explicit interface for COM clients to use rather than generating the class interface. Because COM interop services generate class interfaces automatically, postversion changes to your class can alter the layout of the class interface exposed by the common language runtime. Since COM clients are typically unprepared to handle changes in the layout of an interface, they break if you change the member layout of the class. To reduce the risk of breaking COM clients by inadvertently reordering the interface layout, isolate all changes to the class from the interface layout by explicitly defining interfaces. Set the Value property of ClassInterfaceAttribute to ClassInterfaceType.None to disengage the automatic generation of the class interface and implement an explicit interface for the class.
!
Avoid caching dispatch identifiers (DISPIDs). Using the class interface is an acceptable option for scripted clients, Visual Basic 6.0 clients, or any late-bound client that does not cache the DISPIDs of interface members. DISPIDs identify interface members and enable late binding. For the class interface, generation of DISPIDs is based on the position of the member in the interface. If you change the order of the member and export the class to a type library, you will alter the DISPIDs generated in the class interface.
54
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
To avoid breaking late-bound COM clients when using the class interface, set the Value property of the ClassInterfaceAttribute to ClassInterfaceType.AutoDispatch. This value implements a dispatch-only class interface, but it omits the interface description from the type library. Without an interface description, clients are unable to cache DISPIDs at compile time. Although this is the default interface type for the class interface, you can apply the attribute value explicitly. !
Restrict using the dual interface option for the class interface. Dual interfaces enable early and late binding to interface members by COM clients. At design time and during testing, you might find it useful to set the class interface to dual. For a managed class and its base classes that will never be modified, this option is also acceptable. In all other cases, avoid setting the class interface to dual.
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
55
Review Topic Objective
To reinforce module objectives by reviewing key points.
Lead-in
The review questions cover some of the key concepts taught in the module.
!
Integration Services
!
Platform Invoke
!
Calling COM Components from Managed Code
!
Calling .NET Objects from COM Objects
*****************************ILLEGAL FOR NON-TRAINER USE****************************** 1. List the steps for calling an API function that is implemented in a DLL. Declare the API function with the static and extern C# keywords, attach the DllImport attribute to the function specifying the name of the DLL that exports the unmanaged function, and optionally specify marshaling information.
2. What is pinning? Pinning is a technique in which data, in its current memory location, is temporarily locked to keep it from being relocated by the common language runtime’s garbage collector.
3. List the tasks that are performed when the Type Library Importer is run on a type library file. The Type Library Importer converts unmanaged COM coclasses to C# classes with a constructor (that does not have parameters) and no other methods, converts unmanaged COM vtable interfaces to C# interfaces, and converts unmanaged COM structures to C# structures with public fields.
4. Which attribute must you use to suppress runtime security checks that are preformed when managed code calls unmanaged code? SuppressUnmanagedCodeSecurityAttribute
56
Module 15 (Optional): Interoperating Between Managed and Unmanaged Code
5. List some best practices when using .NET code from COM clients. Define an explicit interface for COM clients to use rather than generating the class interface, and avoid caching dispatch identifiers (DISPIDs).
6. Which must you use to prevent the class interface from being generated? Set the Value property of the ClassInterfaceAttribute to ClassInterfaceType.None
Contents Overview Overview of ADO.NET
Module 16 (Optional): Using Microsoft ADO.NET to Access Data 1 2
Connecting to a Data Source
10
Accessing Data with DataSets
12
Using Stored Procedures
26
Lab 16: Using ADO.NET to Access Data
34
Accessing Data with DataReaders
42
Binding to XML Data
50
Review
56
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places, and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, places or events is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. 2001-2002 Microsoft Corporation. All rights reserved. Microsoft, ActiveX, BizTalk, IntelliMirror, Jscript, MSDN, MS-DOS, MSN, PowerPoint, Visual Basic, Visual C++, Visual C#, Visual Studio, Win32, Windows, Windows Media, and Window NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
iii
Instructor Notes Presentation: 120 Minutes
This module focuses on using ADO.NET to access data from various data sources.
Lab: 60 Minutes
After completing this module, students will be able to: !
Describe the ADO.NET object model.
!
Connect to a data source by using ADO.NET.
!
Retrieve data from a database by using DataReaders and DataSets.
!
Display the data from a database on the client by using DataGrid controls.
!
Use stored procedures to read data from a data source.
!
Read data from an XML file into DataSets.
Materials and Preparation This section provides the materials and preparation tasks that you need to teach this module.
Required Materials To teach this module, you need the Microsoft® PowerPoint® file 2349B_16.ppt.
Preparation Tasks To prepare for this module, you should: !
Read all of the materials for this module.
!
Complete all of the demonstrations.
!
Complete the lab.
!
Go through the animation.
iv
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
Multimedia Presentation This section provides multimedia presentation procedures that do not fit in the margin notes or are not appropriate for the student notes.
Using ADO.NET to Access Data ! To present the animation Action
Say this
Start animation
There are two ways to access data from a database by using ADO.NET: by using a DataSet or by using a DataReader. This animation demonstrates how these two methods work and highlights their differences.
Click Start Click DataSet
The DataSet method is a disconnected way to access data from a database. In this method, when a user requests data from a database, the DataAdapter object is used to create a DataSet, which is basically a collection of data tables from the database that also retains the relationships between these tables. Notice that, after a DataSet is populated, it is disconnected from the database. To display the data from the DataSet, you set up a DataView for the desired table. The DataView is then bound to a listbound control for displaying purposes. You can use any of the three list-bound controls, DataGrid, Repeater, or DataList, to display data. The data in the list-bound control is then displayed on the client. An important point to make here is that the use of a DataView to display data is necessary only in ASP.NET Beta 1. From the Beta 2 version onward, you can directly bind the DataSet to a list-bound control.
Click DataReader
This method is similar to the Microsoft ActiveX® Data Objects (ADO) way of accessing data by using recordsets. In this method, when a user requests data from a database, the Command object retrieves the data into a DataReader. A DataReader is a read-only/forward-only view of the data. A DataReader works similarly to a Recordset in ADO, allowing you to simply loop through the records. Like the ADO Recordset, the DataReader is connected to the database. You must explicitly close the connection when you are finished reading data.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
v
Module Strategy Use the following strategy to present this module: !
Overview of ADO.NET This section provides students with an overview of ADO.NET. The section begins a description of the objects used when connecting to a database both with a DataReader and a DataSet. Point out to students that there are Microsoft SQL Server™ and ADO versions of many of these objects. After describing the process of accessing data through a DataReader and a DataSet, show the animation. Because students may be familiar with ADO, this may be an ideal time to discuss some of the main differences between ADO and ADO.NET. When talking about using namespaces, explain their significance to the students.
!
Connecting to a Data Source From this point onward, students will actually start working with ADO.NET. Tell them that all of the examples in this module use SqlConnection objects rather than OleDbConnection objects. Direct the students to the Microsoft .NET Framework software development kit (SDK) documentation for more information.
!
Accessing Data with DataSets ADO.NET provides two ways to access data, the DataSet and the DataReader. This section focuses on accessing data by using the DataSet. The DataSet represents a new concept, so spend additional time on this section. The demonstrations actually show every aspect of data access with ADO.NET. Go through the demonstrations carefully, and make sure that the students understand the details.
!
Using Stored Procedures Most students who have worked with a SQL Server database and ADO will have experience with using stored procedures. This section provides the students with information about how to use stored procedures and parameterized stored procedures with ADO.NET.
!
Lab16: Using ADO.NET to Access Data The lab for this module is encountered in the middle of the module. This is because the module is long and also because the lab does not use material from the last two sections in the module.
vi
Module 16 (Optional): Using Microsoft ADO.NET to Access Data !
Accessing Data with DataReaders This section focuses on accessing data by using a DataReader. Point out to the students that, when they use a DataReader, the database connection is always open. When they are finished reading data, they must explicitly close the connection.
!
Binding to XML Data XML is fast emerging as the most popular language for exchanging data. This section provides students with information on how to read XML data by using ADO.NET. Most students will already know about XML documents. However, for students who are not familiar with XML, it will be useful to show an example of an XML document and how it is displayed on the client.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
1
Overview Topic Objective
To provide an overview of the module topics and objectives.
Lead-in
In this module, you will learn about the data binding features in ASP.NET.
!
Overview of ADO.NET
!
Connecting to a Data Source
!
Accessing Data with DataSets
!
Using Stored Procedures
!
Accessing Data with DataReaders
!
Binding to XML Data
*****************************ILLEGAL FOR NON-TRAINER USE****************************** ADO.NET, offers a rich suite of data handling and data binding functions for manipulating all types of data. ADO.NET is an evolution of the ADO data access model that directly addresses user requirements for developing scalable applications. It was designed specifically for the Web with scalability, statelessness, and XML in mind. After completing this module, you will be able to: !
Describe the ADO.NET object model.
!
Connect to a data source by using ADO.NET.
!
Retrieve data from a database by using DataReaders and DataSets.
!
Display the data from a database on the client by DataGrid controls.
!
Use stored procedures.
!
Read data from an XML file into DataSets.
2
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
" Overview of ADO.NET Topic Objective
To introduce the topics included in this section.
Lead-in
ASP.NET offers a new means to retrieve data with the introduction of ADO.NET.
!
The ADO.NET Object Model
!
RecordSets vs. DataSets
!
Using Namespaces
*****************************ILLEGAL FOR NON-TRAINER USE****************************** ADO.NET is not a revision of Microsoft® ActiveX® Data Objects (ADO), but a new way to manipulate data that is based on disconnected data and XML. Although ADO is an important data access tool it is connected by default, relies on an OLE DB provider to access data, and it is entirely Component Object Model (COM)-based. ADO.NET has been designed to work with disconnected datasets. Disconnected datasets reduce network traffic. ADO.NET uses XML as the universal transmission format. This guarantees interoperability as long as the receiving component runs on a platform where an XML parser is available. When the transmission occurs through XML, it is no longer necessary that the receiver be a COM object. The receiving component has no architectural restrictions whatsoever. Any software component can share ADO.NET data, as long as it uses the same XML schema for the format of the transmitted data. In this section, you will learn about ADO.NET. You will learn about the new and modified objects in ADO.NET. You will also learn about some of the new namespaces.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
3
The ADO.NET Object Model Topic Objective
.ASPX Page
To describe the ADO.NET object model.
DataReader Command
Lead-in
ADO.NET includes some of the same objects as ADO (like Connection and Command), and introduces new objects, such as DataSets, DataReaders and DataAdapters.
Company: Company: Northwind NorthwindTraders Traders Database Connection
DataAdapter
DataView DataView
DataSet .ASPX Page
List-Bound List-Bound Control Control
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Delivery Tip
Point out that there are ADO and SQL Server versions of the Connection, Command, DataAdapter, and DataReader objects.
ADO.NET evolved from the ADO data access model. By using ADO.NET, you can develop applications that are robust and scalable, and that can use XML. ADO.NET has some of the same objects as ADO (like the Connection and Command objects), and introduces new objects, such as the Dataset, DataReader, and DataAdapter.
Connection Objects Connection objects are used to talk to databases. They have properties, such as DataSource, UserID, and Password, which are needed to access a particular DataSource. Commands travel over connections, and result sets are returned in the form of streams that can be read by DataReaders or pushed into DataSet objects. There are two kinds of connection objects in ADO.NET: SqlConnection and OleDbConnection.
Command Objects Command objects contain the information that is submitted to a database. A command can be a stored procedure call, an update statement, or a statement that returns results. You can also use input and output parameters and return values. In ADO.NET, you can use two kinds of command objects: SqlCommand and OleDbCommand.
4
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
DataReader Objects A DataReader is somewhat synonymous with a read-only/forward-only view of the data. The DataReader API supports flat as well as hierarchical data. A DataReader object is returned after executing a command against a database. It works similarly to a recordset in ADO; however the format of the returned DataReader object is different from a recordset. For example, you may use the DataReader to show the results of a search list in a Web page. ADO.NET includes two types of DataReader objects: the SqlDataReader for Microsoft SQL Server™ version 7.0 (or later) data, and the OleDbDataReader for ADO data. The DataReader object is database-specific. The behavior of the SqlDataReader may differ from the behavior of the OleDbDataReader and additional DataReader objects that are introduced in the future. You use the OleDbCommand and SqlCommand objects and the ExecuteReader method to transfer data into a DataReader.
DataSet Objects The DataSet object is similar to the ADO Recordset object, but more powerful, and with one other important distinction: the DataSet is always disconnected. The DataSet provides a rich object model to work with when passing data between various components of an enterprise solution. The DataSet object represents a cache of data, with database-like behavior. It contains tables, columns, relationships, constraints, and data. Data coming from a database, an XML file, code, or user input can be entered into DataSet objects and converted into files, forms, or databases. The behavior of a DataSet is completely consistent regardless of the underlying database, SQL Server or OLE DB. As changes are made to the DataSet, they are tracked in a way similar to the way changes are tracked in a word processing document. The DataSet object has a collection of DataTable objects. A DataTable represents one table of in-memory data. It contains a collection of columns that represents the table's schema. A DataTable also contains a collection of rows, representing the data contained in the table. You use the OleDbDataAdapter and SqlDataAdapter objects and the Fill method to get data into a DataSet.
DataView Objects A DataView enables you to create different views of the data stored in a DataTable, a capability that is often used in data-binding applications. By using a DataView, you can expose the data in a table with different sort orders, and you can filter the data by row state or based on a filter expression. A DataView provides a dynamic view of data whose content, ordering, and membership reflect changes to the underlying DataTable as they occur. This is different from the Select method of the DataTable, which returns a DataRow array from a table per a particular filter and/or sort order, and whose content reflects changes to the underlying table, but whose membership and ordering remain static. The dynamic capabilities of the DataView make it ideal for databinding applications.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
5
DataAdapter Object While the DataSet object provides a tool for in-memory data storage, you need another tool to create and initialize the various tables. This tool is the DataAdapter object. It represents a centralized console that hides the details of working with connections and commands. The DataAdapter object allows for the retrieval and saving of data between a DataSet object and the source data store. It is responsible for pulling out data from the physical store and pushing it into data tables and relations. The DataAdapter object is also responsible for transmitting any update, insertion, or deletion to the physical database. You can use four command objects to make any updates: UpdateCommand, InsertCommand, DeleteCommand, and SelectCommand. The DataAdapter object exists in two forms: SqlDataAdapter objects and OleDbDataAdapter objects. The data source is SQL Server for SqlDataAdapter objects and any other OLE DB provider for OleDbDataAdapter objects. The following illustration shows the use of a SQLDataAdapter object to transfer data between a SQL Server database and a DataSet object.
6
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
Multimedia: Using ADO.NET to Access Data Topic Objective
To provide a high-level overview of how to access data by using ADO.NET.
Lead-in
In this animation, you will learn about how ADO.NET accesses data and how you can display that data in an ASP.NET page.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Delivery Tip
Tell students that they can view the animation again later for themselves by opening the 2349B_mod3.htm file from the Media folder. For details about how to run and describe the animation, see the Multimedia Presentation section in the Instructor Notes for this module.
In this animation, you will learn how to access data by using ADO.NET and how you can display that data in an ASP.NET page. To view the animation, open the file 2349B_16A001.htm file from the Media folder.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
7
Recordsets vs. DataSets Topic Objective
To highlight the differences between DataSets and Recordsets.
Lead-in
In ADO, you used Recordsets.
Feature
Recordset
DataSet
Number of tables
One table
Multiple tables
Relationships
Based on join
Includes relationships
Moving through data
Move row-by-row
Navigate via relationships
Data connections
Connected or disconnected
Disconnected
Transmitting data
COM marshalling
Transmit XML file
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In ADO, the in-memory representation of database data is the recordset. In ADO.NET, it is the DataSet. The DataSet contains a collection of tables and knowledge of relationships between those tables. Each table contains a collection of columns. These objects represent the schema of the DataSet. Each table can then have multiple rows, representing the data held by the DataSet. These rows track their original state along with their current state, so that the DataSet tracks what kinds of changes have occurred. Additionally, the DataSet provides persistence and de-persistence through XML. There are important differences between recordsets and DataSets, which are highlighted in the following table and detailed in the text that follows. Feature
Recordset
DataSet
Number of tables
One table
Multiple tables
Relationships
Based on join
Includes relationships
Moving through data
Move row-by-row
Navigate via relationships
Data connections
Connected or disconnected
Disconnected
Transmitting data
COM marshalling
Transmit XML file
Number of Tables An ADO recordset looks like a single table. If a recordset is to contain data from multiple database tables, it must use a JOIN query, which assembles the data from the various database tables into a single result table. In contrast, an ADO.NET DataSet is a collection of one or more tables. The tables within a data set are called data tables; specifically, they are DataTable objects.
8
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
Relationships Typically, a DataSet also contains relationships. A relationship within a DataSet is analogous to a foreign-key relationship in a database. In ADO.NET, a DataRelation represents the relationship.
Moving Through Data In ADO.NET, the methods you use to read or modify data differ from the programming methods you use in ADO in the following ways: !
In ADO, you scan sequentially through the rows of the recordset.
!
In ADO.NET, rows are represented as collections, so you can loop through a table as you would through any collection or access particular rows through ordinal or primary key index. DataRelation objects maintain information about master and detail records and provide a method that allows you to get records related to the one you are working with. For example, starting from the row of the Investor table for "Jose Lugo", you can navigate to the set of rows of the Purchase table that describes the purchases made by Jose Lugo.
Data Connections In ADO.NET, the DataSet provides disconnected access to database data. In ADO, the recordset can provide disconnected access, but is typically used to provide connected access.
Transmitting Data To transmit an ADO disconnected recordset from one component to another, you use COM marshalling. To transmit an ADO.NET data set, you simply transmit an XML file. Because components exchange ADO.NET datasets by using XML, firewalls can allow datasets to pass.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
9
Using Namespaces Topic Objective
To describe the new namespaces used with ADO.NET.
!
Namespaces Used with ADO.NET Include:
Lead-in
In.NET, you must specify the namespace that you want to use.
#
System.Data
#
System.Data.OleDb
#
System.Data.SqlClient
#
System.Data.SqlTypes
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The Microsoft .NET Framework is an object-oriented system. When using specific parts of the framework, you must include references to the appropriate namespace. When using ADO.NET from either Microsoft Visual Basic® or Microsoft Visual C#™, you must reference the System.Data namespace, plus either the System.Data.OleDb or System.Data.SqlClient namespace, depending on the data source you choose to use. System.Data provides the code facilities, while System.Data.OleDb and System.Data.SqlClient are the namespaces for the two managed providers. The System.Data.Common namespace contains classes that are shared by the .NET data providers. The System.Data.SqlTypes namespace provides classes for native data types within SQL Server. These classes provide a safer, faster alternative to other data types. The following table summarizes the list of available namespaces with ADO.NET. Namespace
Contains
System.Data
Base objects and types for ADO.NET
System.Data.OleDb
Managed OLE DB data store objects
System.Data.SqlClient
SQL Server specific implementations of ADO.NET objects
System.Data.Common
Classes shared by the .NET data providers
System.Data.SqlTypes
SQL data types
10
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
Connecting to a Data Source Topic Objective
To describe how to connect to a data source by using ADO.NET.
Lead-in
Connecting to a data source is the first step in data access.
!
Using SqlConnection
SqlConnection SqlConnection mySqlConnection mySqlConnection == new new SqlConnection("server=(local)\\NetSDK;! SqlConnection("server=(local)\\NetSDK;! Trusted_Connection=yes;database=northwind"); Trusted_Connection=yes;database=northwind"); !
Using OleDbConnection
OleDbConnection OleDbConnection myOleDbConnection myOleDbConnection == new new OleDbConnection("server=(local)\\NetSDK; OleDbConnection("server=(local)\\NetSDK; ! ! Trusted_Connection=yes;database=northwind; Trusted_Connection=yes;database=northwind; ! ! provider=sqloledb"); provider=sqloledb");
*****************************ILLEGAL FOR NON-TRAINER USE****************************** When you connect to a database, there are two routes that you can take: use OLE DB or use the native SQL provider. The native SQL provider is faster, but you must use Microsoft SQL Server as your database. If you are using Microsoft Access, Microsoft Excel, a comma-delimited file, or some other data source, you must use the OLE DB provider. You can use the OLE DB provider with a Microsoft SQL Server database; however, it is not as fast as using the native SQL provider. The Connection object defines how to connect to a specific data store. The .NET Framework provides two Connection objects: SqlConnection and OleDbConnection. The SqlConnection object defines how to connect to SQL Server databases and the OleDbConnection object allows you to establish a connection to a database through an OLE DB provider.
Using SqlConnection Delivery Tip
Point out to students that in ADO.NET you do not always need to explicitly open and close the connection to the database.
The following code illustrates how to create and open a connection to a Microsoft SQL Server database by using the SqlConnection object. SqlConnection mySqlConnection = new SqlConnection("server=(local)\\NetSDK;! Trusted_Connection=yes;database=northwind");
Warning Embedded usernames/passwords in database connection strings are inherently unsecure, and should only be used on platforms, such as Microsoft Windows® 9x, that do not support Integrated Security. For more information about Integrated Security, see the .NET Framework SDK documentation.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
11
Using OleDbConnection For the OLE DB Managed Provider, the connection string format is quite similar to the connection string format used in OLE DB. The following code illustrates how to create and open a connection to a Microsoft SQL Server database by using OleDbConnection. OleDbConnection myOleDbConnection = new OleDbConnection("server=(local)\\NetSDK;! Trusted_Connection=yes;database=northwind;! provider=sqloledb");
Delivery Tip
Stress to students that all examples in the module and lab will use SqlConnection objects.
The examples in this module, use SqlConnection objects. Implementation is slightly different for using OleDbConnection objects. For more information about using OleDbConnection objects, search for OleDbConnection in the Microsoft .NET Framework SDK documentation.
Choosing a .NET Data Provider Depending on the design and data source for your application, your choice of .NET data provider can improve the performance, capability, and integrity of your application. The following table discusses the advantages and limitations of each .NET data provider. Provider
Notes
SQL Server .NET Data Provider
Recommended for middle-tier applications that use Microsoft SQL Server 7.0 or later. Recommended for single-tier applications that use Microsoft Data Engine (MSDE) or Microsoft SQL Server 7.0 or later. Recommended over use of the OLE DB Provider for SQL Server (SQLOLEDB) with the OLE DB .NET Data Provider. For Microsoft SQL Server version 6.5 and earlier, you must use the OLE DB Provider for SQL Server with the OLE DB .NET Data Provider.
OLE DB .NET Data Provider
Recommended for middle-tier applications that use Microsoft SQL Server 6.5 or earlier, or any OLE DB provider that supports the OLE DB interfaces listed in OLE DB Interfaces Used by the OLE DB .NET Data Provider (OLE DB 2.5 interfaces are not required). For Microsoft SQL Server 7.0 or later, the SQL Server .NET Data Provider is recommended. Recommended for single-tier applications that use Microsoft Access databases. Use of a Microsoft Access database for a middle-tier application is not recommended. Support for the OLE DB Provider for ODBC (MSDASQL) is disabled. For access to Open Database Connectivity (ODBC) data sources, an ODBC .NET Data Provider is available as a separate download at http://msdn.microsoft.com/downloads.
12
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
" Accessing Data with DataSets Topic Objective
To introduce the topics included in the section.
Lead-in
After connecting to a database, the next step is to access data from it.
!
Using DataSets to Read Data
!
Storing Multiple Tables in a DataSet
!
Using DataViews
!
Updating a Database from a DataSet
!
Displaying Data in the DataGrid Control
*****************************ILLEGAL FOR NON-TRAINER USE****************************** ADO.NET provides two ways to access data, DataSets and DataReaders. In this section, you will learn how to access data by using DataSets. You will also learn about DataViews and displaying data in DataGrid controls.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
13
Using DataSets to Read Data Topic Objective
To describe how to retrieve data from a database by using DataSets.
Lead-in
Now that we can establish a connection, we need to be able to execute statements against the database to retrieve data.
!
Create the Database Connection
!
Store the Query in a SqlDataAdapter
SqlDataAdapter SqlDataAdapter mySqlDataAdapter mySqlDataAdapter == new new SqlDataAdapter( SqlDataAdapter( "select "select ** from from customers", customers", mySqlConnection); mySqlConnection); !
Create and Populate the DataSet with DataTables
DataSet DataSet myDataSet myDataSet == new new DataSet(); DataSet(); mySqlDataAdapter.Fill(myDataSet,"Customers"); mySqlDataAdapter.Fill(myDataSet,"Customers");
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Delivery Tip
Explain the parameters to the Fill method. The second parameter is the name that will be given to the DataTable created in the DataSet. You use this name when reading data from the DataSet.
After you establish a connection to a database, you can access its data. ADO.NET provides multiple ways to access data.
Using DataSets The DataSet object is the centerpiece of ADO.NET. It represents a complete set of data, including multiple, related tables, and constraints. Although a DataSet stores data, you need DataAdapter objects to create and initialize the various tables. You also need the Fill method to populate a DataSet with the results from a query. The Fill method takes two parameters: a DataSet instance and a string. The DataSet instance represents the DataSet to be filled, and the string identifies the DataTable that will be created inside the DataSet. A DataSet can contain many DataTables. You use the string supplied to the Fill method to reference the DataTable after it is created.
14
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
The following code example illustrates how to create a SqlDataAdapter object that contains the query statement. The Fill method then populates the DataSet with the results from the query. // Create a connection SqlConnection mySqlConnection = new SqlConnection("server=(local)\\NetSDK;! Trusted_Connection=yes;database=northwind"); // Create the DataAdapter SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter( "select * from customers", mySqlConnection); // Create and populate the DataSet DataSet myDataSet = new DataSet(); mySqlDataAdapter.Fill(myDataSet,"Customers");
Displaying Data In A DataSet Because the data is stored in a collection of rows in the table, you can easily use a foreach statement to iterate through the rows: foreach (DataRow myDataRow in myDataSet.Tables["Customers"].Rows) { Console.WriteLine( " CustomerID: {0}", myDataRow["CustomerID"].ToString); }
Typed DataSet Along with late bound access to values through weakly typed variables, the DataSet provides access to data through a strongly typed metaphor. By using user-friendly names and strongly typed variables, you can access tables and columns that are part of the DataSet. You can also transport a strongly typed DataSet by using an XML Web service. A typed DataSet is a class that derives from a DataSet. As such, it inherits all of the methods, events, and properties of a DataSet. Additionally, a typed DataSet provides strongly typed methods, events, and properties. This means that you can access tables and columns by name, instead of using collectionbased methods. Aside from the improved readability of the code, a typed DataSet also allows the Microsoft Visual Studio® .NET code editor to automatically complete lines as you type. Additionally, a strongly typed DataSet provides access to the correct types for values at compile time. With a strongly typed DataSet, type mismatch errors are caught when the code is compiled rather than at run time. Using an XML Schema that complies with the XML Schema definition language (XSD) standard, you can generate a strongly typed DataSet by using the XSD.exe tool that is provided with the .NET Framework SDK. The use of this tool is outside the scope of this module. You will see how to easily create and use a typed DataSet using Visual Studio .NET in this module’s lab.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
15
Storing Multiple Tables in a DataSet Topic Objective
To explain how to retrieve and store multiple tables in a DataSet.
Lead-in
Unlike a disconnected recordset, a DataSet can hold more than one table.
!
Add the First Table
SqlDataAdapter SqlDataAdapter mySqlDataAdapter mySqlDataAdapter == new new SqlDataAdapter( SqlDataAdapter( "select "select ** from from customers",mySqlConnection); customers",mySqlConnection); DataSet DataSet myDataSet myDataSet == new new DataSet(); DataSet(); mySqlDataAdapter.Fill(myDataSet,"Customers"); mySqlDataAdapter.Fill(myDataSet,"Customers"); !
Add the Subsequent Table(s)
mySqlDataAdapter.SelectCommand.CommandText mySqlDataAdapter.SelectCommand.CommandText == "select "select ** from from orders"; orders"; mySqlDataAdapter.Fill(myDataSet,"Orders"); mySqlDataAdapter.Fill(myDataSet,"Orders"); Customers
DataSet:
Orders
Data Tables
*****************************ILLEGAL FOR NON-TRAINER USE****************************** A DataSet can contain multiple tables. You can retrieve multiple tables from a database and store them in a DataSet. Note You can store tables from different databases in the same DataSet. Delivery Tip
Tell students that they can create new relationships between the tables in a DataSet.
! To retrieve and store multiple tables in a DataSet 1. Create and populate the first DataSet. DataSet myDataSet = new DataSet(); string strSql ="select * from customers"; SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter( strSql, mySqlConnection); mySqlDataAdapter.Fill(myDataSet,"Customers");
2. Reset the SelectCommand, InsertCommand, or DeleteCommand property of the DataAdapter object to a new command string. strSql = "select * from orders"; mySqlDataAdapter.SelectCommand.CommandText = strSql;
3. Call Fill again. mySqlDataAdapter.Fill(myDataSet,"Orders");
16
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
The following code shows how you can add two tables from two different queries, one for authors and the other for titles, to the same DataSet. // create connection to database string strConn = "server=(local)\\NetSDK;! Trusted_Connection=yes;database=northwind"; SqlConnection mySqlConnection = new SqlConnection(strConn); string strSql = "select * from customers" SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter(strSql, mySqlConnection); //fill DataSet with first set of data DataSet myDataSet = new DataSet(); mySqlDataAdapter.Fill(myDataSet,"Customers"); //fill DataSet with first set of data strSql = "select * from orders" mySqlDataAdapter.SelectCommand.CommandText = strSql; mySqlDataAdapter.Fill(myDataSet,"Orders");
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
17
Using DataViews Topic Objective
To explain the role of DataViews in accessing data from a database by using DataSets.
!
DataViews Can be Customized to Present a Subset of Data from a DataTable
Lead-in
!
The DefaultView Property Returns the Default DataView for the Table
To display data in a DataSet, you can use a DataView.
DataView DataView myDataView myDataView == myDataSet.Tables["Customers"].DefaultView; myDataSet.Tables["Customers"].DefaultView; !
Setting Up Different Views
myDataView.Sort myDataView.Sort == "Country"; "Country"; myDataView.RowFilter myDataView.RowFilter == "Country "Country == 'Argentina'"; 'Argentina'";
*****************************ILLEGAL FOR NON-TRAINER USE****************************** There are many ways to filter data. One way is to filter data at the database command level, by using a where clause in your query. A second way is to filter the data after it is in the DataSet. This topic covers filtering in the DataSet.
Filtering and Sorting with DataViews To display the data held in a DataSet, you can use a DataView. DataViews can be customized to present a subset of data from the DataTable. This capability allows you to have two controls bound to the same DataTable, but showing different versions of the data. For example, one control may be bound to a DataView that shows all the rows in the table, and a second may be configured to display only the rows that have been deleted from the DataTable. Each DataTable in a DataSet has a DefaultView property, which returns the default view for the table. You can access the default DataView of a DataSet as follows: DataView myDataView = myDataSet.Tables["Customers"].DefaultView;
Note The DataSet object contains a Tables collection. You reference the DataTable you are interested in by name. You can sort the data. For example, you can sort the customers by country. myDataView.Sort = "Country";
18
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
Setting up a Different View of a DataSet You can also create a view of a subset of the data in a DataTable. For example, you can set the RowFilter property on a DataView to retrieve only customers from Argentina. myDataView.RowFilter = "Country = 'Argentina'";
For more information about the properties of the DataView object, see the Microsoft .NET Framework SDK documentation.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
19
Updating a Database From a DataSet Topic Objective
To show how to update data in a Database from a DataSet.
Lead-in
This topic illustrates how to update data in a database using a DataSet. You can also insert, update, and delete data in a database directly using a SqlCommand as described in the .NET Framework SDK.
!
SQLCommandBuilder generates Update command
SqlCommandBuilder SqlCommandBuilder mySqlCommandBuilder mySqlCommandBuilder == new new SqlCommandBuilder(mySqlDataAdapter); SqlCommandBuilder(mySqlDataAdapter); !
Set the MissingSchemaAction property
mySqlDataAdapter.MissingSchemaAction mySqlDataAdapter.MissingSchemaAction == MissingSchemaAction.AddWithKey; MissingSchemaAction.AddWithKey; !
Add a row
DataRow DataRow myDataRow myDataRow == myDataSet.Tables["Customers"].NewRow(); myDataSet.Tables["Customers"].NewRow(); myDataRow["CustomerId"] myDataRow["CustomerId"] == "NewID"; "NewID"; // // ... ... myDataSet.Tables["Customers"].Rows.Add(myDataRow); myDataSet.Tables["Customers"].Rows.Add(myDataRow); !
To submit the data
mySqlDataAdapter.Update(myDataSet, mySqlDataAdapter.Update(myDataSet, "Customers"); "Customers");
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This topic illustrates how to update data in a database by using a DataSet. You can also insert, update, and delete data in a database directly by using a SqlCommand as described in the .NET Framework SDK documentation. After a DataSet is loaded, you can modify the data, and the DataSet will track the changes. The DataSet may be considered an in-memory cache of data that is retrieved from a database. In this topic, you will see how to use the Add method on the DataTable to add new data to a DataSet. The Add method takes either an array of the expected data columns, or a DataRow. To load the DataSet from the database: SqlConnection myConnection = new SqlConnection( "server=(local)\\NetSDK;! Trusted_Connection=yes;database=northwind"); SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter( "Select * from Customers", myConnection); DataSet myDataSet = new DataSet();
Before you can submit the update back to the database, you must set up the InsertCommand, UpdateCommand, and DeleteCommand to reconcile the changes to the database. For limited scenarios you can use the SqlCommandBuilder to automatically generate those for you: SqlCommandBuilder mySqlCommandBuilder = new SqlCommandBuilder(mySqlDataAdapter);
20
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
Because Fill will not cause primary key and unique key information to be retrieved unless AddWithKey is specified, you must set the MissingSchemaAction property to AddWithKey: mySqlDataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey; mySqlDataAdapter.Fill(myDataSet, "Customers"); DataRow myDataRow; myDataRow = myDataSet.Tables["Customers"].NewRow();
The DataTable must return a DataRow through the NewRow method. The method returns a DataRow object with the appropriate schema of the DataTable. The new DataRow is independent of the table until it is added to the RowsCollection. myDataRow["CustomerId"] = "NewID"; myDataRow["ContactName"] = "New Name"; myDataRow["CompanyName"] = "New Company Name"; myDataSet.Tables["Customers"].Rows.Add(myDataRow);
To submit the data from the DataSet into the database, use the Update method on the SqlDataAdapter. mySqlDataAdapter.Update(myDataSet, "Customers");
You can change data in a DataRow by accessing the DataRow. You can use the index of the row in the RowsCollection that is accessed through the Rows property: myDataSet.Tables["Customers"].Rows[0]["ContactName"]="Peach";
You can also access a specific row by the primary key value: DataRow myDataRow1 = myDataSet.Tables["Customers"].Rows.Find("ALFKI"); myDataRow1["ContactName"]="Peach";
In the preceding example, "ALFKI" is the value of the primary key "CustomerID" in the Customers table. When using the SqlDataAdapter, the key is established from the database. You can also set the key through the PrimaryKey property if you are not using the database. Use the Delete method to remove the Row. Note that a logical deletion occurs in the DataSet, which only results in a hard deletion after the DataSet is updated to the database. Similarly, you can use RejectChanges on the DataSet, in which case the Row is restored. myDataSet.Tables["Customers"].Rows[0].Delete();
The original and new values are maintained in the row. The RowChanging event allows you to access both original and new values to decide whether you want the edit to proceed. Because original and new values are maintained, you can establish scenarios such as optimistic locking and key changes.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
21
Demonstration: Accessing Data with DataSets Topic Objective
To demonstrate how to open a database, create a DataSet with multiple tables, and create views on the data.
Lead-in
In this demonstration, you will see how to access data with a DataSet.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this demonstration, you will see how to access data with a DataSet. Run the Visual Studio .NET Accessing Data with DataSets project from the following location: \Democode\Mod16\Demo16.1 The code reads in data from database tables, creates views, and updates the database. Tip You can examine the database tables and stored procedures by using the Server Explorer window Data Connections entries.
22
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
Running this program produces output that is similar to the following: Opened Connection to! server=(local)\NetSDK;! Trusted_Connection=yes;database=northwind Total Number of Regions: 4 Total Number of Customers: 91 Regions: ID: 1 Description: ID: 2 Description: ID: 3 Description: ID: 4 Description:
Eastern Western Northern Southern
First 5 Customer IDs: Customer ID: ALFKI Customer ID: ANATR Customer ID: ANTON Customer ID: AROUT Customer ID: BERGS View sorted by country First 5 Customers in the view: Customer ID: CACTU Country: Customer ID: OCEAN Country: Customer ID: RANCH Country: Customer ID: ERNSH Country: Customer ID: PICCO Country:
Argentina Argentina Argentina Austria Austria
Filtered by country equals Argentina Customers with Country = Argentina, number of entries 3 ID: CACTU Country: Argentina ID: OCEAN Country: Argentina ID: RANCH Country: Argentina
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
23
Displaying Data in the DataGrid Control Topic Objective
To describe how to display data on the client DataGrid controls.
Lead-in
After you connect to a data source and retrieve data from it, the next step is to display the data on the client.
!
Create a Windows Forms DataGrid control
dataGrid1 dataGrid1 == new new System.Windows.Forms.DataGrid(); System.Windows.Forms.DataGrid(); !
Bind to a DataSet dataGrid1.DataSource dataGrid1.DataSource dataGrid1.DataMember dataGrid1.DataMember
!
== ==
myDataSet; myDataSet; "Regions"; "Regions";
Using a custom view
DataView DataView myDataView myDataView == myDataSet.Tables["Customers"].DefaultView; myDataSet.Tables["Customers"].DefaultView; myDataView.RowFilter myDataView.RowFilter == "Country "Country == 'Argentina'"; 'Argentina'"; dataGrid2.DataSource dataGrid2.DataSource == myDataView; myDataView;
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Displaying data from a data source is extremely simple and flexible in either Windows Forms or ASP.NET. These application development environments include a set of controls that perform the function of displaying data. You only need to bind these controls to a data source. To display data on the client, you can use any list-bound control, such as the DataGrid.
Using the DataGrid Control The DataGrid control is designed to produce output that resembles a worksheet. In Visual Studio .NET, you can drag and drop a DataGrid from the Toolbox onto the Design window. This generates code to create a DataGrid object as follows: dataGrid1 = new System.Windows.Forms.DataGrid();
To bind a DataSet to a DataGrid control, you first set the DataSource property of the DataGrid to a DataTable, or DataVieweither the DefaultView property of a DataSet or a custom DataView object. To specify a DataTable, set the DataMember property of the DataGrid to the name of the DataTable.
24
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
Example Binding to a DataSet dataGrid1.DataSource = myDataSet; dataGrid1.DataMember = "Regions";
Alternatively, you can use the Tables collection of the DataSet to assign the DataTable directly to the DataSource of the DataGrid, as in the following example: dataGrid1.DataSource = myDataSet.Tables["Regions"];
If you want to display a different view of data in the DataGrid control, create a new DataView object from the DataSet and bind that to the control. For example, the following code will display only those customers in Argentina.
Example Using a Custom View DataView myDataView = myDataSet.Tables["Customers"].DefaultView; myDataView.RowFilter = "Country = 'Argentina'"; dataGrid2.DataSource = myDataView;
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
25
Demonstration: Displaying Data in a DataGrid Topic Objective
To introduce the Data Grid.
Lead-in
In this demonstration, you will see how to create a Windows Form that reads data from a database and displays it in a DataGrid control.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this demonstration, you will see how to read data from a database into a DataSet and then display it in a DataGrid control. Run the Visual Studio .NET DataGrid project from the following location: \Democode\Mod16\Demo16.2 Running this program displays a button labeled “Load Data” and two DataGrid controls. Clicking the button reads region and customer data from a database and displays them in the grids. A DataView object is used to display only those customers in Argentina.
26
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
" Using Stored Procedures Topic Objective
To introduce the topics in this section.
Lead-in
When working with data from a data source, the ability to use stored procedures is important.
!
Calling Stored Procedures
!
Passing Parameters
!
Calling Action Stored Procedures
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In the past, data processing has been primarily connection-based. Now, in an effort to make multi-tiered applications more efficient, data processing is turning to a message-based approach that revolves around chunks of information. In ADO.NET, this is accomplished by the DataAdapter object, which provides a bridge to retrieve and save data between a DataSet object and its source data store. The DataAdapter object accomplishes this by invoking the appropriate SQL commands against the data store. Both the SqlDataAdapter and the OleDbDataAdapter classes feature four command objects, InsertCommand, UpdateCommand, DeleteCommand, and SelectCommand. These objects provide the create, update, delete, and read functions for a specific DataTable in the DataSet. These command objects are used when you want to perform a number of updates at the same time. Instead of having one stored procedure to do it all, you can put SQL Statements in each object and call the Update method. For more information about these command objects, see the .NET Framework SDK documentation. However, the easier and more commonly used method of updating data in a database is to use stored procedures. You can use stored procedures to read and modify data from a database. You can call stored procedures both from DataAdapter and Command objects. In this section, you will learn how to call stored procedures, pass parameterized stored procedures, and call action stored procedures.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
27
Calling Stored Procedures Topic Objective
To explain how to use stored procedures to retrieve data in a database.
Lead-in
Like ADO, ADO.NET allows developers to use stored procedures to modify data.
!
Stored Procedures Provide Security for Database
!
Set Up the DataAdapter
SqlDataAdapter SqlDataAdapter mySqlDataAdapter mySqlDataAdapter == new new SqlDataAdapter(); SqlDataAdapter(); mySqlDataAdapter.SelectCommand mySqlDataAdapter.SelectCommand == new new SqlCommand(); SqlCommand(); mySqlDataAdapter.SelectCommand.Connection mySqlDataAdapter.SelectCommand.Connection == mySqlConnection; mySqlConnection; mySqlDataAdapter.SelectCommand.CommandText mySqlDataAdapter.SelectCommand.CommandText == "GetProducts"; "GetProducts"; mySqlDataAdapter.SelectCommand.CommandType mySqlDataAdapter.SelectCommand.CommandType == CommandType.StoredProcedure; CommandType.StoredProcedure; !
Run the Stored Procedure and Store Returned Records
mySqlDataAdapter.Fill(myDataSet,"Products"); mySqlDataAdapter.Fill(myDataSet,"Products");
*****************************ILLEGAL FOR NON-TRAINER USE****************************** A stored procedure is a sequence of Transact-SQL (T-SQL) statements stored on the database server. Stored procedures provide a level of security to a database. The database designer can create stored procedures to retrieve and modify data, and not allow developers access to the actual tables of the database. In this way, the database designer can then change the structure of the database without breaking applications that use it. Though it is not always the case, stored procedures can return a set of records. Stored procedures can encapsulate repetitive tasks and execute them efficiently. When you call a stored procedure that returns a set of records, use a DataAdapter and the Fill method. When you call a stored procedure that performs some function on the database but does not return a set of records, use a Command object and the ExecuteNonQuery method.
28
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
Calling a Select Stored Procedure Using stored procedures in ADO.NET is similar to ADO. You create a Command object and point it to the database connection. Next, you set the CommandText property to the name of the stored procedure and the CommandType property to CommandType.StoredProcedure. The following is the GetProducts stored procedure. It returns a list of ProductNames. PROCEDURE GetProducts as select ProductName from Products
Using a Connection object named mySqlConnection, the following example shows how to setup the SqlDataAdpator for the GetProducts stored procedure: SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter(); mySqlDataAdapter.SelectCommand = new SqlCommand(); mySqlDataAdapter.SelectCommand.Connection = mySqlConnection; mySqlDataAdapter.SelectCommand.CommandText = "GetProducts"; mySqlDataAdapter.SelectCommand.CommandType = CommandType.StoredProcedure;
Note You can directly set the connection and command text when creating the SqlDataAdapter object: SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter( "GetProducts", mySqlConnection);
You then set the CommandType property before you call the Fill method. mySqlDataAdapter.SelectCommand.CommandType = CommandType.StoredProcedure;
To execute the stored procedure, call the Fill method of the mySqlDataAdapter object. This fills a DataTable object with the returned records of the stored procedure. DataSet myDataSet = new DataSet(); mySqlDataAdapter.Fill(myDataSet,"Products");
After you have filled a DataTable with the results of a Select stored procedure, you can bind it to a list-bound control to display the data. For example, to bind to a Windows Forms DataGrid control named dataGrid1: dataGrid1.DataSource = myDataSet.Tables["Products"];
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
29
Passing Parameters Topic Objective
To show students how to use parameterized stored procedures.
Lead-in
When using Microsoft SQL Server, or other procedure-based databases, you can use parameters to pass and retrieve information from the database.
!
Create Parameter, Set Direction and Value, Add to the Parameters Collection
SqlParameter SqlParameter workParam workParam == new new SqlParameter("@ProductName",SqlDbType.NChar, SqlParameter("@ProductName",SqlDbType.NChar, 40); 40); workParam.Direction workParam.Direction == ParameterDirection.Input; ParameterDirection.Input; // // Input Input is is default default workParam.Value = textBox1.Text; workParam.Value = textBox1.Text; mySqlDataAdapter.SelectCommand.Parameters.Add( mySqlDataAdapter.SelectCommand.Parameters.Add( workParam); workParam); !
Run Stored Procedure
DataSet DataSet myDataSet myDataSet == new new DataSet(); DataSet(); mySqlDataAdapter.Fill(myDataSet,"ProductData"); mySqlDataAdapter.Fill(myDataSet,"ProductData");
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Delivery Tip
Stress that, when you create a parameter, you must use the same name that was used in the stored procedure. Also, stress that the order in which you create parameters does not matter.
When using Microsoft SQL Server, or other procedure-based databases, parameters can be used to pass and retrieve information from the database. Using parameters in ADO.NET works just as it does in ADO. You can pass the string in the command or use the parameters collection. When using parameters, the names of the parameters added to the parameters collection of the command must match the names of the parameter markers in the stored procedure.
Parameters Your application can pass specific data to a stored procedure by using parameters. The following table describes the types of parameters that are available to you. Direction
Use
Input
Used by your application to send specific data values to a stored procedure.
Output
Used by a stored procedure to send specific values back to the calling application.
InputOutput
Used by a stored procedure to both retrieve information sent by your application and to send specific values back to the application.
ReturnValue
Used by a stored procedure to send a return value back to the calling application.
30
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
Creating a Parameter To create a parameter for the SqlDataAdapter object, create a new SqlParameter object with the name and data type of the parameter. Next, set the Direction property of the new parameter to indicate how the parameter is used by the stored procedure. If the parameter is an input parameter, set the Value property to specify the data that should be sent to the server. For example, the GetProductData stored procedure takes one input parameter: PROCEDURE GetProductData ! @ProductName nchar(40) as select * from Products ! where @ProductName = ProductName
To call the GetProductData stored procedure, create an input parameter named @ProductName and set its value to the Text property of a text box named textBox1. SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter(); mySqlDataAdapter.SelectCommand = new SqlCommand(); mySqlDataAdapter.SelectCommand.Connection = mySqlConnection; mySqlDataAdapter.SelectCommand.CommandText = "GetProductData"; mySqlDataAdapter.SelectCommand.CommandType = CommandType.StoredProcedure; SqlParameter workParam = new SqlParameter("@ProductName", SqlDbType.NChar, 40); // ParameterDirection.Input is the default for the Direction // property. Thus the following line is not needed here. // To set the Direction property to its default value, // use the following line. // workParam.Direction = ParameterDirection.Input; workParam.Value = textBox1.Text;
After you have created the parameter, use the Add method of the Parameters collection of the SelectCommand object. The Add method takes a SqlParameter as an argument. If a stored procedure has more than one parameter, it does not matter in which order you add them because you create them by name. mySqlDataAdapter.SelectCommand.Parameters.Add(workParam);
Use the Fill method to run the stored procedure and retrieve the records. DataSet myDataSet = new DataSet(); mySqlDataAdapter.Fill(myDataSet,"ProductData"); // bind to a DataGrid dataGrid.DataSource = myDataSet.Tables["ProductData"];
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
31
Calling Action Stored Procedures Topic Objective
To show how to call a stored procedure that does not return records.
Lead-in
When calling a stored procedure that does not return records, you use the SqlCommand directly and call the ExecuteNonQuery method to run it.
!
Use SQLCommand object
SqlCommand SqlCommand mySqlCommand mySqlCommand == new new SqlCommand("GetProductCount", SqlCommand("GetProductCount", mySqlConnection); mySqlConnection); mySqlCommand.CommandType mySqlCommand.CommandType == CommandType.StoredProcedure; CommandType.StoredProcedure; SqlParameter SqlParameter workParam workParam == new new SqlParameter("@ProductCount", SqlParameter("@ProductCount", SqlDbType.Int); SqlDbType.Int); workParam.Direction workParam.Direction == ParameterDirection.Output; ParameterDirection.Output; mySqlCommand.Parameters.Add(workParam); mySqlCommand.Parameters.Add(workParam); !
Call the ExecuteNonQuery Method
mySqlCommand.ExecuteNonQuery(); mySqlCommand.ExecuteNonQuery(); !
Retrieve Output Parameters
textBox2.Text textBox2.Text == mySqlCommand.Parameters["@ProductCount"].! mySqlCommand.Parameters["@ProductCount"].! Value.ToString(); Value.ToString();
*****************************ILLEGAL FOR NON-TRAINER USE****************************** When calling a stored procedure that does not return records, you use the SqlCommand directly and call the ExecuteNonQuery method to run it. The GetProductCount stored procedure returns the number of products: PROCEDURE GetProductCount @ProductCount int out as ! select @ProductCount=COUNT(ProductID) from Products
Because this stored procedure does not return a set of records, you do not need to use a DataAdapter object. Instead, you can use a Command object directly, and call the ExecuteNonQuery method to run the stored procedure. To call this stored procedure, create an output parameter named @ProductCount, add it to the Parameters collection of a Command object, and then call ExecuteNonQuery to run the stored procedure: SqlCommand mySqlCommand = new SqlCommand("GetProductCount", mySqlConnection); mySqlCommand.CommandType = CommandType.StoredProcedure; SqlParameter workParam = new SqlParameter("@ProductCount", SqlDbType.Int); workParam.Direction = ParameterDirection.Output; mySqlCommand.Parameters.Add(workParam); // execute the stored procedure mySqlCommand.ExecuteNonQuery();
32
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
Retrieving an Output Parameter Delivery Tip
If a stored procedure returns records and an output parameter, retrieve the records in the usual way by calling the Fill method.
If you need to retrieve a value from a stored procedure that returns a value or sets an output parameter, use the Parameters collection to find the value of the output parameter. You can reference the value of the output parameter by name or index. The following example code retrieves the value of the @ProductCount output parameter by name and assigns it to a text box named textBox2: textBox2.Text = mySqlCommand.Parameters["@ProductCount"].Value.ToString();
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
33
Demonstration: Calling Stored Procedures Topic Objective
To introduce the demonstration.
Lead-in
In this demonstration, you will see how to call stored procedures both with and without parameters.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this demonstration, you will see how to create and call stored procedures with and without parameters. Run the Visual Studio .NET StoredProcedure project from the following location: \Democode\Mod16\Demo16.3 Tip You can examine the database tables and stored procedures by using the Server Explorer window Data Connections entries. To create and execute a stored procedure that returns the product names of all the entries in the Products table, click the Load Product Names button. Typing a name in the text box that is labeled Enter Product Name allows you to click on the Load Product Data button. This button creates and executes a stored procedure that takes the product name as an input parameter and returns all the fields of that product. To create and execute a stored procedure that has an output parameter, which gets set to the number of products in the database’s Products table, click the Get Number of Products button.
34
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
Lab 16: Using ADO.NET to Access Data Topic Objective
To introduce the lab.
Lead-in
In this lab, you will create an XML Web service and a Windows form that reads and updates database records by using ADO.NET.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Explain the lab objectives.
Objectives After completing this lab, you will be able to: !
Use the rapid application design facilities of Visual Studio .NET to create an XML Web service whose methods can be used to read and update a database by using ADO.NET.
!
Use Visual Studio .NET rapid application design facilitates to create a Windows Forms application that displays and enables a user to update a DataGrid control that is bound to a DataSet. The DataSet is obtained and updated by using an XML Web service.
Lab Setup Only solution files are associated with this lab. The solution files for this lab are in the folder \Labs\Lab16\Solution.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
35
Scenario This lab is based on the Visual Studio .NET Framework SDK Walkthrough: Creating a Distributed Application. In this lab, you will create a multi-tiered, distributed application. The application consists of three logical tiers: data, business object, and user interface. The data tier is a SQL Server database. The business object tier handles the tasks of accessing the data and distributing it to the clients. The user interface tier consists of a Windows-based application. You will create the Authors XML Web service and the Windows Client Application. The following diagram shows the architecture of the distributed application:
As part of the distributed application, you will build a simple data application with look-up and update functionality. You will create an XML Web service to retrieve data from the Authors table in the SQL Server Pubs sample database. You will also build a Windows-based client application to display the results of the query by the XML Web service. The communication between the client and the XML Web service is handled by using HTTP and XML. For this lab, the data has already been generated and is available in the Pubs sample database. Therefore, you will begin by creating the business object, the XML Web service, followed by building the Windows Form user interface.
Estimated time to complete this lab: 60 minutes
36
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
Exercise 1 Creating the Middle-Tier Business Object In this exercise, you will create a business object that runs on a Web server. You will implement the business object as an XML Web service that holds the data connections and dataset definition. After first creating an ASP.NET Web Service project in Visual Studio .NET, you will use the Visual Studio .NET design environment to add a data connection, a data adapter, and a dataset class definition to the XML Web service. You will add two methods to the XML Web service: GetAuthors, which returns a dataset from the database, and UpdateAuthors, which updates the database with changes from the user.
! Create a ASP.NET Web Service project 1. Open Visual Studio .Net and on the File menu, click New, and then click Project to display the New Project dialog box. 2. In the Project Types pane, click Visual C# Projects, and then select ASP.NET Web Service in the Templates pane. 3. In the Location box, type the name of the Web server, http://localhost/AuthorsWebService. The dimmed Name box will now contain the text: AuthorsWebService. Click OK. 4. The AuthorsWebService project is added to the solution. The Component Designer for Service1.asmx appears in the development environment. In this component, you will create a connection to the data store and obtain an instance of the data by using a dataset. In Solution Explorer, double-click Service1.asmx. 5. In the Properties window, set the Name property of Service1 to AuthorsService. 6. In Solution Explorer, right-click the Service1.asmx file, click Rename, and rename the file AuthorsService.asmx, to match the service name.
! Create a database connection and data adapter 1. If Server Explorer is not visible, on the View menu, click Server Explorer. 2. In Server Explorer, right-click the Data Connections node and on the shortcut menu, click Add Connection. 3. In the Connection tab of the Data Link Properties dialog box, type the name of the SQL Server where the pubs database is installed: (local)\NETSDK Select Use Windows NT Integrated security for the logon information. 4. Select the pubs database from the list. Click Test Connection to validate the information that you provided, and then click OK to establish the connection. A new node appears in the Data Connections node of Server Explorer. 5. In Server Explorer, expand the node for the new connection node and then expand the Tables node.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
37
6. Find the authors node and expand it to show the fields in the authors table. 7. Using CTRL+Click, select the au_id, au_lname, au_fname, and city fields. 8. Drag these fields from Server Explorer onto the design surface. A SqlConnection that is paired with a SqlDataAdapter appears in the designer. A connection has now been created to the database, with the transfer of information to be handled by the SqlDataAdapter. These components are configured to move a dataset with the selected fields of the authors table in and out of the database.
! Configure Integrated Windows authentication 1. Start the Internet Information Services tool. It can be run from the Start button, Control Panel, Performance and Maintenance, Administrative Tools. 2. Expand the node for your server. 3. Expand the Default Web Site node. 4. Right-click the node for AuthorsWebService and click Properties. 5. Click the Directory Security tab. 6. Click the Edit button in the Anonymous access and authentication control section, click the Edit button. 7. Clear the Anonymous access check box. 8. Select the Integrated Windows authentication check box. Click OK to return to the AuthorsWebService Properties and click OK again. You have now configured your XML Web service directory. 9. Returning to the project in Visual Studio, in Solution Explorer, doubleclick the Web.config file. 10. Add the following tag on the line after the <system.web> tag to configure integrated security for your XML Web service.
! Create a dataset class definition 1. In Solution Explorer, double-click the AuthorsService.asmx file to open it in the designer. 2. On the Data menu, click Generate DataSet. In the Generate Dataset dialog box, select New and name the dataset authors1. Do not select the Add this dataset to the designer check box. Click OK. Note A dataset schema file, authors1.xsd, is created and added to the project. This schema file contains a class definition for authors1. This class, which inherits from DataSet, contains a typed dataset definition for the authors table. 3. On the File menu, click Save All.
38
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
! Add methods to the XML Web service 1. In Solution Explorer, double-click AuthorsService.asmx, if it is not already open in the designer. 2. On the View menu, click Code. 3. Add a method named GetAuthors to deliver a dataset to the client. This method creates a new authors1 dataset, and fills it by using the SqlDataAdapter that is based on the authors table. The method then returns the dataset: The following code shows the method: [WebMethod] public authors1 GetAuthors() { authors1 authors = new authors1(); sqlDataAdapter1.Fill(authors); return authors; }
4. Add a method named UpdateAuthors to propagate changes from the client back to the database. This method has an authors1 dataset parameter (authorChanges) that contains the changed data and updates the database through the SqlDataAdapter.Update method. The Update method accepts the changes in the dataset. The dataset is returned to the client. The client then uses this returned dataset to update its own instance of the authors1 dataset. The following code shows the method: [WebMethod] public authors1 UpdateAuthors(authors1 authorChanges) { if (authorChanges != null) { sqlDataAdapter1.Update(authorChanges); return authorChanges; } else { return null; } }
Note In a production application, you would add error checking and exception handling to these methods. 5. On the File menu, click Save All. 6. On the Build menu, click Build Solution. 7. To test the XML Web service, in the Solution Explorer right-click AuthorsService.asmx and click View in Browser. In the browser, click GetAuthors and in the resulting GetAuthors page, click the Invoke button. You should see the list of authors displayed as XML.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
39
Exercise 2 Creating a Windows User Interface In this exercise, you will create a Windows Forms client application. The application consists of one Windows Form that contains a reference to AuthorsWebService. The data in the database is displayed in a DataGrid control when a Load button on the form is clicked. To implement the display of data in the DataGrid, you call the XML Web service’s GetAuthors method. The DataGrid control enables direct editing of the data, with changes being passed directly to the underlying dataset. In addition to a Load button, the form has a save button. The code for this button calls the XML Web service's UpdateAuthors method to save the changes back to the database.
! Create the Windows application 1. Open Visual Studio .NET and on the File menu, click New, and then click Project to display the New Project dialog box. 2. Select Visual C# Projects in the Project Types pane, then select Windows Application in the Templates pane. 3. Name the project AuthorsWinClient. 4. In the Location box, type the name of the Starter folder: \Labs\Lab16\Starter 5. Click OK. The AuthorsWinClient project is created and Form1 is automatically added to the project and appears in the Windows Forms Designer. 6. Add a reference to the XML Web service project that you created earlier. a. In Solution Explorer, expand the AuthorsWinClient node, right-click the References node, and on the shortcut menu, click Add Web Reference. b. In the Address box at the top of the Add Web Reference dialog box, type the following and then press ENTER: http://localhost/AuthorsWebService/AuthorsService.asmx This is the location of the XML Web service file of your ASP.NET Web Service project. c. Click Add Reference. You can now create an instance of the authors1 dataset in your application.
40
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
! Add the controls to the form 1. Drag a DataGrid control from the Windows Forms tab of the Toolbox onto the form. 2. Drag a Button control from the Windows Forms tab of the Toolbox onto the form. Set the button's Name property to LoadData and its Text property to Load. 3. Drag a second Button control from the Windows Forms tab of the Toolbox onto the form. Set the button's Name property to SaveData and its Text property to Save. 4. Drag a DataSet object from the Data tab of the Toolbox onto the form. In the Add DataSet dialog box, select Typed dataset and then select AuthorsWinClient.localhost.authors1 from the Name list. Click OK. This action creates a DataSet object in the component tray that is based on the authors1 dataset class definition. 5. Select the DataSet control and set the Name property to AuthorData. 6. Select the DataGrid control and select AuthorData from the DataSource property list. Select authors from the DataMember property list. The column headings of the DataGrid are set to the Authors table column names. You may want to adjust the width of the form and the DataGrid control to accommodate all of the column headings. The user can then avoid having to scroll horizontally to view all of the columns in the DataGrid.
! Add code for the LoadData and SaveData buttons 1. On the View menu, click Designer. Double-click the button that is labeled Load to create an empty event handler for the Click event. Add the following code to the LoadData_Click method: private void LoadData_Click( object sender, System.EventArgs e) { AuthorsWinClient.localhost.AuthorsService ws = new AuthorsWinClient.localhost.AuthorsService(); ws.Credentials = System.Net.CredentialCache.DefaultCredentials; AuthorData.Merge(ws.GetAuthors()); }
XML Web service methods are called by first creating an instance of the service class, and then calling the service methods. In this case, the GetAuthors method is called. The dataset returned is merged with the AuthorData dataset.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
41
2. On the View menu, click Designer. Double-click the button labeled Save to create an empty event handler for the Click event. Add the following code to the SaveData_Click method: private void SaveData_Click( object sender, System.EventArgs e) { if (AuthorData.HasChanges()) { AuthorsWinClient.localhost.AuthorsService ws = new AuthorsWinClient.localhost.AuthorsService(); ws.Credentials = System.Net.CredentialCache.DefaultCredentials; AuthorsWinClient.localhost.authors1 diffAuthors = new AuthorsWinClient.localhost.authors1(); diffAuthors.Merge(AuthorData.GetChanges()); ws.UpdateAuthors(diffAuthors); AuthorData.Merge(diffAuthors); } }
If there are changes in the dataset, a new dataset of type authors1 is created to hold just the changed data. This dataset is then passed to the XML Web service's UpdateAuthors method. The dataset is returned with the changes accepted, and the AuthorData dataset is updated to reflect these new changes.
! Run the application 1. On the File menu, click Save All. 2. In Solution Explorer, right-click AuthorsWinClient, and click Set as StartUp Project. 3. Press CTRL+F5 to run the application. A window is displayed that contains an empty table with headers from the authors table in the pubs database. 4. Click Load to populate the table. Make some changes to the data, and then click Save. You can click Load again to see that the changes that you made were stored in the database. Also, close and reopen the application. The next time you click Load, you will see that your changes have persisted.
42
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
" Accessing Data with DataReaders Topic Objective
To introduce the topics in this section.
Lead-in
Another method of accessing data from a data source is to use DataReaders.
!
Creating a DataReader
!
Reading Data from a DataReader
!
Using DataSets vs. DataReaders
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The benefit of using a DataSet is that it gives you a disconnected view of the database. For long running applications, this is often the best approach. For Web applications, developers usually perform short and simple operations with each request, such as displaying data. For such operations, developers do not have to maintain a DataSet object. In such cases, you can use a DataReader.
Choosing a DataReader or a DataSet When deciding whether your application should use a DataReader or a DataSet, you should consider the type of functionality that your application requires. Use a DataSet when you need to do any of the following: !
Remote data between tiers or from an XML Web service.
!
Interact with data dynamically such as binding to a Windows Forms control or combining and relating data from multiple sources.
!
Cache data locally in your application.
!
Provide a hierarchical XML view of relational data and use tools like an XSL Transformation or an XML Path Language (XPath) Query on your data. For more information, see XML and the DataSet in the .NET Framework SDK documentation.
!
Perform extensive processing on data without requiring an open connection to the data source, which frees the connection to be used by other clients.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
43
If you do not require the functionality that is provided by the DataSet, you can improve the performance of your application by using the DataReader to return your data in a forward-only read-only fashion. Although the DataAdapter uses the DataReader to fill the contents of a DataSet, by using the DataReader instead, you can receive the following performance gains: !
The saving of memory that would be consumed by the DataSet.
!
The saving of the processing that would be required to create and fill the contents of the DataSet.
In this section, you will learn how to read data from a data source by using DataReaders.
44
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
Creating a DataReader Topic Objective
To describe how to retrieve data from a database by using a DataReader.
Lead-in
You can also use a DataReader object to read data from a database.
!
Create and Open the Database Connection
SqlConnection SqlConnection mySqlConnection mySqlConnection == new new SqlConnection("server=(local)\\NetSDK; SqlConnection("server=(local)\\NetSDK; ! ! Trusted_Connection=yes;database=northwind"); Trusted_Connection=yes;database=northwind"); mySqlConnection.Open(); mySqlConnection.Open(); !
Create the DataReader From a Command Object
SqlCommand SqlCommand mySqlCommand mySqlCommand == new new SqlCommand( SqlCommand( "select * from customers", "select * from customers", mySqlConnection); mySqlConnection); SqlDataReader SqlDataReader myReader myReader == mySqlCommand.ExecuteReader(); mySqlCommand.ExecuteReader(); !
Close the Reader and the Connection
If If (myReader (myReader != != null) null) myReader.Close(); myReader.Close(); if (mySqlConnection.State if (mySqlConnection.State == == ConnectionState.Open) ConnectionState.Open) mySqlConnection.Close(); mySqlConnection.Close();
*****************************ILLEGAL FOR NON-TRAINER USE****************************** When a large amount of data is being retrieved, holding memory open becomes an issue. For example, reading 10,000 rows out of a database causes a DataTable to allocate and maintain memory for those 10,000 rows for the lifetime of the table. If 1,000 users do this against the same server at the same time, memory usage becomes critical. To address such situations, the DataReader is designed to produce a read-only, forward-only stream returned from the database. Only one record at a time is ever in memory. There are two DataReader objects, the SqlDataReader and the OleDbDataReader. A DataReader keeps the connection open until the DataReader is closed. To use a SqlDataReader, declare a SqlCommand instead of a SqlDataAdapter. The SqlCommand exposes an ExecuteReader method that takes a SqlDataReader as a parameter. Note that you must explicitly open and close the SqlConnection when you use a SQLCommand. SqlConnection mySqlConnection = new SqlConnection("server=(local)\\NetSDK;! Trusted_Connection=yes;database=northwind"); mySqlConnection.Open(); SqlCommand mySqlCommand = new SqlCommand( "select * from customers", mySqlConnection); SqlDataReader myReader = mySqlCommand.ExecuteReader(); //... if (myReader != null) myReader.Close(); if (mySqlConnection.State == ConnectionState.Open) mySqlConnection.Close();
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
45
Error Handling with a DataReader When using connections with the DataReader object, you should always use a try/finally block to ensure that, if anything fails, connections will be closed. SqlDataReader myReader = null; SqlConnection mySqlConnection = new SqlConnection( "server=(local)\\NetSDK;! Trusted_Connection=yes;database=northwind"); SqlCommand mySqlCommand = new SqlCommand( "select * from customers", mySqlConnection); try { mySqlConnection.Open(); myReader = mySqlCommand.ExecuteReader(); //… } catch(Exception e) { Console.WriteLine(e.ToString()); } finally { if (myReader != null) myReader.Close(); if (mySqlConnection.State == ConnectionState.Open) mySqlConnection.Close(); }
46
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
Reading Data from a DataReader Topic Objective
To explain how to read data from a DataReader.
!
Lead-in
After you have called the ExecuteReader method of the Command object, you access a record in the DataReader by calling the Read method.
Call Read for Each Record #
!
Returns false when there are no more records
Get Field(s) #
Parameter is the ordinal position or name of the field
while while (myReader.Read()) (myReader.Read()) {{ Console.Write(myReader["CustomerID"].ToString() Console.Write(myReader["CustomerID"].ToString() ++ "" "); "); Console.WriteLine(myReader["CompanyName"].ToString()); Console.WriteLine(myReader["CompanyName"].ToString()); }} !
Call Close to Free Up the Reader and the Connection
*****************************ILLEGAL FOR NON-TRAINER USE****************************** After you have called the ExecuteReader method of the Command object, you access a record in the DataReader by calling the Read method. The default positioning in the DataReader is before the first record, therefore you must call Read before accessing any data. When no more records are available, the Read method returns a null value.
Reading Fields from the Current Record To get the data from fields in the current record, call an appropriate Get method, for example, GetDateTime, GetDouble, GetInt32, or GetString. The parameter of the Get method is the ordinal value of the field that you want to read. For example, the following code reads the CustomerID and CompanyName fields, from the current record of the DataReader, by using the GetString method: string customerID = myReader.GetString(0); string companyName = myReader.GetString(1);
You can also reference the fields of data in the current record of the data reader by name, and then call an appropriate conversion function, as shown in the following example code: string customerID = myReader["CustomerID"].ToString(); string companyName = myReader["CompanyName"].ToString();
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
47
Looping Through All Records To loop through and output to the Console all the records in a DataReader, you can use a While loop, as shown in the following sample code: while (myReader.Read()) { Console.Write(myReader["CustomerID"].ToString() + " "); Console.WriteLine(myReader["CompanyName"].ToString()); }
Closing the DataReader While the DataReader is in use, the associated connection is busy serving the DataReader. Therefore, you must call Close to close the DataReader when you are finished using it. if (myReader != null) myReader.Close(); if (mySqlConnection.State == ConnectionState.Open) mySqlConnection.Close();
48
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
Demonstration: Accessing Data Using DataReaders Topic Objective
To introduce the demonstration.
Lead-in
In this demonstration, you will see how to read data from a database by using a SqlDataReader.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this demonstration, you will see how to read data from a database by using a SQLDataReader. Run the Visual Studio .NET DataReader project from the following location: \Democode\Mod16\Demo16.4
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
49
Using DataSets vs. DataReaders Topic Objective
To describe the different processes for getting data with a DataSet versus a DataReader.
DataSet
DataReader
1.
Create a database connection
1.
Create a database connection
Lead-in
2.
Store query in DataAdapter
2.
Open the database connection
3.
Populate DataSet with Fill method
3.
Store query in SqlCommand
4.
4.
Create DataView
Populate DataReader with ExecuteReader method
5.
Bind DataView to list-bound control
5.
Call Read for each record, and Get for each field
6.
Manually display data
7.
Close the DataReader and the connection
The procedure for accessing databases from ASP.NET is different depending on whether you use a DataSet or a DataReader.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Delivery Tip
Discuss performance issues in DataReaders and DataSets.
The general procedure for accessing databases from ASP.NET is different depending on whether you will be using a DataSet or a DataReader: Using DataSets
Using DataReaders
1. Connect to the database by using SqlConnection or OleDbConnection.
1. Connect to the database by using SqlConnection or OleDbConnection.
2. Store the database query in SqlDataAdapter or OleDbDataAdapter objects.
2. Open the connection with the Open method.
3. Populate a DataSet from the DataAdapter by using Fill.
3. Store database query in SqlCommand or OleDbCommand objects.
4. Set up a new DataView for the desired table.
4. Populate a DataReader from the Command by using ExecuteReader method.
5. Bind a server control, such as the DataGrid, to the DataView.
5. Call Read and Get methods of the DataReader to read data. 6. Manually display data. 7. Close the DataReader. and the connection.
50
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
" Binding to XML Data Topic Objective
To introduce the topics included in this section.
Lead-in
Another common source of data for applications is XML documents.
!
Overview of XML
!
Reading XML Data into a DataSet
*****************************ILLEGAL FOR NON-TRAINER USE****************************** HTML is widely used for presenting information on the Web. HTML works well as a presentation language, but it is not suitable for representing data. For example, you can easily format data in an HTML table, but you cannot describe the individual components of the information. To share information between applications, you must have a language that can describe data in a standardized way so that any application, present or future, can understand and use this data correctly. XML is one such standardized language. XML not only helps you structure your data but acts as a common language between different business applications. In this section, you will get an overview of XML. You will also learn how to read and display XML data by using ADO.NET.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
51
Overview of XML Topic Objective
To provide an overview of XML data.
Lead-in
XML is a common language used for exchanging information between business applications.
!
Machine-Readable and Human-Readable Data
!
Defines the Data Content and Structure
!
Separates Structure from Presentation
!
Allows You to Define Your Own Tags and Attributes <employee> <employee> Jake Jake <salary>25000 <salary>25000 Ohio Ohio
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Businesses today face many problems when it comes to organizing data. They need to meet the following requirements: !
Data must be readable by both computers and users.
!
Both the content and the structure of the data must be defined.
!
The structure of the data needs to be separate from the presentation of the data.
!
The structure needs to be open and extensible.
XML fulfills all of these requirements. XML defines the structure of data in an open and self-describing manner. This allows data to be easily transferred over a network and consistently processed by the receiver. XML describes how data is structured, not how it should be displayed or used. XML documents contain tags that assign meaning to the content of the document. These tags allow programmers to find the data they need in the XML document. For example, the following XML sample contains information about an employee but does not specify how to display this information: <employee> Jake <salary>25000 Ohio
52
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
XML is considered a markup language because it allows you to define data structure by using markup tags. You can define your own tags that describe the data in whatever way you find useful. XML data is held in a simple, open format that is easily parsed by other applications. The fact that XML documents contain text rather than binary data is another key advantage. Applications can parse an XML document, looking for specific tags of interest to those applications. Unknown tags and their associated data can be freely ignored.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
53
Reading XML Data into a DataSet Topic Objective
To explain how to read XML data into a DataSet.
Lead-in
Because XML is an important part of ASP.NET, there are various easy ways to bind to XML data.
!
Setup the XML file for reading
FileStream FileStream fs fs == File.OpenRead("myFile.xml"); File.OpenRead("myFile.xml"); StreamReader StreamReader myStreamReader myStreamReader == new new StreamReader(fs); StreamReader(fs); !
Read the contents of the File into a DataSet DataSet DataSet myDataSet myDataSet == new new DataSet(); DataSet(); myDataSet.ReadXml(myStreamReader); myDataSet.ReadXml(myStreamReader);
!
Bind a DataGrid to the DataSet dataGrid1.DataSource dataGrid1.DataSource == myDataSet.Tables[0]; myDataSet.Tables[0];
*****************************ILLEGAL FOR NON-TRAINER USE****************************** DataSets in ADO.NET are designed to extract data in a way that is independent of its data source. Therefore, reading data from an XML source is similar to reading data from a database. To read XML data, you import the System.IO namespace in your ASP.NET page. Note You cannot read XML data into a DataReader. You can read it only into a DataSet. For XML data, the DataSet supports a ReadXml method that takes a FileStream as its parameter. The DataSet expects data to be in the following format: column column column column column column
value value value
value value value
Each TableName section corresponds to a single row in the table.
54
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
The following example shows how to read the schema and data from an XML file by using the ReadXml method, the FileStream object, and the StreamReader object. Note that after the data is read into the DataSet, it is indistinguishable from SQL data—the DataGrid binds to it in the same way. You can perform the following steps to read a file named myFile.xml that contains XML data and displays it in a DataGrid. First open the XML file and create a StreamReader: FileStream fs = File.OpenRead("myFile.xml"); StreamReader myStreamReader = new StreamReader(fs);
Next, read the XML data into the DataSet: DataSet myDataSet = new DataSet(); myDataSet.ReadXml(myStreamReader);
Finally, bind a DataGrid to the DataSet: dataGrid1.DataSource = myDataSet.Tables[0];
After the data has been read into a DataSet, the repeated elements in the XML become the columns in the DataSet and can be bound to any control to be displayed on the client. The Windows Forms code, including try/catch/finally blocks, that is used in Demonstration: Reading XML Data into a DataSet, is as follows: StreamReader myStreamReader = null; statusBar1.Text = ""; try { FileStream fs = File.OpenRead(textBox1.Text); myStreamReader = new StreamReader(fs); DataSet myDataSet = new DataSet(); myDataSet.ReadXml(myStreamReader); dataGrid1.DataSource = myDataSet.Tables[0]; statusBar1.Text = "Read file: " + textBox1.Text; } catch(Exception exception) { statusBar1.Text = exception.ToString(); } finally { if (myStreamReader != null) myStreamReader.Close(); }
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
55
Demonstration: Reading XML Data into a DataSet Topic Objective
To demonstrate how to display information from an XML file.
Lead-in
In this demonstration, you will see how to read information from an XML file and display it in a DataGrid control.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** In this demonstration, you will see how to read information from an XML file and display it in a DataGrid control. Run the Visual Studio .NET XMLData project from the following location: \Democode\Mod16\Demo16.5 Enter the default filename books1.xml and click the Load Data button. The file books1.xml is read into a DataSet and displayed in a DataGrid.
56
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
Review Topic Objective
To reinforce module objectives by reviewing key points.
Lead-in
The review questions cover some of the key concepts taught in the module.
!
Overview of ADO.NET
!
Connecting to a Data Source
!
Accessing Data with DataSets
!
Using Stored Procedures
!
Accessing Data with DataReaders
!
Binding to XML Data
*****************************ILLEGAL FOR NON-TRAINER USE****************************** 1. What are some of the new objects in the ADO.NET object model? Some of the new objects in the ADO.NET object model are: DataSet, DataAdapter, DataView, DataReader, and DataTable.
2. What is the difference between a DataSet and a DataView? DataSet is a collection of DataTables and a DataView is a custom view of the DataTables in a DataSet.
3. What is the difference between a DataSet and a DataReader? The DataReader is designed to produce a read-only, forward-only stream returned from the database. The DataSet is designed to handle the actual data from a data store. It represents a cache of data, with database-like behavior. It can contain tables, columns, relationships, constraints, and data.
Module 16 (Optional): Using Microsoft ADO.NET to Access Data
57
4. What is the purpose of the DataAdapter object? A DataAdapter object is a tool that is used to create and initialize various tables. It allows for the retrieval and saving of data between a DataSet object and the source data store. It is responsible for pulling out data from the physical store and pushing it into data tables and relations.
5. Which method is used to populate a DataSet with results of a query? The method that is used to populate the DataSet with results of a query is the Fill method.
THIS PAGE INTENTIONALLY LEFT BLANK
Module 17 (Optional): Attributes Contents Overview Overview of Attributes Defining Custom Attributes
1 2 13
Retrieving Attribute Values
22
Demonstration: Custom Attributes
26
Lab 17: Defining and Using Attributes
27
Review
36
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places, and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, places or events is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. 2001-2002 Microsoft Corporation. All rights reserved. Microsoft, ActiveX, BizTalk, IntelliMirror, Jscript, MSDN, MS-DOS, MSN, PowerPoint, Visual Basic, Visual C++, Visual C#, Visual Studio, Win32, Windows, Windows Media, and Window NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
Module 17 (Optional): Attributes
Instructor Notes Presentation: 60 Minutes
Teach this module if time permits. Module 17 is a stand-alone module that is not dependent upon any other module.
Lab: 45 Minutes
This module provides students with the details about how to use attributes in code. It describes the predefined attributes that are provided by the Microsoft® .NET Framework and provides some simple examples of how to use some common attributes. The concept of custom attributes is introduced. This introduction is followed by a detailed explanation of how to define and use custom attributes. The process used to compile code that has custom attributes is also explained. Finally, the module describes how to retrieve attribute values during run time by using reflection. The procedure that is used to retrieve attribute information into an array and query the array to obtain the required values is explained. After completing this module, students will be able to: !
Use common predefined attributes.
!
Create simple custom attributes.
!
Query attribute information at run time.
Materials and Preparation This section provides the materials and preparation tasks that you need to teach this module.
Required Materials To teach this module, you need Microsoft PowerPoint® file 2349B_17.ppt.
Preparation Tasks To prepare for this module, you should: !
Read all of the materials for this module.
!
Complete the lab.
!
Read the instructor notes and margin notes for the module.
!
Practice using the Microsoft Intermediate Language disassembler to examine the metadata of an assembly.
iii
iv
Module 17 (Optional): Attributes
Demonstration This section provides demonstration procedures that will not fit in the margin notes or are not appropriate for the student notes. Use the debugger to step through the code while you point out features and ask students what they think will happen next.
Custom Attributes In this demonstration, you will show students how to declare and apply custom attributes and how to use reflection to retrieve custom attribute metadata. The code for this demonstration is contained in one project and is located in \Democode\Mod17.
Module Strategy Use the following strategy to present this module: !
Overview of Attributes Begin by explaining that attributes are only annotations to classes and perform no function themselves. Before attributes can cause an action, certain code must be implemented. This code is in the runtime for the predefined attributes and is written by the developer for custom attributes. Explain the syntax used to apply an attribute, and, after covering the lists of predefined attributes briefly, discuss the three attributes—Conditional, DllImport, and Transaction—using examples.
!
Defining Custom Attributes Introduce the need for creating custom attributes, and explain the procedures involved in defining a custom attribute. Explain the use of AttributeUsage and how to create an attribute class. Then explain the details of the procedure used to compile code that uses custom attributes. Finish the section by explaining how to use multiple attributes in code.
!
Retrieving Attribute Values In this section, introduce the concept of retrieving attribute values at run time by using reflection. Explain how to use the MemberInfo class and the typeof operator to obtain attribute values. Finally, discuss how to iterate through the stored attribute values in an array to retrieve the required values. To end the discussion about attributes, use the review slide to recapitulate the main concepts covered in the module.
Module 17 (Optional): Attributes
1
Overview Topic Objective
To provide an overview of the module topics and objectives.
Lead-in
In this module, you will learn about using attributes in Microsoft Visual C# .NET.
!
Overview of Attributes
!
Defining Custom Attributes
!
Retrieving Attribute Values
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Delivery Tip
Teach this module if time permits. This is a standalone module that is not dependent upon any other module.
Attributes are a simple technique for adding metadata to classes. They can be useful when you need to build components. In this module, you will learn the purpose of attributes and the function that they perform in Microsoft® Visual C#™ .NET applications. You will learn about attribute syntax and how to use some of the predefined attributes in the Microsoft .NET Framework environment. You will also learn to create custom user-defined attributes. Finally, you will learn how classes and other object types can implement and use these custom attributes to query attribute information at run time. After completing this module, you will be able to: !
Use common predefined attributes.
!
Create simple custom attributes.
!
Query attribute information at run time.
2
Module 17 (Optional): Attributes
" Overview of Attributes Topic Objective
To introduce the topics covered in this section.
Lead-in
In this section, you will learn what attributes are and how they are used.
!
Introduction to Attributes
!
Applying Attributes
!
Common Predefined Attributes
!
Using the Conditional Attribute
!
Using the DllImport Attribute
!
Using the Transaction Attribute
*****************************ILLEGAL FOR NON-TRAINER USE****************************** With the introduction of attributes, the C# language provides a convenient technique that will help handle tasks such as changing the behavior of the runtime, obtaining transaction information about an object, conveying organizational information to a designer, and handling unmanaged code. After completing this lesson, you will be able to: !
Identify which tasks you can perform with attributes.
!
Use the syntax for using attributes in your code.
!
Identify some of the predefined attributes that are available in the .NET Framework.
Module 17 (Optional): Attributes
3
Introduction to Attributes Topic Objective
To define attributes.
Lead-in
!
The concept of an attribute is simple.
!
Attributes Are: #
Declarative tags that convey information to the runtime
#
Stored with the metadata of the element
.NET Framework Provides Predefined Attributes #
The runtime contains code to examine values of attributes and act on them
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Delivery Tip
Stress that attributes are fundamentally a very simple idea—they are simply annotations for your code that are intended to convey useful declarative information.
The .NET Framework provides attributes so that you can extend the capabilities of the C# language. An attribute is a declarative tag that you use to convey information to the runtime about the behavior of programmatic elements such as classes, enumerators, and assemblies. You can think of attributes as annotations that your programs can store and use. In most cases, you write the code that retrieves the values of an attribute in addition to the code that performs a change in behavior at run time. In its simplest form, an attribute is an extended way to document your code. You can apply attributes to many elements of the source code. Information about the attributes is stored with the metadata of the elements they are associated with. The .NET Framework is equipped with a number of predefined attributes. The code to examine them and act upon the values they contain is also incorporated as a part of the runtime and .NET Framework software development kit (SDK).
4
Module 17 (Optional): Attributes
Applying Attributes Topic Objective
To explain the syntax for using attributes.
Lead-in
Attributes can be applied to several different types of programming elements.
!
Syntax: Use Square Brackets to Specify an Attribute
[attribute(positional_parameters,named_parameter=value, [attribute(positional_parameters,named_parameter=value, ...)] ...)] element element !
To Apply Multiple Attributes to an Element, You Can: #
Specify multiple attributes in separate square brackets
#
Use a single square bracket and separate attributes with commas
#
For some elements such as assemblies, specify the element name associated with the attribute explicitly
*****************************ILLEGAL FOR NON-TRAINER USE****************************** You can apply attributes to different kinds of programming elements. These elements include assemblies, modules, classes, structs, enums, constructors, methods, properties, fields, events, interfaces, parameters, return values, and delegates.
Attribute Syntax To specify an attribute and associate it with a programming element, use the following general syntax: [attribute(positional_parameters,name_parameter=value, ...)] element
You specify an attribute name and its values within square brackets ([ and ]) before the programmatic element to which you want to apply the attribute. Most attributes take one or more parameters, which can be either positional or named. You specify a positional parameter in a defined position in the parameter list, as you would specify parameters for methods. Any named parameter values follow the positional parameters. Positional parameters are used to specify essential information, whereas named parameters are used to convey optional information in an attribute. Tip Before using an unfamiliar attribute, it is a good practice to check the documentation for the attribute to find out which parameters are available and whether they should be positional or named.
Module 17 (Optional): Attributes
Example As an example of using attributes, consider the following code, in which the DefaultEvent attribute is applied on a class by using a positional string parameter, ShowResult: [DefaultEvent("ShowResult")] public class Calculator: System.Windows.Forms.UserControl { ... }
Applying Multiple Attributes You can apply more than one attribute to an element. You can enclose each attribute in its own set of square brackets, although you can also enclose multiple attributes, separated with commas, in the same set of square brackets. In some circumstances, you must specify exactly which element an attribute is associated with. For example, in the case of assembly attributes, place them after any using clauses but before any code, and explicitly specify them as attributes of the assembly. The following example shows how to use the CLSCompliant assembly attribute. This attribute indicates whether or not an assembly strictly conforms to the Common Language Specification. using System; [assembly:CLSCompliant(true)] class MyClass { ... }
5
6
Module 17 (Optional): Attributes
Common Predefined Attributes Topic Objective
To list some common predefined attributes.
Lead-in
!
.NET Provides Many Predefined Attributes
The .NET Framework provides a large number of predefined attributes.
#
General attributes
#
COM interoperability attributes
#
Transaction handling attributes
#
Visual designer component-building attributes
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The capabilities of predefined attributes in the .NET Framework encompass a wide range of areas, from interoperability with COM to compatibility with visual design tools. This topic describes some of the common predefined attributes that are provided by the .NET Framework. However, it is not intended to be comprehensive. For more information about predefined attributes, refer to the Microsoft Visual Studio® .NET documentation.
General Attributes The following list summarizes some of the general attributes that are provided by the .NET Framework. Attribute
Applicable to
Description
Conditional
Method
Tests to see whether a named symbol is defined. If it is defined, any calls to the method are executed normally. If the symbol is not defined, the call is not generated.
DllImport
Method
Indicates that the method is implemented in unmanaged code, in the specified DLL. It causes the DLL to be loaded at run time and the named method to execute.
SecurityPermissionAttributes
Assembly, Class, Struct, Constructor, Method
Allows security actions for SecurityPermission to be applied to code using declarative security.
Module 17 (Optional): Attributes
Delivery Tip
Avoid getting into long conversations about COM, transactions, and interoperability because this is beyond the scope of this module. This information is presented to show that the .NET Framework is compatible with COM. Tell students that COM and interoperability is covered in depth in Module 15, “Interoperating Between Managed and Unmanaged Code,” in this course.
7
COM Interoperability Attributes When using the attributes to provide interoperability with COM, the goal is to ensure that using COM components from the managed .NET Framework environment is as seamless as possible. The .NET Framework has many attributes relating to COM interoperability. Some of these are listed in the following table. Attribute
Applicable to
Description
ComImport
Class/Interface
Indicates that a class or interface definition was imported from a COM type library.
ComRegisterFunction
Method
Specifies the method to be called when a .NET Framework assembly is registered for use from COM.
ComUnregisterFunction
Method
Specifies the method to be called when a .NET assembly is unregistered for use from COM.
DispId
Method, field, property
Indicates which dispatch ID is to be used for the method, field, or property.
In
parameter
Indicates that the data should be marshaled from the caller to the callee.
MarshalAs
Field, parameter, return values
Specifies how data should be marshaled between COM and the managed environment.
ProgId
Class
Specifies which prog ID is to be used for the class.
Out
parameter
Indicates that data should be marshaled from the callee back to caller.
InterfaceType
Interface
Specifies whether a managed interface is IDispatch, IUnknown, or Dual when it is exposed to COM.
For more information about COM interoperability, see Module 15, “Interoperating Between Managed and Unmanaged Code,” in Course 2349B, Programming with the Microsoft .NET Framework (Microsoft Visual C# .NET), or search for "Microsoft ComServices" in the .NET Framework SDK.
Transaction Handling Attributes Components running in a COM+ environment use transaction management. The attribute you use for this purpose is shown in the following table. Attribute
Applicable to
Description
Transaction
Class
Specify the type of transaction that should be available to this object.
8
Module 17 (Optional): Attributes
Visual Designer Component-Building Attributes Developers who build components for a visual designer use the attributes listed in the following table. Attribute
Applicable to
Description
Bindable
Property
Specifies whether a property is typically used for binding.
DefaultProperty
Class
Specifies the default property for the component.
DefaultValue
Property
Indicates that the property is the default value for the component.
Localizable
Property
When code is generated for a component, members that are marked with Localizable(true) have their property values saved in resource files. You can localize these resource files without modifying the code.
DefaultEvent
Class
Specifies the default event for the component.
Category
Property, event
Specifies the category into which the visual designer should place this property or event in the property window.
Description
Property, event
Defines a brief piece of text to be displayed at the bottom of the property window in the visual designer when this property or event is selected.
Module 17 (Optional): Attributes
9
Using the Conditional Attribute Topic Objective
To show how to use the Conditional attribute.
!
Serves As a Debugging Tool #
Lead-in
The Conditional attribute is frequently used for debugging classes.
#
!
Causes conditional compilation of method calls, depending on the value of a programmer-defined symbol Does not cause conditional compilation of the method itself
Restrictions on Methods
class class MyClass MyClass {{ [Conditional [Conditional ("DEBUGGING")] ("DEBUGGING")] public public static static void void MyMethod( MyMethod( )) {{ ... ... }} }}
#
Must have return type of void
#
Must not be declared as override
#
Must not be from an inherited interface
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Delivery Tip
The Conditional attribute is common and is used in the labs. Make sure students understand how to use it.
You can use the Conditional attribute as a debugging aid in your C# code. This attribute causes conditional compilation of method calls, depending on the value of a symbol that you define. It lets you invoke methods that, for example, display the values of variables, while you test and debug code. After you have debugged your program, you can “undefine” the symbol and recompile your code without changing anything else. (Or you can simply remove the symbol from the command line, and not change anything.)
Example The following example shows how to use the Conditional attribute. In this example, the MyMethod method in MyClass is tagged with the Conditional attribute by the symbol DEBUGGING: class MyClass { [Conditional ("DEBUGGING")] public static void MyMethod( ) { ... } }
10
Module 17 (Optional): Attributes
The symbol DEBUGGING is defined as follows: #define DEBUGGING class AnotherClass { public static void Test( ) { MyClass.MyMethod( ); } }
As long as the symbol DEBUGGING remains defined when the method call is compiled, the method call will operate normally. When DEBUGGING is undefined, the compiler will omit calls to the method. Therefore, when you run the program, it will be treated as though that line of code does not exist. You can define the symbol in one of two ways. You can either add a #define directive to the code as shown in the preceding example, or define the symbol from the command line when you compile your program.
Restrictions on Methods The methods to which you can apply a Conditional attribute are subject to a number of restrictions. In particular, they must have a return type of void, they must not be marked as override, and they must not be the implementation of a method from an inherited interface. Note The Conditional attribute does not cause conditional compilation of the method itself. The attribute only determines the action that will occur when the method is called. If you require conditional compilation of a method, then you must use the #if and #endif directives in your code.
Module 17 (Optional): Attributes
11
Using the DllImport Attribute Topic Objective
To explain how to use the DllImport attribute.
Lead-in
The DllImport attribute is used to handle unmanaged code.
!
With the DllImport Attribute, You Can: #
Invoke unmanaged code in DLLs from a C# environment
#
Tag an external method to show that it resides in an unmanaged DLL
[DllImport("MyDLL.dll", [DllImport("MyDLL.dll", EntryPoint=“MyFunction")] EntryPoint=“MyFunction")] public public static static extern extern int int MyFunction(string MyFunction(string param1); param1); public public class class MyClass( MyClass( )) {{ ... ... int int result result == MyFunction("Hello MyFunction("Hello Unmanaged Unmanaged Code"); Code"); ... ... }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** You can use the DllImport attribute to invoke unmanaged code in your C# programs. Unmanaged code is the term used for code that has been developed outside the .NET environment (that is, standard C compiled into DLL files). By using the DllImport attribute, you can invoke unmanaged code residing in dynamic-link libraries (DLLs) from your managed C# environment.
Invoking Unmanaged Code The DllImport attribute allows you to tag an extern method as residing in an unmanaged DLL. When your code calls this method, the common language runtime locates the DLL, loads it into the memory of your process, marshals parameters as necessary, and transfers control to the address at the beginning of the unmanaged code. This is unlike a normal program, which does not have direct access to the memory that is allocated to it. The following code provides an example of how to invoke unmanaged code: [DllImport("MyDLL.dll", EntryPoint="MyFunction")] public static extern int MyFunction(string param1); public class MyClass( ) { ... int result = MyFunction("Hello Unmanaged Code"); ... }
12
Module 17 (Optional): Attributes
Using the Transaction Attribute Topic Objective
To show how to use the Transaction attribute.
Lead-in
The Transaction attribute is used to manage transactions.
!
To Manage Transactions in COM+ #
Specify that your component be included when a transaction commit is requested
#
Use a Transaction attribute on the class that implements the component
using using EnterpriseServices; EnterpriseServices; ... ... [Transaction(TransactionOption.Required)] [Transaction(TransactionOption.Required)] public public class class MyTransactionalComponent MyTransactionalComponent :: ServicedComponent ServicedComponent {{ ... ... }}
*****************************ILLEGAL FOR NON-TRAINER USE****************************** It is likely that, as a Microsoft Visual Basic® or C++ developer working in a Microsoft environment, you are familiar with technologies such as COM+. An important feature of COM+ is that it allows you to develop components that can participate in distributed transactions, which are transactions that can span multiple databases, machines, and components.
Managing Transactions in COM+ Writing code to guarantee a correct transaction commit in a distributed environment is difficult. However, if you use COM+, it takes care of managing the transactional integrity of the system and coordinating events on the network. In this case, you only need to specify that your component be included when an application that uses your component requests a transaction commit. To make this specification, you can use a Transaction attribute on the class that implements the component, as follows: using EnterpriseServices; ... [Transaction(TransactionOption.Required)] public class MyTransactionalComponent : ServicedComponent { ... }
The Transaction attribute is one of the predefined .NET Framework attributes that the .NET Framework runtime interprets automatically.
Module 17 (Optional): Attributes
13
" Defining Custom Attributes Topic Objective
To introduce the topics covered in this section.
Lead-in
You can define your own custom attributes.
!
Defining Custom Attribute Scope
!
Defining an Attribute Class
!
Processing a Custom Attribute
!
Using Multiple Attributes
*****************************ILLEGAL FOR NON-TRAINER USE****************************** When you encounter a situation in which none of the predefined .NET Framework attributes satisfy your requirements, you can create your own attribute. Such a custom attribute will provide properties that allow you to store and retrieve information from the attribute. Like predefined attributes, custom attributes are objects that are associated with one or more programmatic elements. They are stored with the metadata of their associated elements, and they provide mechanisms for a program to retrieve their values. After completing this lesson, you will be able to: !
Define your own custom attributes.
!
Use your own custom attributes.
14
Module 17 (Optional): Attributes
Defining Custom Attribute Scope Topic Objective
To describe the scope of a custom attribute.
!
Use the AttributeUsage Tag to Define Scope #
Lead-in
Example
You need to scope your custom attribute to define the elements that can use your custom attribute.
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)] public public class class MyAttribute: MyAttribute: System.Attribute System.Attribute {{ ... ... }}
!
Use the Bitwise “or” Operator (|) to Specify Multiple Elements #
Example
[AttributeUsage(AttributeTargets.Class [AttributeUsage(AttributeTargets.Class || AttributeTargets.Struct)] AttributeTargets.Struct)] public public class class MyAttribute: MyAttribute: System.Attribute System.Attribute {{ ... } ... }
*****************************ILLEGAL FOR NON-TRAINER USE****************************** As with some predefined attributes, you must explicitly specify the programming element to which you want to apply a custom attribute. To do so, you annotate your custom attribute with an AttributeUsage tag as shown in the following example: [AttributeUsage(target_elements)] public class MyAttribute: System.Attribute { ... }
Defining Attribute Scope The parameter to AttributeUsage contains values from the System.AttributeTargets enumeration to specify how the custom attribute can be used. The members of this enumeration are summarized in the following table. Member name
Attribute can be applied to
All
Any element
Assembly
assembly
Class
class
Constructor
constructor
Delegate
delegate
Enum
enum
Event
event
Field
field
Interface
interface
Method
method
Module
module
Module 17 (Optional): Attributes
15
(continued) Member name
Attribute can be applied to
Parameter
parameter
Property
property
ReturnValue
return value
Struct
struct
Example of Using Custom Attributes To specify that the MyAttribute custom attribute can be applied only to methods, use the following code: [AttributeUsage(AttributeTargets.Method)] public class MyAttribute: System.Attribute { ... }
Specifying Multiple Elements If the attribute can be applied to more than one element type, use the bitwise “or” operator (|) to specify multiple target types. For example, if MyAttribute can also be applied to constructors, the earlier code will be modified as follows: [AttributeUsage(AttributeTargets.Method | !AttributeTargets.Constructor)] public class MyAttribute: System.Attribute { ... }
If a developer attempts to use MyAttribute in a context different than that which is defined by AttributeUsage, the developer’s code will not compile.
16
Module 17 (Optional): Attributes
Defining an Attribute Class Topic Objective
To describe what attribute classes are and how they are defined.
!
Lead-in
To define custom attributes, you must first define an attribute class. !
Deriving an Attribute Class #
All attribute classes must derive from System.Attribute, directly or indirectly
#
Suffix name of attribute class with “Attribute”
Components of an Attribute Class #
Define a public constructor for the attribute class whose arguments are the required positional parameters
#
Use non-static public read-write properties for optional named parameters
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Delivery Tip
Use the terminology carefully. Talking about attributes having attributes could be confusing to some students.
Key Points
1. Always append the suffix “Attribute” to the end of custom attribute classes. 2. Custom attribute classes must descend from System.Attribute.
After you define the scope of a custom attribute, you need to specify the way you want the custom attribute to behave. For this purpose, you must define an attribute class. Such a class will define the name of the attribute, how it can be created, and the information that it will store. The .NET Framework SDK provides a base class, System.Attribute that you must use to derive custom attribute classes and to access the values held in custom attributes.
Deriving an Attribute Class All custom attribute classes must derive from System.Attribute, either directly or indirectly. The following code provides an example: public class DeveloperInfoAttribute: System.Attribute { ... public DeveloperInfoAtribute(string developer) public string Date { get { ... } set { ... } } }
It is a good practice to append the name of a custom attribute class with the suffix “Attribute,” as in DeveloperInfoAttribute. This makes it easier to distinguish the attribute classes from the non-attribute classes.
Module 17 (Optional): Attributes
17
Components of an Attribute Class All attribute classes must have a constructor. For example, the DeveloperInfo attribute requires the name of the developer as a string parameter; it therefore has a constructor that accepts a string parameter. A custom attribute must define a single constructor that sets the mandatory information. The positional parameter or parameters of the attribute pass this information to the constructor. An attribute class can allow for optional values by declaring non-static public read-write properties or fields. You can specify these optional values as named parameters when using the attribute. For example, the following DeveloperInfoAttribute provides the required developer name, Bert, as a positional parameter and specifies the optional Date parameter, “08-28-2001” as a named parameter: [DeveloperInfoAttribute("Bert", Date="08-28-2001")] public class MyClass { ... }
18
Module 17 (Optional): Attributes
Processing a Custom Attribute Topic Objective
To describe how the compiler processes a custom attribute.
Lead-in
In this topic, you will see the process that occurs when the compiler encounters an attribute on a program element.
!
The Compilation Process 1. Searches for the Attribute Class 2. Checks the Scope of the Attribute 3. Checks for a Constructor in the Attribute 4. Creates an Instance of the Object 5. Checks for a Named Parameter 6. Sets Field or Property to Named Parameter Value 7. Saves Current State of Attribute Class
*****************************ILLEGAL FOR NON-TRAINER USE****************************** When the compiler encounters an attribute on a programming element, the compiler uses the following process to determine how to apply the attribute: 1. Searches for the attribute class 2. Checks the scope of the attribute 3. Checks for a constructor in the attribute 4. Creates an instance of the object 5. Checks for a named parameter 6. Sets the field or property to a named parameter value 7. Saves the current state of the attribute class To be completely accurate, the compiler actually verifies that it could create the attribute, and then stores the information to do so in the metadata. The compiler does not create attribute instances at compile time.
Module 17 (Optional): Attributes
19
Example To learn more about how the compiler handles attributes, consider the following example: [AttributeUsage(AttributeTargets.Class)] public class DeveloperInfoAttribute: System.Attribute { ... } ..... { ..... } [DeveloperInfo("Bert", Date="08-28-2001")] public class MyClass { ... }
The Compilation Process In the preceding example, when MyClass is compiled, the compiler will search for an attribute class called DevloperInfoAttribute. If the class cannot be located, the compiler will then search for DeveloperInfo. After it finds DeveloperInfo, the compiler will check whether the attribute is allowed on a class. Then it will check for a constructor that matches the parameters specified in the attribute use. If it finds one, it creates an instance of the object by calling the constructor with the specified values. If there is a named parameter, the compiler matches the name of the parameter with a field or property in the attribute class, and then sets the field or property to the specified value. Then the current state of the attribute class is saved to the metadata for the program element on which it is applied. Note As is mentioned in the previous topic, it is a good practice to add the suffix “Attribute” to the name of an attribute class. Strictly speaking, it is not necessary to do so. Even if you omit the Attribute suffix as shown in the example, your code will still compile correctly. However, without the Attribute suffix there are potential issues concerning how the compiler searches for classes. Always use the Attribute suffix.
20
Module 17 (Optional): Attributes
Using Multiple Attributes Topic Objective
To describe how the same attribute can be applied multiple times to the same programming element.
Lead-in
Depending upon the circumstances, it might be useful to use a custom attribute multiple times on the same element.
!
An Element Can Have More Than One Attribute #
!
Define both attributes separately
An Element Can Have More Than One Instance of The Same Attribute #
Use AllowMultiple = true
*****************************ILLEGAL FOR NON-TRAINER USE****************************** You can apply more than one attribute to a programming element, and you can use multiple instances of the same attribute in an application.
Using Multiple Attributes You can apply more than one attribute to a programming element. For example, the following code shows how you can tag the FinancialComponent class with two attributes: DeveloperInfo and DefaultProperty: [DeveloperInfo("Bert", Date="08-28-2001")] [DefaultProperty("Balance")] public class FinancialComponent: { ... public long Balance { ... } }
Module 17 (Optional): Attributes
21
Using the Same Attribute Multiple Times Delivery Tip
Stress that the default behavior of a custom attribute is that it does not permit its multiple use.
The default behavior of a custom attribute does not permit multiple instances of the attribute. However, under some circumstances it might make sense to allow an attribute to be used on the same element more than once. An example of this is the custom attribute DeveloperInfo. This attribute allows you to record the name of the developer that wrote a class. If more than one developer was involved in the development, you need to use the DeveloperInfo attribute more than once. For an attribute to permit this, you must mark it as AllowMultiple in the AttributeUsage attribute, as follows: [AttributeUsage(AttributeTargets.Class, AllowMultiple=true)] public class DeveloperInfoAttribute: System.Attribute { ... }
22
Module 17 (Optional): Attributes
" Retrieving Attribute Values Topic Objective
To introduce the topics covered in this section.
Lead-in
Having learned to create a custom attribute and apply it to a class, you now need to be able to query and process attribute information.
!
Examining Class Metadata
!
Querying for Attribute Information
*****************************ILLEGAL FOR NON-TRAINER USE****************************** After you have applied attributes to programming elements in your code, it is useful to be able to determine the values of the attributes. In this section, you will learn how to use reflection to examine the attribute metadata of a class and query classes for attribute information. After completing this lesson, you will be able to: !
Use reflection to examine the attribute metadata of a class.
!
Query classes for attribute information.
Module 17 (Optional): Attributes
23
Examining Class Metadata Topic Objective
To describe how to access the information inside a class at run time.
!
Lead-in
Attribute information is stored with the metadata of a class. To gain access to it, you first need to retrieve the metadata for that class. !
To Query Class Metadata Information: #
Use the MemberInfo class in System.Reflection
#
Populate a MemberInfo array by using the method System.Type.GetMembers
#
Create a System.Type object by using the typeof operator
Example
System.Reflection.MemberInfo[ System.Reflection.MemberInfo[ ]] memberInfoArray; memberInfoArray; memberInfoArray memberInfoArray == typeof(MyClass).GetMembers( typeof(MyClass).GetMembers( ); );
*****************************ILLEGAL FOR NON-TRAINER USE****************************** The .NET Framework runtime supplies a mechanism called reflection that allows you to query information held in metadata. Metadata is where attribute information is stored.
Using the MemberInfo Class The .NET Framework provides a namespace named System.Reflection, which contains classes that you can use for examining metadata. One particular class in this namespace—the MemberInfo class—is very useful if you need to find out about the attributes of a class. To populate a MemberInfo array, you can use the GetMembers method of the System.Type object. To create a System.Type object, you use the typeof operator with a class or any other element, as shown in the following code: System.Reflection.MemberInfo[ ] memberInfoArray; memberInfoArray = typeof(MyClass).GetMembers( ); ...
Once created, the memberInfoArray elements can be queried for metadata information about the members of the class MyClass. Tip If you need more detailed information, for example, if you want to discover the values of attributes that a method has, you can use a MethodInfo object. In addition, there are other “Info” classes: ConstructorInfo, EventInfo, FieldInfo, ParameterInfo, and PropertyInfo. Detailed information about how to use these classes is beyond the scope of this course. For more information, search for “System.Reflection namespace” in the .NET Framework SDK. Note MemberInfo is actually the abstract base class of the other “Info” types.
24
Module 17 (Optional): Attributes
Querying for Attribute Information Topic Objective
To show how to use the GetCustomAttributes method.
!
To Retrieve Custom Attribute Information: #
Lead-in
Once you have the MemberInfo for a class, you can query to find out attribute information for that class.
Use GetCustomAttributes to retrieve all attribute information as an array
System.Reflection.MemberInfo System.Reflection.MemberInfo typeInfo; typeInfo; typeInfo typeInfo == typeof(MyClass); typeof(MyClass); object[ object[ ]] attrs attrs == typeInfo.GetCustomAttributes(false); typeInfo.GetCustomAttributes(false); #
Iterate through the array and examine the values of each element in the array
#
Use the IsDefined method to determine whether a particular attribute has been defined for a class
*****************************ILLEGAL FOR NON-TRAINER USE****************************** After you create a MemberInfo variable typeInfo and initialize it with typeof(MyClass), you can query it to get information about the attributes that are applied to its associated class.
Retrieving Attribute Information The MemberInfo object has a method called GetCustomAttributes. This method retrieves the information about all attributes of a class and stores it as an array, as shown in the following code: System.Reflection.MemberInfo typeInfo; typeInfo = typeof(MyClass); object [ ] attrs = typeInfo.GetCustomAttributes(false);
For Your Information
GetCustomAttribute is an overloaded method. The parameter specifies whether to search the inheritance chain.
You can then iterate through the array to find the values of the attributes that you are interested in.
Module 17 (Optional): Attributes
25
Iterating Through Attributes You can iterate through the array of attributes and examine the value of each one in turn. In the following code, the only attribute of interest is DeveloperInfoAttribute, and all the others are ignored. For each DeveloperInfoAttribute found, the values of the Developer and Date properties are displayed as follows: ... object [ ] attrs = typeInfo.GetCustomAttributes(false); foreach(Attribute atr in attrs) { if (atr is DeveloperInfoAttribute) { DeveloperInfoAttribute dia = (DeveloperInfoAttribute)atr; Console.WriteLine("{0} {1}", dia.Developer, dia.Date); } } ...
Tip GetCustomAttributes is an overloaded method. If you only want values for that one attribute type, you can invoke this method by passing the type of the custom attribute you are looking for through it, as shown in the following code: object [ ] attrs = typeInfo.GetCustomAttributes(typeof(DeveloperInfoAttribute), false);
Using the IsDefined Method If there are no matching attributes for a class, GetCustomAttributes returns a null object reference. However, to find out whether a particular attribute has been defined for a class, you can use the IsDefined method of MemberInfo as follows: Type devInfoAttrType = typeof(DeveloperInfoAttribute); if (typeInfo.IsDefined(devInfoAttrType, false)) { Object [ ] attrs = typeInfo.GetCustomAttributes(devInfoAttrType, false); ... }
Note You can use the Microsoft Intermediate Language Disassembler to see these attributes inside the assembly.
26
Module 17 (Optional): Attributes
Demonstration: Custom Attributes Topic Objective
This demonstration shows how to declare and apply custom attributes and how to use reflection to retrieve custom attribute metadata.
Lead-in
This demonstration shows how to declare and apply a custom attribute and how to use the .NET Framework reflection mechanism to retrieve this metadata information.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** This demonstration shows how to declare a custom attribute and apply the custom attribute to a class and to a method. The .NET Framework reflection mechanism is then used to retrieve metadata information about the class and its members including custom attributes. The code for this demonstration is located in \Democode\Mod17\CustomAttribute. The demonstration declares a custom attribute MyAttribute and applies this attribute both to a class named MyClass1 and to a public method of MyClass1 named MyMethod. The demonstration then uses reflection to iterate over and print out metadata for MyClass1. The output of this demonstration is similar to the following: MyClass1 has 1 custom attributes Custom Attribute MyAttribute's Name Property: This is an example attribute associated with the class! MyClass1
MyClass1 members: member name: GetHashCode has 0 custom attributes: member name: Equals has 0 custom attributes: member name: ToString has 0 custom attributes: member name: MyMethod has 1 custom attributes: attribute has type: MyAttribute Name Property: This is an example attribute associated! with the method MyMethod in MyClass1 member name: GetType has 0 custom attributes: member name: .ctor has 0 custom attributes:
Module 17 (Optional): Attributes
27
Lab 17: Defining and Using Attributes Topic Objective
To introduce the lab.
Lead-in
In this lab, you will use the predefined Conditional attribute, and then create and use a custom attribute.
*****************************ILLEGAL FOR NON-TRAINER USE****************************** Explain the lab objectives.
Objectives After completing this lab, you will be able to: !
Use the predefined Conditional attribute.
!
Create a custom attribute.
!
Add a custom attribute value to a class.
!
Use reflection to query attribute values.
Prerequisites Before working on this lab, you should be familiar with the following: !
Creating classes in C#
!
Defining constructors and methods
!
Using the typeof operator
!
Using properties and indexers in C#
Estimated time to complete this lab: 45 minutes
28
Module 17 (Optional): Attributes
Exercise 1 Using the Conditional Attribute In this exercise, you will use the predefined Conditional attribute to conditionally execute your code. Conditional execution is a useful technique if you want to incorporate testing or debugging code into a project but do not want to edit the project and remove the debugging code after the system is complete and functioning correctly. During this exercise, you will add a method called DumpToScreen to the BankAccount class. This method will display the details of the account. You will use the Conditional attribute to execute this method depending on the value of a symbol called DEBUG_ACCOUNT.
! Apply the Conditional attribute 1. Open the Bank.sln project in the install folder\Labs\Lab17\Starter\Bank folder. 2. In the BankAccount class, add a public void method called DumpToScreen that takes no parameters. The method must display the contents of the account: account number, account holder, account type, and account balance. The following code shows a possible example of the method: public void DumpToScreen( ) { Console.WriteLine("Debugging account {0}. Holder is {1}. !Type is {2}. Balance is {3}", this.accNo, this.holder, this.accType, this.accBal); }
3. Make use of the method’s dependence on the DEBUG_ACCOUNT symbol. Add the following Conditional attribute before the method as follows: [Conditional("DEBUG_ACCOUNT")]
4. Add a using directive for the System.Diagnostics namespace. 5. Compile your code and correct any errors.
Module 17 (Optional): Attributes
29
! Test the Conditional attribute 1. Open the TestHarness.sln project in the install folder\Labs\Lab17\ Starter\TestHarness folder. 2. Add a reference to the Bank library. a. In Solution Explorer, expand the TestHarness tree. b. Right-click References, and then click Add Reference. c. Click Browse, and then navigate to install folder\Labs\Lab17\ Starter\Bank\Bin\Debug. d. Click Bank.dll, click Open, and then click OK. 3. Review the Main method of the CreateAccount class. Notice that it creates a new bank account. 4. Add the following line of code to Main to call the DumpToScreen method of myAccount: myAccount.DumpToScreen( );
5. Save your work, compile the project, and correct any errors. 6. Run the test harness. Notice that nothing happens. This is because the DumpToScreen method has not been called. 7. Use the MSIL disassembler (ildasm), from the Visual Studio .NET command prompt to examine install folder\Labs\Lab17\Starter\ Bank\Bin\Debug\Bank.dll. Important To use Microsoft Visual Studio .NET tools within a command prompt window, the command prompt window must have the proper environment settings. The Visual Studio .NET Command Prompt window provides such an environment. To run a Visual Studio .NET Command Prompt window: on the start menu, point to All Programs, point to Microsoft Visual Studio .NET, Visual Studio .NET Tools, and then click Visual Studio .NET Command Prompt. You will see that the DumpToScreen method is present in the BankAccount class. 8. Double-click the DumpToScreen method to display the Microsoft intermediate language (MSIL) code. You will see the ConditionalAttribute at the beginning of the method. The problem is in the test harness. Because of the ConditionalAttribute on DumpToScreen, the runtime will effectively ignore calls made to that method if the DEBUG_ACCOUNT symbol is not defined when the calling program is compiled. The call is made, but because DEBUG_ACCOUNT is not defined, the runtime finishes the call immediately.
30
Module 17 (Optional): Attributes
9. Close the MSIL disassembler. 10. Return to the test harness. At the top of the CreateAccount.cs file, before the first using directive, add the following code: #define DEBUG_ACCOUNT
This defines the DEBUG_ACCOUNT symbol. 11. Save and compile the test harness, correcting any errors. 12. Run the test harness. Notice that the DumpToScreen method displays the information from myAccount.
Module 17 (Optional): Attributes
31
Exercise 2 Defining and Using a Custom Attribute In this exercise, you will create a custom attribute called DeveloperInfoAttribute. This attribute will allow the name of the developer and, optionally, the creation date of a class to be stored in the metadata of that class. This attribute will permit multiple use because more than one developer might be involved in the coding of a class. You will then write a method that retrieves and displays all of the DevloperInfoAttribute values for a class.
! Define a custom attribute class 1. Using Visual Studio .NET, create a new Microsoft Visual C# project, using the information shown in the following table. Element
Value
Project Type
Visual C# Projects
Template
Class Library
Name
CustomAttribute
Location
install folder\Labs\Lab17\Starter
2. Change the name and file name of class Class1 to DeveloperInfoAttribute. Make sure that you also change the name of the constructor. 3. Specify that the DeveloperInfoAttribute class is derived from System.Attribute. This attribute will be applicable to classes, enums, and structs only. It will also be allowed to occur more than once when it is used. 4. Add the following AttributeUsage attribute before the class definition: [AttributeUsage(AttributeTargets.Class | !AttributeTargets.Enum | AttributeTargets.Struct, !AllowMultiple=true)]
5. Document your attribute with a meaningful summary (between the <summary> tags). Use the exercise description to help you. 6. The DeveloperInfoAttribute attribute requires the name of the developer of the class as a mandatory parameter and takes the date that the class was written as an optional string parameter. Add private instance variables to hold this information, as follows: private string developerName; private string dateCreated;
7. Modify the constructor so that it takes a single string parameter that is also called developerName, and add a line of code to the constructor that assigns this parameter to this.developerName. 8. Add a public string read-only property called Developer that can be used to get the value of developerName. Do not write a set accessor.
32
Module 17 (Optional): Attributes
9. Add another public string property that is called Date. This property should have a get accessor that reads dateCreated and a set accessor that writes dateCreated. 10. Compile the class and correct any errors. Because the class is in a class library, the compilation process will produce a DLL (CustomAttribute.dll) rather than a stand-alone executable program. The complete code for the DeveloperInfoAttribute class follows: namespace CustomAttribute { using System; /// <summary> /// This class is a custom attribute that allows /// the name of the developer of a class to be stored /// with the metadata of that class. /// [AttributeUsage(AttributeTargets.Class | !AttributeTargets.Enum | AttributeTargets.Struct, !AllowMultiple=true)] public class DeveloperInfoAttribute: System.Attribute { private string developerName; private string dateCreated; // Constructor. Developer name is the only // mandatory parameter for this attribute. public DeveloperInfoAttribute(string developerName) { this.developerName = developerName; } public string Developer { get { return developerName; } } // Optional parameter public string Date { get { return dateCreated; } set { dateCreated = value; } } } }
Module 17 (Optional): Attributes
! Add a custom attribute to a class 1. You will now use the DeveloperInfo attribute to record the name of the developer of the Rational number class. Open the Rational.sln project in the install folder\Labs\Lab17\Starter\Rational folder. 2. Perform the following steps to add a reference to the CustomAttribute library that you created earlier: a. In Solution Explorer, expand the Rational tree. b. Right-click References, and then click Add Reference. c. In the Add Reference dialog box, click Browse. d. Navigate to the install folder\Labs\Lab17\Starter\ CustomAttribute\Bin\Debug folder, and click CustomAttribute.dll. e. Click Open, and then click OK. 3. Add a CustomAttribute.DeveloperInfo attribute to the Rational class, specifying your name as the developer and the current date as the optional date parameter, as follows: [CustomAttribute.DeveloperInfo("Your Name", !Date="Today")]
4. Add a second developer to the Rational class. 5. Compile the Rational project and correct any errors. 6. Open a Visual Studio .NET Command Prompt window and navigate to the install folder\Labs\Lab17\Starter\Rational\Bin\Debug folder. This folder should contain your Rational.exe executable. 7. Run the MSIL disassembler and open Rational.exe. 8. Expand the Rational namespace in the tree view. 9. Expand the Rational class. 10. Near the top of the class, notice your custom attribute and the values that you supplied. 11. Close the MSIL disassembler.
33
34
Module 17 (Optional): Attributes
! Use reflection to query attribute values Using the MSIL disassembler is only one way to examine attribute values. You can also use reflection in C# programs. Return to Visual Studio, and edit the TestRational class in the Rational project. 1. In the Main method, create a variable called attrInfo of type System.Reflection.MemberInfo, as shown in the following code: public static void Main( ) { System.Reflection.MemberInfo attrInfo; ...
2. You can use a MemberInfo object to hold information about the members of a class. Assign the Rational type to the MemberInfo object by using the typeof operator, as follows: attrInfo = typeof(Rational);
3. The attributes of a class are held as part of the class information. You can retrieve the attribute values by using the GetCustomAttributes method. Create an object array called attrs, and use the GetCustomAttributes method of attrInfo to find all of the custom attributes used by the Rational class, as shown in the following code: object[ ] attrs = attrInfo.GetCustomAttributes(false);
4. Now you need to extract the attribute information that is stored in the attrs array and print it. Create a variable called developerAttr of type CustomAttribute.DeveloperInfoAttribute, and assign it the first element in the attrs array, casting as appropriate, as shown in the following code: CustomAttribute.DeveloperInfoAttribute developerAttr; developerAttr = !(CustomAttribute.DeveloperInfoAttribute)attrs[0];
Note In production code, you would use reflection rather than a cast to determine the type of the attribute. 5. Use the get accessor of the DeveloperInfoAttribute attribute to retrieve the Developer and Date attributes and print them out as follows: Console.WriteLine("Developer: {0}\tDate: {1}", !developerAttr.Developer, developerAttr.Date);
6. Repeat steps 4 and 5 for element 1 of the attrs array. You can use a loop if you want to be able to retrieve the values of more than two attributes.
Module 17 (Optional): Attributes
7. Compile the project and correct any errors. The completed code for the Main method is shown in the following code: namespace Rational { using System; // Test harness public class TestRational { public static void Main( ) { System.Reflection.MemberInfo attrInfo; attrInfo = typeof(Rational); object[ ] attrs = attrInfo.GetCustomAttributes(false); CustomAttribute.DeveloperInfoAttribute developerAttr; developerAttr = !(CustomAttribute.DeveloperInfoAttribute)attrs[0]; Console.WriteLine("Developer: {0}\tDate: {1}", !developerAttr.Developer, developerAttr.Date); developerAttr = !(CustomAttribute.DeveloperInfoAttribute)attrs[1]; Console.WriteLine("Developer: {0}\tDate: {1}", !developerAttr.Developer, developerAttr.Date); } } }
Here is an alternative Main that uses a foreach loop: public static void Main( ) { System.Reflection.MemberInfo attrInfo; attrInfo = typeof(Rational); object[ ] attrs = attrInfo.GetCustomAttributes(false); foreach (CustomAttribute.DeveloperInfoAttribute ! devAttr in attrs) { Console.WriteLine("Developer: {0}\tDate: {1}", !devAttr.Developer, devAttr.Date); } }
8. When you run this program, it will display the names and dates that you supplied as DeveloperInfoAttribute information to the Rational class.
35
36
Module 17 (Optional): Attributes
Review Topic Objective
To reinforce module objectives by reviewing key points.
Lead-in
The review questions cover some of the key concepts taught in the module.
!
Overview of Attributes
!
Defining Custom Attributes
!
Retrieving Attribute Values
*****************************ILLEGAL FOR NON-TRAINER USE****************************** 1. Can you tag individual objects by using attributes? No. Attributes can be associated with classes or other types, or with methods, parameters, return values, constructors, assemblies, delegates, events, interfaces, properties, or fields, but not with individual objects.
2. Where are attribute values stored? An attribute value is stored with the metadata of the programming element that it was used with.
3. What mechanism is used to determine the value of an attribute at run time? Reflection.
Module 17 (Optional): Attributes
4. Define an attribute class called CodeTestAttributes that is applicable only to classes. It should have no positional parameters and two named parameters called Reviewed and HasTestSuite. These parameters should be of type bool and should be implemented by using read/write properties. using System; [AttributeUsage(AttributeTargets.Class)] public class CodeTestAttributes: System.Attribute { public bool Reviewed { get { return reviewed; } set { reviewed = value; } } public bool HasTestSuite { get { return hasTestSuite; } set { hasTestSuite = value; } } private bool reviewed, hasTestSuite; }
5. Define a class called Widget, and use CodeTestAttributes from the previous question to mark that Widget has been reviewed but has no test suite. [CodeTestAttributes(Reviewed=true, HasTestSuite=false)] class Widget { ... }
6. Suppose that Widget from the previous question had a method called LogBug. Could CodeTestAttributes be used to mark only this method? No. CodeTestAttributes can only target whole classes.
37
THIS PAGE INTENTIONALLY LEFT BLANK